From 837815636397b25ee6894bcb66b65f11afe25abe Mon Sep 17 00:00:00 2001 From: tashcan Date: Thu, 15 Oct 2026 16:40:40 -0700 Subject: [PATCH 1/2] Add typed IL2CPP library Resolve managed classes and members once, check their signatures against metadata, and invoke methods through typed bindings. Managed objects are owned through GC handles, and missing metadata is reported without unwinding through hooks. Fake-runtime tests cover calls, dispatch, fields and GC ownership against a controlled runtime. --- .github/workflows/ci.yaml | 26 +- libs/il2cpp/include/il2cpp/api.h | 35 ++ libs/il2cpp/include/il2cpp/il2cpp.h | 512 +++++++++++++++++++++++ libs/il2cpp/src/api.cc | 80 ++++ libs/il2cpp/src/il2cpp.cc | 410 +++++++++++++++++++ libs/il2cpp/xmake.lua | 9 + mods/xmake.lua | 5 +- tests/il2cpp_bindings.cc | 612 ++++++++++++++++++++++++++++ tests/il2cpp_gc.cc | 78 ++++ tests/il2cpp_test_api.h | 23 ++ xmake.lua | 32 +- 11 files changed, 1777 insertions(+), 45 deletions(-) create mode 100644 libs/il2cpp/include/il2cpp/api.h create mode 100644 libs/il2cpp/include/il2cpp/il2cpp.h create mode 100644 libs/il2cpp/src/api.cc create mode 100644 libs/il2cpp/src/il2cpp.cc create mode 100644 libs/il2cpp/xmake.lua create mode 100644 tests/il2cpp_bindings.cc create mode 100644 tests/il2cpp_gc.cc create mode 100644 tests/il2cpp_test_api.h diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 131a45df4..450d941ee 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -191,16 +191,16 @@ jobs: - name: Test IL2CPP runtime helpers run: | - xmake build -y il2cpp-runtime-tests + xmake build -y il2cpp-bindings-tests if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - xmake run il2cpp-runtime-tests + xmake run il2cpp-bindings-tests if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: Test IL2CPP class lookup run: | - xmake build -y il2cpp-class-lookup-tests + xmake build -y il2cpp-gc-tests if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - xmake run il2cpp-class-lookup-tests + xmake run il2cpp-gc-tests if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: Build @@ -225,11 +225,6 @@ jobs: "build/windows/x64/release/stfc-community-mod.dll" ` "version.dll" - - name: Test checked IL2CPP helpers - run: | - xmake build -y il2cpp-checked-tests - xmake run il2cpp-checked-tests - - name: Verify DLL provenance shell: pwsh run: | @@ -500,13 +495,13 @@ jobs: - name: Test IL2CPP runtime helpers run: | - xmake build -y il2cpp-runtime-tests - xmake run il2cpp-runtime-tests + xmake build -y il2cpp-bindings-tests + xmake run il2cpp-bindings-tests - name: Test IL2CPP class lookup run: | - xmake build -y il2cpp-class-lookup-tests - xmake run il2cpp-class-lookup-tests + xmake build -y il2cpp-gc-tests + xmake run il2cpp-gc-tests - name: Build # -D keeps the existing XMake compiler-cache diagnostics too. @@ -535,11 +530,6 @@ jobs: echo "::warning::Swift module cache directory was not created" fi - - name: Test checked IL2CPP helpers - run: | - xmake build -y il2cpp-checked-tests - xmake run il2cpp-checked-tests - - name: Verify portable macOS linkage shell: bash run: | diff --git a/libs/il2cpp/include/il2cpp/api.h b/libs/il2cpp/include/il2cpp/api.h new file mode 100644 index 000000000..1cc56a404 --- /dev/null +++ b/libs/il2cpp/include/il2cpp/api.h @@ -0,0 +1,35 @@ +#pragma once + +#ifdef _WIN32 +#pragma comment(lib, "GameAssembly.lib") +#endif + +#include +#include +#include +#include + +#if defined(__cplusplus) +extern "C" { +#endif // __cplusplus +#if _WIN32 +#define DO_API(r, n, p) IL2CPP_IMPORT r n p; +#define DO_API_NO_RETURN(r, n, p) IL2CPP_IMPORT NORETURN r n p; +#else +#define DO_API(r, n, p) \ + using n##_t = r(*) p; \ + extern n##_t n; +#define DO_API_NO_RETURN(r, n, p) DO_API(r, n, p) +#endif +#include "il2cpp-api-functions.h" +#undef DO_API +#undef DO_API_NO_RETURN +#if defined(__cplusplus) +} +#endif // __cplusplus + +namespace il2cpp +{ +// Bind macOS exports from the already loaded GameAssembly image. Windows uses imports. +void Initialize(void* assembly); +} // namespace il2cpp diff --git a/libs/il2cpp/include/il2cpp/il2cpp.h b/libs/il2cpp/include/il2cpp/il2cpp.h new file mode 100644 index 000000000..92630c80d --- /dev/null +++ b/libs/il2cpp/include/il2cpp/il2cpp.h @@ -0,0 +1,512 @@ +#pragma once + +// Typed access to the game's managed classes. Bindings name a managed class once, then read +// properties and fields and call methods by name. Member lookups and signature checks happen +// once. After that, calls jump straight to the generated native code. + +#include "api.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace il2cpp +{ +// Missing classes and members, and signatures that no longer match the binding, are reported +// once per lookup. Without a handler, reports go to stderr. +void SetErrorHandler(void (*handler)(std::string_view message)); + +// Compile-time member name, used as a template argument: Call<"Show">(). +template struct Name { + char text[N]; + constexpr Name(const char (&value)[N]) + { std::copy_n(value, N, text); } + constexpr std::string_view View() const + { return {text, N - 1}; } +}; + +// Resolved method metadata. Hooks detour Pointer(); typed calls go through Class and Object. +class Method +{ +public: + Method() = default; + explicit Method(const MethodInfo* info) + : info_(info) + { + } + explicit operator bool() const + { return info_ != nullptr; } + const MethodInfo* Info() const + { return info_; } + // Generated code takes `this` first (for instance methods) and a trailing const MethodInfo*. + void* Pointer() const + { return info_ ? reinterpret_cast(info_->methodPointer) : nullptr; } + +private: + const MethodInfo* info_ = nullptr; +}; + +namespace detail +{ + // What a C++ type means at the managed boundary. Bindings declare these through their template + // arguments; resolution compares them with the metadata once and rejects drifted signatures. + struct Slot { + enum class Kind : uint8_t { kVoid, kValue, kReference }; + Kind kind = Kind::kVoid; + bool byref = false; + uint32_t size = 0; + Il2CppTypeEnum primitive = IL2CPP_TYPE_END; + // Required managed class for typed references; null accepts any reference type. + Il2CppClass* (*klass)() = nullptr; + }; + struct Signature { + Slot result; + std::span params; + }; + + Il2CppClass* StringClass(); + Il2CppClass* TypeClass(); + + template + concept Bound = requires { T::Class::Raw(); }; + + template constexpr auto ClassOf() + { + if constexpr (Bound) + return &T::Class::Raw; + else if constexpr (std::is_same_v) + return &StringClass; + else if constexpr (std::is_same_v) + return &TypeClass; + else + return static_cast(nullptr); + } + + template constexpr Il2CppTypeEnum Primitive() + { + if constexpr (std::is_enum_v) + return Primitive>(); + else if constexpr (std::is_same_v) + return IL2CPP_TYPE_BOOLEAN; + else if constexpr (std::is_same_v) + return IL2CPP_TYPE_CHAR; + else if constexpr (std::is_same_v) + return IL2CPP_TYPE_R4; + else if constexpr (std::is_same_v) + return IL2CPP_TYPE_R8; + else if constexpr (std::is_integral_v) { + constexpr bool kSigned = std::is_signed_v; + if constexpr (sizeof(T) == 1) + return kSigned ? IL2CPP_TYPE_I1 : IL2CPP_TYPE_U1; + else if constexpr (sizeof(T) == 2) + return kSigned ? IL2CPP_TYPE_I2 : IL2CPP_TYPE_U2; + else if constexpr (sizeof(T) == 4) + return kSigned ? IL2CPP_TYPE_I4 : IL2CPP_TYPE_U4; + else + return kSigned ? IL2CPP_TYPE_I8 : IL2CPP_TYPE_U8; + } + return IL2CPP_TYPE_VALUETYPE; + } + + template struct Unref { + using Type = T; + static constexpr bool kByRef = false; + }; + template struct Unref> { + using Type = T; + static constexpr bool kByRef = true; + }; + + template constexpr Slot SlotOf() + { + using U = typename Unref::Type; + Slot slot; + slot.byref = Unref::kByRef; + if constexpr (std::is_void_v) + return slot; + else if constexpr (std::is_same_v) + slot.kind = Slot::Kind::kReference; + else if constexpr (std::is_pointer_v) { + static_assert(!std::is_function_v>, "managed values cannot be function pointers"); + slot.kind = Slot::Kind::kReference; + slot.klass = ClassOf>>(); + } else { + static_assert(std::is_trivially_copyable_v, "managed values must be trivially copyable"); + slot.kind = Slot::Kind::kValue; + slot.size = sizeof(U); + slot.primitive = Primitive(); + } + return slot; + } + + template inline constexpr std::array kParams{SlotOf()...}; + template constexpr Signature SignatureOf() + { return {SlotOf(), kParams}; } + + Il2CppClass* FindClass(std::string_view assembly, std::string_view namespaze, std::string_view name); + Method FindMethod(Il2CppClass* klass, std::string_view name, int count); + Method FindMethod(Il2CppClass* klass, std::string_view name, std::initializer_list params); + const MethodInfo* ResolveMethod(Il2CppClass* klass, std::string_view name, const Signature& signature, bool is_static); + const MethodInfo* ResolveProperty(Il2CppClass* klass, std::string_view name, const Slot& value, bool setter, + bool is_static); + FieldInfo* ResolveField(Il2CppClass* klass, std::string_view name, const Slot& value, bool is_static); + + // Members of bound classes resolve once. Unbound wrappers (generic collections, Object itself) + // resolve against the receiver's runtime class, cached per class and thread. + template auto Lookup(const Il2CppObject& self, Resolve resolve) + { + using Result = std::invoke_result_t; + if constexpr (Bound) { + static const Result member = resolve(Self::Class::Raw()); + return member; + } else { + static thread_local Il2CppClass* last = nullptr; + static thread_local Result cached{}; + if (self.klass != last) { + static thread_local std::unordered_map members; + auto [found, inserted] = members.try_emplace(self.klass); + if (inserted) + found->second = resolve(self.klass); + cached = found->second; + last = self.klass; + } + return cached; + } + } + + template using Native = std::conditional_t< + Unref::kByRef, typename Unref::Type*, + std::conditional_t, void*, T>>; + template Native ToNative(T& value) + { + if constexpr (Unref::kByRef) + return &value.get(); + else + return value; + } + template void* ToBoxedArgument(T& value) + { + if constexpr (Unref::kByRef) + return &value.get(); + else if constexpr (std::is_pointer_v || std::is_same_v) + return value; + else + return &value; + } + + Il2CppObject* InvokeBoxed(const MethodInfo* method, void* target, void** args); + void ReportException(const MethodInfo* method, Il2CppException* exception); + + template R InvokeNative(const MethodInfo* method, Il2CppObject* target, A... args) + { + // Fully shared generic code takes a different native ABI. Let the runtime marshal it. + if (method->has_full_generic_sharing_signature) { + void* argv[] = {ToBoxedArgument(args)..., nullptr}; + // The runtime invoker takes value-type receivers unboxed. + void* receiver = target && il2cpp_class_is_valuetype(method->klass) ? il2cpp_object_unbox(target) : target; + auto* result = InvokeBoxed(method, receiver, argv); + if constexpr (std::is_pointer_v) + return reinterpret_cast(result); + else if constexpr (!std::is_void_v) { + R value{}; + if (result) + std::memcpy(&value, il2cpp_object_unbox(result), sizeof(R)); + return value; + } else + return; + } + if (method->flags & METHOD_ATTRIBUTE_STATIC) { + using Fn = R (*)(Native..., const MethodInfo*); + return reinterpret_cast(method->methodPointer)(ToNative(args)..., method); + } + // virtualMethodPointer takes a boxed receiver, including value-type methods (adjustor thunks). + using Fn = R (*)(Il2CppObject*, Native..., const MethodInfo*); + return reinterpret_cast(method->virtualMethodPointer)(target, ToNative(args)..., method); + } + + // Methods are validated at resolution, so this is a plain native call. Missing members (already + // reported) and missing receivers return a value-initialized R. Managed exceptions stop here: + // they are reported once per method and the call returns R(), so a throwing getter cannot unwind + // through a hook and skip the game's own code. + template R Invoke(const MethodInfo* method, Il2CppObject* target, A... args) + { + const bool is_static = method && (method->flags & METHOD_ATTRIBUTE_STATIC); + if (!method || (!is_static && !target)) + return R(); + if (!is_static && (method->flags & METHOD_ATTRIBUTE_VIRTUAL)) { + method = il2cpp_object_get_virtual_method(target, method); + if (!method) + return R(); + } + try { + return InvokeNative(method, target, args...); + } catch (const Il2CppExceptionWrapper& error) { + ReportException(method, error.ex); + } catch (...) { + ReportException(method, nullptr); + } + return R(); + } + + template T ReadField(FieldInfo* field, const Il2CppObject* object) + { + T value{}; + if (!field) + return value; + if (field->type->attrs & FIELD_ATTRIBUTE_STATIC) + il2cpp_field_static_get_value(field, &value); + else if (object) + std::memcpy(&value, reinterpret_cast(object) + field->offset, sizeof(T)); + return value; + } + // The runtime's setter applies GC write barriers to references, including those inside structs. + // It takes value types by address but stores a reference argument as the reference itself. + template void WriteField(FieldInfo* field, Il2CppObject* object, T value) + { + if (!field) + return; + void* argument = &value; + if constexpr (std::is_pointer_v || std::is_same_v) { + if (!il2cpp_class_is_valuetype(il2cpp_class_from_type(field->type))) + argument = const_cast(static_cast(value)); + } + if (field->type->attrs & FIELD_ATTRIBUTE_STATIC) + il2cpp_field_static_set_value(field, argument); + else if (object) + il2cpp_field_set_value(object, field, argument); + } +} // namespace detail + +// Names one managed class. A binding declares `using Class = il2cpp::Class<...>;` and uses this for +// static members, hooks, and type checks. Nested types use "Outer/Inner". +template struct Class { + static Il2CppClass* Raw() + { + static Il2CppClass* const klass = detail::FindClass(Assembly.View(), Namespace.View(), TypeName.View()); + return klass; + } + static Il2CppReflectionType* TypeObject() + { + auto* klass = Raw(); + return klass ? reinterpret_cast(il2cpp_type_get_object(il2cpp_class_get_type(klass))) + : nullptr; + } + + // Lookups for detours. A count of -1 accepts any arity; parameter names are "Namespace.Type". + static il2cpp::Method Method(std::string_view name, int count = -1) + { return detail::FindMethod(Raw(), name, count); } + static il2cpp::Method Method(std::string_view name, std::initializer_list params) + { return detail::FindMethod(Raw(), name, params); } + + template static R Call(A... args) + { + static const auto* method = detail::ResolveMethod(Raw(), N.View(), detail::SignatureOf(), true); + return detail::Invoke(method, nullptr, args...); + } + template static T Get() + { + static const auto* getter = detail::ResolveProperty(Raw(), N.View(), detail::SlotOf(), false, true); + return detail::Invoke(getter, nullptr); + } + template static void Set(T value) + { + static const auto* setter = detail::ResolveProperty(Raw(), N.View(), detail::SlotOf(), true, true); + detail::Invoke(setter, nullptr, value); + } + template static T Field() + { + static auto* field = detail::ResolveField(Raw(), N.View(), detail::SlotOf(), true); + return detail::ReadField(field, nullptr); + } + template static void SetField(T value) + { + static auto* field = detail::ResolveField(Raw(), N.View(), detail::SlotOf(), true); + detail::WriteField(field, nullptr, value); + } +}; + +// Base for reference-type bindings. A binding pointer is the managed object itself; bindings add +// no data members. Receivers must not be null. Missing members return a value-initialized result. +struct Object : Il2CppObject { + template T Get(this Self& self) + { + auto* getter = detail::Lookup(self, [](Il2CppClass* klass) { + return detail::ResolveProperty(klass, N.View(), detail::SlotOf(), false, false); + }); + return detail::Invoke(getter, &self); + } + template void Set(this Self& self, T value) + { + auto* setter = detail::Lookup(self, [](Il2CppClass* klass) { + return detail::ResolveProperty(klass, N.View(), detail::SlotOf(), true, false); + }); + detail::Invoke(setter, &self, value); + } + template T Field(this Self& self) + { + auto* field = detail::Lookup(self, [](Il2CppClass* klass) { + return detail::ResolveField(klass, N.View(), detail::SlotOf(), false); + }); + return detail::ReadField(field, &self); + } + template void SetField(this Self& self, T value) + { + auto* field = detail::Lookup(self, [](Il2CppClass* klass) { + return detail::ResolveField(klass, N.View(), detail::SlotOf(), false); + }); + detail::WriteField(field, &self, value); + } + // Overloads are selected by the C++ argument types. std::ref marks ref/out parameters. + template R Call(this Self& self, A... args) + { + auto* method = detail::Lookup(self, [](Il2CppClass* klass) { + return detail::ResolveMethod(klass, N.View(), detail::SignatureOf(), false); + }); + return detail::Invoke(method, &self, args...); + } + + template bool Is() const + { + auto* klass = T::Class::Raw(); + return klass && il2cpp_class_is_assignable_from(klass, this->klass); + } + template T* As() + { return Is() ? static_cast(this) : nullptr; } +}; + +// Allocate T and run the constructor matching the arguments. Null when the class or constructor +// is missing. Native stacks are scanned by the GC; keep a GcHandle to hold the result longer. +template T* New(A... args) +{ + static const auto* ctor = detail::ResolveMethod(T::Class::Raw(), ".ctor", detail::SignatureOf(), false); + if (!ctor) + return nullptr; + auto* object = static_cast(il2cpp_object_new(T::Class::Raw())); + detail::Invoke(ctor, object, args...); + return object; +} + +// Managed T[]. Element references are borrowed; use Set to store references with a GC barrier. +// Set supports references and primitives only. +template struct Array : Il2CppObject { + Il2CppArrayBounds* bounds; + il2cpp_array_size_t max_length; + + static Array* New(Il2CppClass* element, size_t length) + { return reinterpret_cast(il2cpp_array_new(element, length)); } + + size_t size() const + { return max_length; } + T* data() + { return reinterpret_cast(reinterpret_cast(this)->vector); } + T* begin() + { return data(); } + T* end() + { return data() + max_length; } + T operator[](size_t index) + { return data()[index]; } + void Set(size_t index, T value) + { + static_assert(std::is_pointer_v || std::is_arithmetic_v || std::is_enum_v, + "structs may hold references that need per-field write barriers"); + if constexpr (std::is_pointer_v) + il2cpp_gc_wbarrier_set_field(this, reinterpret_cast(data() + index), value); + else + data()[index] = value; + } +}; + +// System.Collections.Generic.List`1, or any class with the same indexer and Count members. +// Members resolve on the runtime class, so this works for inflated generics and IList implementers. +template struct List : Object { + int32_t Count() + { return Call<"get_Count", int32_t>(); } + T operator[](int32_t index) + { return Call<"get_Item", T>(index); } + void Add(T value) + { Call<"Add">(value); } + void RemoveAt(int32_t index) + { Call<"RemoveAt">(index); } + + struct Iterator { + List* list; + int32_t index; + T operator*() const + { return (*list)[index]; } + Iterator& operator++() + { + ++index; + return *this; + } + bool operator==(const Iterator& other) const + { return index == other.index; } + }; + // Count is read once; do not mutate the list while iterating. + Iterator begin() + { return {this, 0}; } + Iterator end() + { return {this, Count()}; } +}; + +// Build a delegate of `delegate_class` bound to a static method. With `native`, invocations jump to +// that C++ function instead of the method's generated code; it must match the method's native ABI. +Il2CppObject* NewDelegate(Il2CppClass* delegate_class, Method target, Il2CppMethodPointer native = nullptr); + +// Move-only managed root. Weak handles do not retain the managed object. +// Release roots before the runtime shuts down; these do not destroy Unity native resources. +class GcHandle +{ +public: + GcHandle() = default; + static GcHandle Strong(Il2CppObject* object) + { return GcHandle(object ? il2cpp_gchandle_new(object, false) : nullptr); } + static GcHandle Weak(Il2CppObject* object) + { return GcHandle(object ? il2cpp_gchandle_new_weakref(object, false) : nullptr); } + ~GcHandle() + { Reset(); } + GcHandle(const GcHandle&) = delete; + GcHandle& operator=(const GcHandle&) = delete; + GcHandle(GcHandle&& other) noexcept + : handle_(std::exchange(other.handle_, nullptr)) + { + } + GcHandle& operator=(GcHandle&& other) noexcept + { + if (this != &other) { + Reset(); + handle_ = std::exchange(other.handle_, nullptr); + } + return *this; + } + explicit operator bool() const + { return handle_ != nullptr; } + Il2CppObject* Get() const + { return handle_ ? il2cpp_gchandle_get_target(handle_) : nullptr; } + template T* Get() const + { return static_cast(Get()); } + void Reset() + { + if (handle_) + il2cpp_gchandle_free(std::exchange(handle_, nullptr)); + } + +private: + explicit GcHandle(Il2CppGCHandle handle) + : handle_(handle) + { + } + Il2CppGCHandle handle_ = nullptr; +}; + +// Unity engine internal calls, e.g. "UnityEngine.Input::GetKeyInt(UnityEngine.KeyCode)". +template Signature* ResolveICall(const char* name) +{ return reinterpret_cast(il2cpp_resolve_icall(name)); } +} // namespace il2cpp diff --git a/libs/il2cpp/src/api.cc b/libs/il2cpp/src/api.cc new file mode 100644 index 000000000..ee42c59b3 --- /dev/null +++ b/libs/il2cpp/src/api.cc @@ -0,0 +1,80 @@ +#include + +#include + +#if !_WIN32 +#include +#if defined(__cplusplus) +extern "C" { +#endif // __cplusplus +#define DO_API(r, n, p) n##_t n; +#define DO_API_NO_RETURN(r, n, p) DO_API(r, n, p) +#include "il2cpp-api-functions.h" +#undef DO_API +#undef DO_API_NO_RETURN +#if defined(__cplusplus) +} +#endif // __cplusplus +#endif + +void il2cpp::Initialize(void* assembly) +{ +#if !_WIN32 + if (!assembly) + throw std::runtime_error("GameAssembly is not loaded"); + // Resolve before installing any detours. Keep the image loaded for the process lifetime. +#define DO_API(r, n, p) n = reinterpret_cast(dlsym(assembly, #n)); +#define DO_API_NO_RETURN(r, n, p) DO_API(r, n, p) +#include +#undef DO_API +#undef DO_API_NO_RETURN +#define REQUIRE_API(name) \ + if (!name) \ + throw std::runtime_error("missing IL2CPP export: " #name); + REQUIRE_API(il2cpp_init) + REQUIRE_API(il2cpp_free) + REQUIRE_API(il2cpp_get_corlib) + REQUIRE_API(il2cpp_domain_get) + REQUIRE_API(il2cpp_domain_assembly_open) + REQUIRE_API(il2cpp_assembly_get_image) + REQUIRE_API(il2cpp_class_from_name) + REQUIRE_API(il2cpp_class_from_type) + REQUIRE_API(il2cpp_class_get_type) + REQUIRE_API(il2cpp_class_get_name) + REQUIRE_API(il2cpp_class_get_namespace) + REQUIRE_API(il2cpp_class_get_declaring_type) + REQUIRE_API(il2cpp_class_get_parent) + REQUIRE_API(il2cpp_class_get_methods) + REQUIRE_API(il2cpp_class_get_method_from_name) + REQUIRE_API(il2cpp_class_get_property_from_name) + REQUIRE_API(il2cpp_class_get_field_from_name) + REQUIRE_API(il2cpp_class_get_nested_types) + REQUIRE_API(il2cpp_class_is_enum) + REQUIRE_API(il2cpp_class_is_valuetype) + REQUIRE_API(il2cpp_class_is_assignable_from) + REQUIRE_API(il2cpp_class_value_size) + REQUIRE_API(il2cpp_type_get_name) + REQUIRE_API(il2cpp_type_get_object) + REQUIRE_API(il2cpp_property_get_get_method) + REQUIRE_API(il2cpp_property_get_set_method) + REQUIRE_API(il2cpp_object_get_virtual_method) + REQUIRE_API(il2cpp_object_new) + REQUIRE_API(il2cpp_object_unbox) + REQUIRE_API(il2cpp_runtime_invoke) + REQUIRE_API(il2cpp_format_exception) + REQUIRE_API(il2cpp_runtime_class_init) + REQUIRE_API(il2cpp_field_set_value) + REQUIRE_API(il2cpp_field_static_get_value) + REQUIRE_API(il2cpp_field_static_set_value) + REQUIRE_API(il2cpp_array_new) + REQUIRE_API(il2cpp_gc_wbarrier_set_field) + REQUIRE_API(il2cpp_gchandle_new) + REQUIRE_API(il2cpp_gchandle_new_weakref) + REQUIRE_API(il2cpp_gchandle_get_target) + REQUIRE_API(il2cpp_gchandle_free) + REQUIRE_API(il2cpp_resolve_icall) +#undef REQUIRE_API +#else + (void)assembly; +#endif +} diff --git a/libs/il2cpp/src/il2cpp.cc b/libs/il2cpp/src/il2cpp.cc new file mode 100644 index 000000000..eed975555 --- /dev/null +++ b/libs/il2cpp/src/il2cpp.cc @@ -0,0 +1,410 @@ +// Owns metadata lookup and signature checks for typed bindings, and their diagnostics. + +#include + +#include +#include +#include +#include +#include + +namespace il2cpp +{ +namespace +{ + std::atomic error_handler = nullptr; + + void Report(const std::string& message) + { + if (auto* handler = error_handler.load()) + handler(message); + else + std::fprintf(stderr, "%s\n", message.c_str()); + } + + std::string ClassName(Il2CppClass* klass) + { + if (!klass) + return ""; + if (auto* outer = il2cpp_class_get_declaring_type(klass)) + return ClassName(outer) + "/" + il2cpp_class_get_name(klass); + std::string name = il2cpp_class_get_namespace(klass); + return name.empty() ? il2cpp_class_get_name(klass) : name + "." + il2cpp_class_get_name(klass); + } + + std::string TypeName(const Il2CppType* type) + { + auto* raw = type ? il2cpp_type_get_name(type) : nullptr; + std::string name = raw ? raw : "?"; + if (raw) + il2cpp_free(raw); + return type && type->byref ? "ref " + name : name; + } + + std::string Describe(const MethodInfo* method) + { + std::string text = TypeName(method->return_type) + " " + method->name + "("; + for (uint8_t i = 0; i < method->parameters_count; ++i) + text += (i ? ", " : "") + TypeName(method->parameters[i]); + return text + ")"; + } + + std::string Describe(const detail::Slot& slot) + { + using Kind = detail::Slot::Kind; + std::string text = slot.byref ? "ref " : ""; + if (slot.kind == Kind::kVoid) + return text + "void"; + if (slot.kind == Kind::kValue) + return text + std::to_string(slot.size) + "-byte value"; + auto* expected = slot.klass ? slot.klass() : nullptr; + return text + (expected ? ClassName(expected) : "reference"); + } + + std::string Describe(const detail::Signature& signature) + { + std::string text = Describe(signature.result) + " ("; + for (size_t i = 0; i < signature.params.size(); ++i) + text += (i ? ", " : "") + Describe(signature.params[i]); + return text + ")"; + } + + bool IsIntegral(Il2CppTypeEnum type) + { return (type >= IL2CPP_TYPE_CHAR && type <= IL2CPP_TYPE_U8) || type == IL2CPP_TYPE_I || type == IL2CPP_TYPE_U; } + + // Integers match by size, so bindings may pick signedness or use enums for managed integers. + // Floats, booleans, and structs must agree on kind as well. + bool Matches(const Il2CppType* type, const detail::Slot& slot) + { + using Kind = detail::Slot::Kind; + if (!type) + return false; + if (slot.kind == Kind::kVoid) + return type->type == IL2CPP_TYPE_VOID; + if (type->byref != slot.byref) + return false; + auto* klass = il2cpp_class_from_type(type); + if (!klass) + return false; + if (slot.kind == Kind::kReference) { + // Untyped pointers also stand in for IntPtr and unmanaged pointers. + if (!slot.klass) + return !il2cpp_class_is_valuetype(klass) || type->type == IL2CPP_TYPE_I || type->type == IL2CPP_TYPE_U + || type->type == IL2CPP_TYPE_PTR; + if (il2cpp_class_is_valuetype(klass)) + return false; + // Bindings may name a base or derived class of the declared type. If the binding's own class + // is missing, nothing matches; guessing could pass an object of the wrong type. + auto* expected = slot.klass(); + return expected && il2cpp_class_is_assignable_from(klass, expected) + || il2cpp_class_is_assignable_from(expected, klass); + } + uint32_t alignment = 0; + if (!il2cpp_class_is_valuetype(klass) || static_cast(il2cpp_class_value_size(klass, &alignment)) != slot.size) + return false; + if (il2cpp_class_is_enum(klass) || IsIntegral(type->type)) + return IsIntegral(slot.primitive); + if (slot.primitive == IL2CPP_TYPE_VALUETYPE) + return type->type == IL2CPP_TYPE_VALUETYPE || type->type == IL2CPP_TYPE_GENERICINST; + return type->type == slot.primitive; + } + + bool Matches(const MethodInfo* method, const detail::Signature& signature) + { + if (method->parameters_count != signature.params.size() || !Matches(method->return_type, signature.result)) + return false; + for (size_t i = 0; i < signature.params.size(); ++i) { + if (!Matches(method->parameters[i], signature.params[i])) + return false; + } + return true; + } + + bool IsStatic(const MethodInfo* method) + { return (method->flags & METHOD_ATTRIBUTE_STATIC) != 0; } + + // Generic method definitions have no native code, including those on inflated classes + // (is_generic and is_inflated both set). Generic instance methods have is_generic clear. + bool Callable(const MethodInfo* method) + { return !method->is_generic; } + + // Constructors are not inherited, so they are only looked up on the class itself. + template void ForEachMethod(Il2CppClass* klass, std::string_view name, Visit visit) + { + const bool inherited = name != ".ctor" && name != ".cctor"; + for (; klass; klass = inherited ? il2cpp_class_get_parent(klass) : nullptr) { + void* iter = nullptr; + while (const auto* method = il2cpp_class_get_methods(klass, &iter)) { + if (name == method->name && !visit(method)) + return; + } + } + } + + bool TypeNamed(const Il2CppType* type, std::string_view expected) + { + auto* klass = type ? il2cpp_class_from_type(type) : nullptr; + if (!klass) + return false; + const auto dot = expected.rfind('.'); + if (dot == std::string_view::npos) + return expected == il2cpp_class_get_name(klass); + return expected.substr(0, dot) == il2cpp_class_get_namespace(klass) + && expected.substr(dot + 1) == il2cpp_class_get_name(klass); + } +} // namespace + +void SetErrorHandler(void (*handler)(std::string_view message)) +{ error_handler = handler; } + +namespace detail +{ + Il2CppClass* StringClass() + { + static auto* klass = il2cpp_class_from_name(il2cpp_get_corlib(), "System", "String"); + return klass; + } + + Il2CppClass* TypeClass() + { + static auto* klass = il2cpp_class_from_name(il2cpp_get_corlib(), "System", "Type"); + return klass; + } + + Il2CppClass* FindClass(std::string_view assembly, std::string_view namespaze, std::string_view name) + { + const std::string assembly_name(assembly), namespace_name(namespaze); + auto* domain = il2cpp_domain_get(); + auto* loaded = domain ? il2cpp_domain_assembly_open(domain, assembly_name.c_str()) : nullptr; + auto* image = loaded ? il2cpp_assembly_get_image(loaded) : nullptr; + + // Nested types are reached through their declaring type: "Outer/Inner". + const auto slash = name.find('/'); + const auto outer = std::string(name.substr(0, slash)); + Il2CppClass* klass = image ? il2cpp_class_from_name(image, namespace_name.c_str(), outer.c_str()) : nullptr; + for (auto rest = slash; klass && rest != std::string_view::npos;) { + const auto next = name.find('/', rest + 1); + const auto nested = name.substr(rest + 1, next == std::string_view::npos ? next : next - rest - 1); + void* iter = nullptr; + auto* parent = std::exchange(klass, nullptr); + while (auto* candidate = il2cpp_class_get_nested_types(parent, &iter)) { + if (nested == il2cpp_class_get_name(candidate)) { + klass = candidate; + break; + } + } + rest = next; + } + if (!klass) + Report("[IL2CPP] missing class " + assembly_name + ":" + (namespace_name.empty() ? "" : namespace_name + ".") + + std::string(name)); + return klass; + } + + Method FindMethod(Il2CppClass* klass, std::string_view name, int count) + { + if (!klass) + return {}; + // Abstract methods and generic definitions only have shared stubs, which must never be detoured. + const MethodInfo* found = nullptr; + ForEachMethod(klass, name, [&](const MethodInfo* method) { + if (!Callable(method) || (method->flags & METHOD_ATTRIBUTE_ABSTRACT) + || (count >= 0 && method->parameters_count != count)) + return true; + found = method; + return false; + }); + if (!found) + Report("[IL2CPP] missing method " + ClassName(klass) + "." + std::string(name) + + (count >= 0 ? " with " + std::to_string(count) + " parameters" : "")); + return Method(found); + } + + Method FindMethod(Il2CppClass* klass, std::string_view name, std::initializer_list params) + { + if (!klass) + return {}; + const MethodInfo* found = nullptr; + ForEachMethod(klass, name, [&](const MethodInfo* method) { + if (!Callable(method) || (method->flags & METHOD_ATTRIBUTE_ABSTRACT) + || method->parameters_count != params.size()) + return true; + size_t index = 0; + for (auto param : params) { + if (!TypeNamed(method->parameters[index++], param)) + return true; + } + found = method; + return false; + }); + if (!found) { + std::string list; + for (auto param : params) + list += (list.empty() ? "" : ", ") + std::string(param); + Report("[IL2CPP] missing method " + ClassName(klass) + "." + std::string(name) + "(" + list + ")"); + } + return Method(found); + } + + const MethodInfo* ResolveMethod(Il2CppClass* klass, std::string_view name, const Signature& signature, + bool is_static) + { + if (!klass) + return nullptr; + // The most derived match wins; virtual calls still dispatch on the receiver. + const MethodInfo* found = nullptr; + std::string candidates; + ForEachMethod(klass, name, [&](const MethodInfo* method) { + if (!Callable(method) || IsStatic(method) != is_static) + return true; + if (Matches(method, signature)) { + found = method; + return false; + } + candidates += "\n " + Describe(method); + return true; + }); + if (found) { + if (is_static) + il2cpp_runtime_class_init(found->klass); + return found; + } + const auto member = ClassName(klass) + "." + std::string(name); + if (candidates.empty()) + Report("[IL2CPP] missing " + std::string(is_static ? "static " : "") + "method " + member); + else + Report("[IL2CPP] no overload of " + member + " matches " + Describe(signature) + "; found:" + candidates); + return nullptr; + } + + const MethodInfo* ResolveProperty(Il2CppClass* klass, std::string_view name, const Slot& value, bool setter, + bool is_static) + { + if (!klass) + return nullptr; + const std::string property_name(name); + const auto member = ClassName(klass) + "." + property_name; + const auto* property = il2cpp_class_get_property_from_name(klass, property_name.c_str()); + if (!property) { + Report("[IL2CPP] missing property " + member); + return nullptr; + } + // An override may redeclare only one accessor; the other is inherited from a base property. + const MethodInfo* method = nullptr; + for (; property; property = property->parent && il2cpp_class_get_parent(property->parent) + ? il2cpp_class_get_property_from_name(il2cpp_class_get_parent(property->parent), + property_name.c_str()) + : nullptr) { + method = setter ? il2cpp_property_get_set_method(const_cast(property)) + : il2cpp_property_get_get_method(const_cast(property)); + if (method) + break; + } + if (!method) { + Report("[IL2CPP] property " + member + " has no " + (setter ? "setter" : "getter")); + return nullptr; + } + if (IsStatic(method) != is_static) { + Report("[IL2CPP] property " + member + (is_static ? " is not static" : " is static")); + return nullptr; + } + const Slot void_slot; + const bool matches = setter ? method->parameters_count == 1 && Matches(method->parameters[0], value) + && Matches(method->return_type, void_slot) + : method->parameters_count == 0 && Matches(method->return_type, value); + if (!matches) { + Report("[IL2CPP] property " + member + " is " + TypeName(setter ? method->parameters[0] : method->return_type) + + ", binding expects " + Describe(value)); + return nullptr; + } + if (is_static) + il2cpp_runtime_class_init(method->klass); + return method; + } + + FieldInfo* ResolveField(Il2CppClass* klass, std::string_view name, const Slot& value, bool is_static) + { + if (!klass) + return nullptr; + const std::string field_name(name); + const auto member = ClassName(klass) + "." + field_name; + auto* field = il2cpp_class_get_field_from_name(klass, field_name.c_str()); + if (!field) { + Report("[IL2CPP] missing field " + member); + return nullptr; + } + if (((field->type->attrs & FIELD_ATTRIBUTE_STATIC) != 0) != is_static) { + Report("[IL2CPP] field " + member + (is_static ? " is not static" : " is static")); + return nullptr; + } + if (field->offset == THREAD_STATIC_FIELD_OFFSET) { + Report("[IL2CPP] field " + member + " is thread-static"); + return nullptr; + } + if (!Matches(field->type, value)) { + Report("[IL2CPP] field " + member + " is " + TypeName(field->type) + ", binding expects " + Describe(value)); + return nullptr; + } + if (is_static) + il2cpp_runtime_class_init(field->parent); + return field; + } + + void ReportException(const MethodInfo* method, Il2CppException* exception) + { + // Once per method: a getter that throws every frame would otherwise flood the log. + static std::mutex mutex; + static std::unordered_set reported; + { + std::lock_guard lock(mutex); + if (!reported.insert(method).second) + return; + } + std::string message = "[IL2CPP] " + ClassName(method->klass) + "." + method->name + " threw"; + if (exception) { + char text[512]{}; + il2cpp_format_exception(exception, text, sizeof(text)); + message += std::string(": ") + text; + } + Report(message + " (further exceptions from this method are not logged)"); + } + + Il2CppObject* InvokeBoxed(const MethodInfo* method, void* target, void** args) + { + // The runtime catches managed exceptions here and only hands them back through the slot. + Il2CppException* exception = nullptr; + auto* result = il2cpp_runtime_invoke(method, target, args, &exception); + if (exception) { + ReportException(method, exception); + return nullptr; + } + return result; + } +} // namespace detail + +Il2CppObject* NewDelegate(Il2CppClass* delegate_class, Method target, Il2CppMethodPointer native) +{ + const auto* info = target.Info(); + if (!delegate_class || !info || !IsStatic(info) || !Callable(info)) + return nullptr; + // Delegate(object target, IntPtr method). Runtime invocation rejects static targets for this + // constructor; its generated invoker accepts a null target and a MethodInfo address. + const MethodInfo* ctor = nullptr; + ForEachMethod(delegate_class, ".ctor", [&](const MethodInfo* method) { + if (method->parameters_count == 2 && method->parameters[0]->type == IL2CPP_TYPE_OBJECT + && method->parameters[1]->type == IL2CPP_TYPE_I && method->invoker_method) + ctor = method; + return ctor == nullptr; + }); + if (!ctor) { + Report("[IL2CPP] " + ClassName(delegate_class) + " has no (object, IntPtr) delegate constructor"); + return nullptr; + } + auto* delegate = il2cpp_object_new(delegate_class); + void* args[] = {nullptr, &info}; + ctor->invoker_method(ctor->methodPointer, ctor, delegate, args, nullptr); + if (native) + reinterpret_cast(delegate)->method_ptr = native; + return delegate; +} +} // namespace il2cpp diff --git a/libs/il2cpp/xmake.lua b/libs/il2cpp/xmake.lua new file mode 100644 index 000000000..d333a5c24 --- /dev/null +++ b/libs/il2cpp/xmake.lua @@ -0,0 +1,9 @@ +target("il2cpp") + set_kind("static") + add_files("src/*.cc") + add_includedirs("include", {public = true}) + add_packages("libil2cpp", {public = true}) + set_exceptions("cxx") + if is_plat("windows") then + add_linkdirs(".", {public = true}) + end diff --git a/mods/xmake.lua b/mods/xmake.lua index aee96c5a2..aa6881062 100644 --- a/mods/xmake.lua +++ b/mods/xmake.lua @@ -89,6 +89,7 @@ do add_rules("stfc.cxx.sccache", {override = true}) end add_files("src/**.cc") + add_deps("il2cpp", {public = true}) add_headerfiles("src/**.h") add_includedirs("src", { public = true }) @@ -115,12 +116,12 @@ do -- Platform-specific settings if is_plat("windows") then add_cxflags("/bigobj") - add_linkdirs("src/il2cpp") + add_linkdirs("../libs/il2cpp", {public = true}) add_syslinks("winmm") elseif is_plat("macosx") then add_cxflags("-fms-extensions") -- Add Objective-C++ source - add_files("src/*.mm") + add_files("src/**.mm") -- Link Cocoa framework add_frameworks("Cocoa") end diff --git a/tests/il2cpp_bindings.cc b/tests/il2cpp_bindings.cc new file mode 100644 index 000000000..1fc383e4d --- /dev/null +++ b/tests/il2cpp_bindings.cc @@ -0,0 +1,612 @@ +// Typed bindings against a controlled managed runtime: lookup, signature checks, native calls, +// fields, dispatch, and failure reporting. +#include "il2cpp_test_api.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +struct FakeClass { + // MSVC cannot embed Il2CppClass because its vtable is a zero-length trailing array. + std::unique_ptr metadata = std::make_unique(); + Il2CppClass& klass = *metadata; + Il2CppType byref{}; + std::vector methods; + std::vector fields; + std::vector properties; + std::vector nested; + int32_t value_size = 0; + bool is_enum = false; +}; + +std::deque classes; +std::deque methods; +std::deque> parameter_lists; +std::deque field_types; +std::unordered_map registry; +std::vector errors; +std::vector initialized; +int method_lookups = 0; + +FakeClass& Define(const char* namespaze, const char* name, Il2CppTypeEnum type, FakeClass* parent = nullptr, + int32_t value_size = 0) +{ + auto& fake = classes.emplace_back(); + fake.klass.name = name; + fake.klass.namespaze = namespaze; + fake.klass.parent = parent ? &parent->klass : nullptr; + fake.klass.byval_arg.type = type; + fake.klass.byval_arg.data.dummy = &fake.klass; + fake.klass.byval_arg.valuetype = value_size > 0; + fake.byref = fake.klass.byval_arg; + fake.byref.byref = 1; + fake.value_size = value_size; + registry[&fake.klass] = &fake; + return fake; +} + +const Il2CppType* T(FakeClass& fake) +{ return &fake.klass.byval_arg; } +const Il2CppType* Ref(FakeClass& fake) +{ return &fake.byref; } + +MethodInfo& AddMethod(FakeClass& owner, const char* name, void* native, const Il2CppType* result, + std::vector params, uint16_t flags = METHOD_ATTRIBUTE_PUBLIC) +{ + auto& list = parameter_lists.emplace_back(std::move(params)); + auto& method = methods.emplace_back(); + method.name = name; + method.methodPointer = reinterpret_cast(native); + method.virtualMethodPointer = method.methodPointer; + method.klass = &owner.klass; + method.return_type = result; + method.parameters = list.data(); + method.parameters_count = static_cast(list.size()); + method.flags = flags; + owner.methods.push_back(&method); + owner.klass.methods = owner.methods.data(); + owner.klass.method_count = static_cast(owner.methods.size()); + return method; +} + +void AddField(FakeClass& owner, const char* name, FakeClass& type, int32_t offset, bool is_static = false) +{ + auto& field_type = field_types.emplace_back(type.klass.byval_arg); + if (is_static) + field_type.attrs |= FIELD_ATTRIBUTE_STATIC; + owner.fields.push_back({name, &field_type, &owner.klass, offset, 0}); +} + +void AddProperty(FakeClass& owner, const char* name, const MethodInfo* get, const MethodInfo* set = nullptr) +{ owner.properties.push_back({&owner.klass, name, get, set, 0, 0}); } + +FakeClass& Fake(const Il2CppClass* klass) +{ return *registry.at(klass); } + +// Managed object storage for tests. Offsets mirror a real object: header, then fields. +struct TestObject { + Il2CppObject header; + int32_t count; + Il2CppObject* target; +}; + +struct Vec3 { + float x, y, z; +}; + +// Runtime classes. +FakeClass& void_class = Define("System", "Void", IL2CPP_TYPE_VOID); +FakeClass& object_class = Define("System", "Object", IL2CPP_TYPE_OBJECT); +FakeClass& int_class = Define("System", "Int32", IL2CPP_TYPE_I4, nullptr, 4); +FakeClass& bool_class = Define("System", "Boolean", IL2CPP_TYPE_BOOLEAN, nullptr, 1); +FakeClass& float_class = Define("System", "Single", IL2CPP_TYPE_R4, nullptr, 4); +FakeClass& string_class = Define("System", "String", IL2CPP_TYPE_STRING, &object_class); +FakeClass& type_class = Define("System", "Type", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& vec3_class = Define("UnityEngine", "Vector3", IL2CPP_TYPE_VALUETYPE, nullptr, sizeof(Vec3)); +FakeClass& state_class = Define("Game", "State", IL2CPP_TYPE_VALUETYPE, nullptr, 4); +FakeClass& base_class = Define("Game", "Base", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& derived_class = Define("Game", "Derived", IL2CPP_TYPE_CLASS, &base_class); +FakeClass& other_class = Define("Game", "Other", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& outer_class = Define("Game", "Outer", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& inner_class = Define("", "Inner", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& list_a_class = Define("Game", "ListA", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& list_b_class = Define("Game", "ListB", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& delegate_base = Define("System", "Delegate", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& action_class = Define("System", "Action", IL2CPP_TYPE_CLASS, &delegate_base); +FakeClass& intptr_class = Define("System", "IntPtr", IL2CPP_TYPE_I, nullptr, sizeof(void*)); + +TestObject base_object{}, derived_object{}, other_object{}, list_a{}, list_b{}, allocated{}; +Il2CppObject string_object{}, type_object{}; +Il2CppDelegate delegate_object{}; + +// Generated code stand-ins. Each records what the binding passed. +const Il2CppObject* seen_self = nullptr; +const MethodInfo* seen_method = nullptr; +int32_t seen_int = 0; +bool seen_bool = false; +std::string seen_overload; +int native_calls = 0; +int32_t static_flag = 0; + +int32_t Base_get_Count(Il2CppObject* self, const MethodInfo* method) +{ + seen_self = self; + seen_method = method; + ++native_calls; + return reinterpret_cast(self)->count; +} +void Base_set_Count(Il2CppObject* self, int32_t value, const MethodInfo*) +{ reinterpret_cast(self)->count = value; } +void Base_Select_Int(Il2CppObject*, int32_t value, const MethodInfo*) +{ + seen_overload = "int"; + seen_int = value; +} +void Base_Select_Bool(Il2CppObject*, bool value, const MethodInfo*) +{ + seen_overload = "bool"; + seen_bool = value; +} +bool Base_Find_String(Il2CppObject*, Il2CppString*, const MethodInfo*) +{ + seen_overload = "string"; + return true; +} +bool Base_Find_Type(Il2CppObject*, Il2CppReflectionType*, const MethodInfo*) +{ + seen_overload = "type"; + return true; +} +bool Base_TryGet(Il2CppObject*, int32_t key, Il2CppObject** result, const MethodInfo*) +{ + *result = key == 1 ? &other_object.header : nullptr; + return *result != nullptr; +} +Vec3 Base_get_Position(Il2CppObject*, const MethodInfo*) +{ return {1.f, 2.f, 3.f}; } +int32_t Base_get_State(Il2CppObject*, const MethodInfo*) +{ return 2; } +const char* Base_Describe(Il2CppObject*, const MethodInfo*) +{ return "base"; } +const char* Derived_Describe(Il2CppObject*, const MethodInfo*) +{ return "derived"; } +int32_t Base_Twice(int32_t value, const MethodInfo*) +{ return value * 2; } +Il2CppObject* Base_get_Instance(const MethodInfo*) +{ return &base_object.header; } +void Base_ctor(Il2CppObject* self, int32_t count, const MethodInfo*) +{ reinterpret_cast(self)->count = count; } +int32_t ListA_get_Count(Il2CppObject*, const MethodInfo*) +{ return 2; } +int32_t ListB_get_Count(Il2CppObject*, const MethodInfo*) +{ return 3; } +Il2CppObject* List_get_Item(Il2CppObject* self, int32_t index, const MethodInfo*) +{ return index == 0 ? self : &other_object.header; } +Il2CppException thrown{}; +int32_t Base_Explode(Il2CppObject*, const MethodInfo*) +{ throw Il2CppExceptionWrapper(&thrown); } +void Never(Il2CppObject*, const MethodInfo*) +{ throw std::runtime_error("shared generic method was called natively"); } + +const MethodInfo* delegate_target = nullptr; +void DelegateInvoker(Il2CppMethodPointer, const MethodInfo*, void* self, void** args, void*) +{ + // Delegate(object target, IntPtr method): the second argument points at the MethodInfo address. + auto* delegate = static_cast(self); + delegate_target = *static_cast(args[1]); + delegate->method = delegate_target; +} +void NativeTarget() +{ +} + +MethodInfo* base_describe = nullptr; +MethodInfo* shared_method = nullptr; +int32_t shared_result = 41; +int boxed_invocations = 0; + +void BuildRuntime() +{ + auto& get_count = AddMethod(base_class, "get_Count", reinterpret_cast(&Base_get_Count), T(int_class), {}); + auto& set_count = AddMethod(base_class, "set_Count", reinterpret_cast(&Base_set_Count), T(void_class), + {T(int_class)}); + AddProperty(base_class, "Count", &get_count, &set_count); + AddMethod(base_class, "Select", reinterpret_cast(&Base_Select_Int), T(void_class), {T(int_class)}); + AddMethod(base_class, "Select", reinterpret_cast(&Base_Select_Bool), T(void_class), {T(bool_class)}); + AddMethod(base_class, "Find", reinterpret_cast(&Base_Find_String), T(bool_class), {T(string_class)}); + AddMethod(base_class, "Find", reinterpret_cast(&Base_Find_Type), T(bool_class), {T(type_class)}); + AddMethod(base_class, "TryGet", reinterpret_cast(&Base_TryGet), T(bool_class), + {T(int_class), Ref(object_class)}); + auto& get_position = + AddMethod(base_class, "get_Position", reinterpret_cast(&Base_get_Position), T(vec3_class), {}); + AddProperty(base_class, "Position", &get_position); + auto& get_state = AddMethod(base_class, "get_State", reinterpret_cast(&Base_get_State), T(state_class), {}); + AddProperty(base_class, "State", &get_state); + base_describe = &AddMethod(base_class, "Describe", reinterpret_cast(&Base_Describe), T(object_class), {}, + METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_VIRTUAL); + AddMethod(derived_class, "Describe", reinterpret_cast(&Derived_Describe), T(object_class), {}, + METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_VIRTUAL); + AddMethod(base_class, "Twice", reinterpret_cast(&Base_Twice), T(int_class), {T(int_class)}, + METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_STATIC); + auto& get_instance = AddMethod(base_class, "get_Instance", reinterpret_cast(&Base_get_Instance), + T(base_class), {}, METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_STATIC); + AddProperty(base_class, "Instance", &get_instance); + AddMethod(base_class, ".ctor", reinterpret_cast(&Base_ctor), T(void_class), {T(int_class)}); + AddMethod(base_class, "Explode", reinterpret_cast(&Base_Explode), T(int_class), {}); + shared_method = &AddMethod(base_class, "Shared", reinterpret_cast(&Never), T(int_class), {}); + shared_method->has_full_generic_sharing_signature = 1; + AddField(base_class, "_count", int_class, offsetof(TestObject, count)); + AddField(base_class, "_target", base_class, offsetof(TestObject, target)); + AddField(base_class, "s_flag", int_class, 0, true); + + AddMethod(list_a_class, "get_Count", reinterpret_cast(&ListA_get_Count), T(int_class), {}); + AddMethod(list_a_class, "get_Item", reinterpret_cast(&List_get_Item), T(object_class), {T(int_class)}); + AddMethod(list_b_class, "get_Count", reinterpret_cast(&ListB_get_Count), T(int_class), {}); + AddMethod(list_b_class, "get_Item", reinterpret_cast(&List_get_Item), T(object_class), {T(int_class)}); + + auto& ctor = AddMethod(action_class, ".ctor", nullptr, T(void_class), {T(object_class), T(intptr_class)}); + ctor.invoker_method = &DelegateInvoker; + AddMethod(base_class, "OnEvent", reinterpret_cast(&NativeTarget), T(void_class), {}, + METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_STATIC); + + inner_class.klass.declaringType = &outer_class.klass; + outer_class.nested.push_back(&inner_class.klass); + + base_object.header.klass = &base_class.klass; + base_object.count = 7; + derived_object.header.klass = &derived_class.klass; + other_object.header.klass = &other_class.klass; + list_a.header.klass = &list_a_class.klass; + list_b.header.klass = &list_b_class.klass; + string_object.klass = &string_class.klass; + type_object.klass = &type_class.klass; +} +} // namespace + +API(Il2CppDomain*, il2cpp_domain_get, ()) +{ return reinterpret_cast(1); } +END_API +API(const Il2CppAssembly*, il2cpp_domain_assembly_open, (Il2CppDomain*, const char*)) +{ return reinterpret_cast(1); } +END_API +API(const Il2CppImage*, il2cpp_assembly_get_image, (const Il2CppAssembly*)) +{ return reinterpret_cast(1); } +END_API +API(const Il2CppImage*, il2cpp_get_corlib, ()) +{ return reinterpret_cast(1); } +END_API +API(Il2CppClass*, il2cpp_class_from_name, (const Il2CppImage*, const char* namespaze, const char* name)) +{ + for (auto& fake : classes) { + if (!std::strcmp(fake.klass.namespaze, namespaze) && !std::strcmp(fake.klass.name, name)) + return &fake.klass; + } + return nullptr; +} +END_API +API(const char*, il2cpp_class_get_name, (Il2CppClass * klass)) +{ return klass->name; } +END_API +API(const char*, il2cpp_class_get_namespace, (Il2CppClass * klass)) +{ return klass->namespaze; } +END_API +API(Il2CppClass*, il2cpp_class_get_declaring_type, (Il2CppClass * klass)) +{ return klass->declaringType; } +END_API +API(Il2CppClass*, il2cpp_class_get_parent, (Il2CppClass * klass)) +{ return klass->parent; } +END_API +API(Il2CppClass*, il2cpp_class_get_nested_types, (Il2CppClass * klass, void** iter)) +{ + auto& nested = Fake(klass).nested; + auto index = reinterpret_cast(*iter); + if (index >= nested.size()) + return nullptr; + *iter = reinterpret_cast(index + 1); + return nested[index]; +} +END_API +API(const MethodInfo*, il2cpp_class_get_methods, (Il2CppClass * klass, void** iter)) +{ + auto index = reinterpret_cast(*iter); + if (index >= klass->method_count) + return nullptr; + *iter = reinterpret_cast(index + 1); + return klass->methods[index]; +} +END_API +API(const MethodInfo*, il2cpp_class_get_method_from_name, (Il2CppClass * klass, const char* name, int count)) +{ + ++method_lookups; + for (; klass; klass = klass->parent) { + for (uint16_t i = 0; i < klass->method_count; ++i) { + auto* method = klass->methods[i]; + if (!std::strcmp(method->name, name) && (count < 0 || method->parameters_count == count)) + return method; + } + } + return nullptr; +} +END_API +API(const PropertyInfo*, il2cpp_class_get_property_from_name, (Il2CppClass * klass, const char* name)) +{ + for (; klass; klass = klass->parent) { + for (auto& property : Fake(klass).properties) { + if (!std::strcmp(property.name, name)) + return &property; + } + } + return nullptr; +} +END_API +API(const MethodInfo*, il2cpp_property_get_get_method, (PropertyInfo * property)) +{ return property->get; } +END_API +API(const MethodInfo*, il2cpp_property_get_set_method, (PropertyInfo * property)) +{ return property->set; } +END_API +API(FieldInfo*, il2cpp_class_get_field_from_name, (Il2CppClass * klass, const char* name)) +{ + for (; klass; klass = klass->parent) { + for (auto& field : Fake(klass).fields) { + if (!std::strcmp(field.name, name)) + return &field; + } + } + return nullptr; +} +END_API +API(Il2CppClass*, il2cpp_class_from_type, (const Il2CppType* type)) +{ return static_cast(type->data.dummy); } +END_API +API(const Il2CppType*, il2cpp_class_get_type, (Il2CppClass * klass)) +{ return &klass->byval_arg; } +END_API +API(Il2CppObject*, il2cpp_type_get_object, (const Il2CppType*)) +{ return &type_object; } +END_API +API(char*, il2cpp_type_get_name, (const Il2CppType* type)) +{ return strdup(static_cast(type->data.dummy)->name); } +END_API +API(void, il2cpp_free, (void* pointer)) +{ std::free(pointer); } +END_API +API(bool, il2cpp_class_is_valuetype, (const Il2CppClass* klass)) +{ return klass->byval_arg.valuetype; } +END_API +API(bool, il2cpp_class_is_enum, (const Il2CppClass* klass)) +{ return klass == &state_class.klass; } +END_API +API(int32_t, il2cpp_class_value_size, (Il2CppClass * klass, uint32_t*)) +{ return Fake(klass).value_size; } +END_API +API(bool, il2cpp_class_is_assignable_from, (Il2CppClass * klass, Il2CppClass* other)) +{ + for (; other; other = other->parent) { + if (other == klass) + return true; + } + return false; +} +END_API +API(void, il2cpp_runtime_class_init, (Il2CppClass * klass)) +{ initialized.push_back(klass); } +END_API +API(const MethodInfo*, il2cpp_object_get_virtual_method, (Il2CppObject * object, const MethodInfo* method)) +{ + for (auto* klass = object->klass; klass; klass = klass->parent) { + for (uint16_t i = 0; i < klass->method_count; ++i) { + if (!std::strcmp(klass->methods[i]->name, method->name)) + return klass->methods[i]; + } + } + return nullptr; +} +END_API +API(void*, il2cpp_object_unbox, (Il2CppObject*)) +{ return &shared_result; } +END_API +API(Il2CppObject*, il2cpp_runtime_invoke, (const MethodInfo* method, void*, void**, Il2CppException** exception)) +{ + if (method != shared_method || !exception) { + std::fputs("unexpected boxed invocation\n", stderr); + std::abort(); + } + ++boxed_invocations; + return &string_object; +} +END_API +API(void, il2cpp_format_exception, (const Il2CppException*, char* message, int size)) +{ std::snprintf(message, size, "NullReferenceException"); } +END_API +API(Il2CppObject*, il2cpp_object_new, (const Il2CppClass* klass)) +{ + if (klass == &action_class.klass) { + delegate_object.object.klass = const_cast(klass); + return &delegate_object.object; + } + allocated = {}; + allocated.header.klass = const_cast(klass); + return &allocated.header; +} +END_API +API(void, il2cpp_field_set_value, (Il2CppObject * object, FieldInfo* field, void* value)) +{ + // Like Field::SetValueRaw: value types are copied from the pointer, references are the pointer. + auto size = Fake(static_cast(field->type->data.dummy)).value_size; + auto* dest = reinterpret_cast(object) + field->offset; + if (size) + std::memcpy(dest, value, size); + else + std::memcpy(dest, &value, sizeof(void*)); +} +END_API +API(void, il2cpp_field_static_get_value, (FieldInfo*, void* value)) +{ std::memcpy(value, &static_flag, sizeof(static_flag)); } +END_API +API(void, il2cpp_field_static_set_value, (FieldInfo*, void* value)) +{ std::memcpy(&static_flag, value, sizeof(static_flag)); } +END_API + +namespace +{ +struct Base : il2cpp::Object { + using Class = il2cpp::Class<"Game", "Game", "Base">; + + int32_t Count() + { return Get<"Count", int32_t>(); } + void SetCount(int32_t value) + { Set<"Count">(value); } + int32_t count() + { return Field<"_count", int32_t>(); } + void SetCount_(int32_t value) + { SetField<"_count">(value); } + Base* target() + { return Field<"_target", Base*>(); } + void SetTarget(Base* value) + { SetField<"_target">(value); } + Vec3 Position() + { return Get<"Position", Vec3>(); } + const char* Describe() + { return Call<"Describe", const char*>(); } +}; +struct Derived : Base { + using Class = il2cpp::Class<"Game", "Game", "Derived">; +}; +struct Other : il2cpp::Object { + using Class = il2cpp::Class<"Game", "Game", "Other">; +}; +enum class State : int32_t { kIdle = 0, kBusy = 2 }; + +void Require(bool condition, const char* what) +{ + if (!condition) + throw std::runtime_error(what); +} + +size_t ErrorsMentioning(std::string_view text) +{ + size_t count = 0; + for (auto& error : errors) + count += error.find(text) != std::string::npos; + return count; +} +} // namespace + +int main() +{ + BuildRuntime(); + il2cpp::SetErrorHandler([](std::string_view message) { errors.emplace_back(message); }); + auto* base = reinterpret_cast(&base_object); + + // Properties call the generated getter with the receiver and its MethodInfo. + Require(base->Count() == 7 && seen_self == &base_object.header, "property getter receiver"); + Require(seen_method && !std::strcmp(seen_method->name, "get_Count"), "trailing MethodInfo"); + base->SetCount(9); + Require(base->Count() == 9, "property setter"); + Require(base->Position().y == 2.f, "struct return"); + Require(base->Get<"State", State>() == State::kBusy, "enum property"); + + // Fields read at their offset and write through the runtime. + Require(base->count() == 9, "field read"); + base->SetCount_(4); + Require(base_object.count == 4, "field write"); + base->SetTarget(base); + Require(base->target() == base, "reference field"); + Base::Class::SetField<"s_flag">(int32_t{5}); + Require(Base::Class::Field<"s_flag", int32_t>() == 5 && static_flag == 5, "static field"); + + // Overloads are chosen by C++ argument types. + base->Call<"Select">(int32_t{3}); + Require(seen_overload == "int" && seen_int == 3, "int overload"); + base->Call<"Select">(true); + Require(seen_overload == "bool" && seen_bool, "bool overload"); + Require(base->Call<"Find", bool>(reinterpret_cast(&string_object)) && seen_overload == "string", + "string overload"); + Require(base->Call<"Find", bool>(Other::Class::TypeObject()) && seen_overload == "type", "type overload"); + + // ref/out arguments. + Il2CppObject* found = nullptr; + Require(base->Call<"TryGet", bool>(int32_t{1}, std::ref(found)) && found == &other_object.header, "out argument"); + + // Virtual calls dispatch on the receiver; non-virtual calls do not. + auto* derived = reinterpret_cast(&derived_object); + Require(!std::strcmp(base->Describe(), "base"), "base dispatch"); + Require(!std::strcmp(static_cast(derived)->Describe(), "derived"), "virtual dispatch"); + + // Static members initialize their class. + Require(Base::Class::Call<"Twice", int32_t>(int32_t{21}) == 42, "static call"); + Require(Base::Class::Get<"Instance", Base*>() == base, "static property"); + Require(std::find(initialized.begin(), initialized.end(), &base_class.klass) != initialized.end(), "class init"); + + // Construction runs the matching constructor. + auto* made = il2cpp::New(int32_t{12}); + Require(made && made->klass == &base_class.klass && made->count() == 12, "construction"); + + // Constructors are not inherited; a missing one is reported instead of running a base constructor. + Require(il2cpp::New(int32_t{1}) == nullptr && ErrorsMentioning("Game.Derived..ctor"), "ctor not inherited"); + + // Lookups happen once per binding, not per call. + const int lookups = method_lookups; + for (int i = 0; i < 3; ++i) + base->Call<"Select">(int32_t{i}); + Require(method_lookups == lookups, "cached method lookup"); + + // Missing and drifted members are reported once and return defaults without calling native code. + const int calls = native_calls; + Require(base->Get<"Missing", int32_t>() == 0 && base->Get<"Missing", int32_t>() == 0, "missing default"); + Require(ErrorsMentioning("missing property Game.Base.Missing") == 1, "missing reported once"); + base->Call<"Select">(1.5f); + Require(ErrorsMentioning("no overload of Game.Base.Select") == 1, "drift reported"); + Require(base->Get<"Count", bool>() == false && ErrorsMentioning("property Game.Base.Count is Int32") == 1, + "property type drift"); + Require(base->Field<"_count", Other*>() == nullptr && ErrorsMentioning("field Game.Base._count") == 1, + "field type drift"); + Require(base->Call<"Find", bool>(reinterpret_cast(&other_object)) == false, "unrelated reference rejected"); + Require(native_calls == calls, "rejected members do not call native code"); + Require(il2cpp::Class<"Game", "Game", "Nope">::Raw() == nullptr && ErrorsMentioning("missing class Game:Game.Nope"), + "missing class"); + + // Managed exceptions are contained, reported once per method, and return the default. + Require(base->Call<"Explode", int32_t>() == 0 && base->Call<"Explode", int32_t>() == 0, "exception contained"); + Require(ErrorsMentioning("Game.Base.Explode threw: NullReferenceException") == 1, "exception reported once"); + + // Nested classes resolve through their declaring type. + Require(il2cpp::Class<"Game", "Game", "Outer/Inner">::Raw() == &inner_class.klass, "nested class"); + + // Unbound wrappers resolve against each runtime class. + auto* first = reinterpret_cast*>(&list_a); + auto* second = reinterpret_cast*>(&list_b); + Require(first->Count() == 2 && second->Count() == 3 && first->Count() == 2, "per-class lookup"); + int items = 0; + for (auto* item : *second) + items += item != nullptr; + Require(items == 3 && (*first)[0] == &list_a.header, "list iteration"); + + // Fully shared generic code is marshalled by the runtime. + Require(base->Call<"Shared", int32_t>() == 41 && boxed_invocations == 1, "shared generic fallback"); + + // Delegates bind static methods and can jump to native code. + auto target = Base::Class::Method("OnEvent"); + auto* delegate = reinterpret_cast( + il2cpp::NewDelegate(&action_class.klass, target, reinterpret_cast(&NativeTarget))); + Require(delegate && delegate_target == target.Info() + && delegate->method_ptr == reinterpret_cast(&NativeTarget), + "static delegate"); + + // Hook lookups by count and by parameter type names. + Require(Base::Class::Method("Select", {"System.Boolean"}).Pointer() == reinterpret_cast(&Base_Select_Bool), + "hook lookup by parameter types"); + Require(!Base::Class::Method("Absent", 0) && ErrorsMentioning("missing method Game.Base.Absent"), "missing hook"); + + std::cout << "IL2CPP binding tests passed\n"; +} diff --git a/tests/il2cpp_gc.cc b/tests/il2cpp_gc.cc new file mode 100644 index 000000000..3b8780901 --- /dev/null +++ b/tests/il2cpp_gc.cc @@ -0,0 +1,78 @@ +#include "il2cpp_test_api.h" + +#include +#include +#include + +namespace +{ +Il2CppObject object{}, other{}; +std::unordered_map targets; +uintptr_t next_handle = 1; +bool fail_allocation = false; +int frees = 0; +bool weak = false; +} // namespace +API(Il2CppGCHandle, il2cpp_gchandle_new, (Il2CppObject * object, bool pinned)) +{ + if (!object || pinned) + throw std::runtime_error("invalid root"); + if (fail_allocation) + return nullptr; + auto handle = reinterpret_cast(next_handle++); + targets[handle] = object; + return handle; +} +END_API +API(Il2CppGCHandle, il2cpp_gchandle_new_weakref, (Il2CppObject * object, bool resurrect)) +{ + if (resurrect) + throw std::runtime_error("unexpected resurrection"); + weak = true; + return il2cpp_gchandle_new(object, false); +} +END_API +API(Il2CppObject*, il2cpp_gchandle_get_target, (Il2CppGCHandle handle)) +{ return targets.at(handle); } +END_API +API(void, il2cpp_gchandle_free, (Il2CppGCHandle handle)) +{ + if (targets.erase(handle) != 1) + throw std::runtime_error("handle freed twice"); + ++frees; +} +END_API +void Require(bool condition) +{ + if (!condition) + throw std::runtime_error("GC ownership regression"); +} +int main() +{ + Require(!il2cpp::GcHandle::Strong(nullptr)); + Require(!il2cpp::GcHandle::Weak(nullptr)); + fail_allocation = true; + Require(!il2cpp::GcHandle::Strong(&object) && targets.empty()); + fail_allocation = false; + { + auto root = il2cpp::GcHandle::Strong(&object); + Require(root.Get() == &object); + auto moved = std::move(root); + Require(!root && !root.Get() && moved.Get() == &object); + root = il2cpp::GcHandle::Strong(&other); + moved = std::move(root); + Require(frees == 1 && !root && moved.Get() == &other); + auto& same = moved; + moved = std::move(same); + Require(moved.Get() == &other && frees == 1); + moved.Reset(); + moved.Reset(); + Require(!moved && frees == 2); + root = il2cpp::GcHandle::Weak(&object); + Require(weak && root.Get() == &object); + targets.begin()->second = nullptr; // Managed collection clears a weak target. + Require(root && !root.Get()); + } + Require(frees == 3 && targets.empty()); + std::cout << "IL2CPP GC ownership tests passed\n"; +} diff --git a/tests/il2cpp_test_api.h b/tests/il2cpp_test_api.h new file mode 100644 index 000000000..fc51a2e18 --- /dev/null +++ b/tests/il2cpp_test_api.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#if _WIN32 +#undef IL2CPP_IMPORT +#define IL2CPP_IMPORT +#endif +#include + +// The library uses Windows imports. Supply both the function and its IAT slot +// so tests exercise the production archive without loading GameAssembly.dll. +#if _WIN32 +#define API(ret, name, params) \ + extern "C" ret name params; \ + extern "C" { \ + ret(*__imp_##name) params = name; \ + } \ + extern "C" ret name params +#define END_API +#else +#define API(ret, name, params) name##_t name = +[] params->ret +#define END_API ; +#endif diff --git a/xmake.lua b/xmake.lua index d75279c69..dc452fa2e 100644 --- a/xmake.lua +++ b/xmake.lua @@ -25,42 +25,24 @@ add_rules("mode.releasedbg") includes("xmake/rules/protobuf_sccache.lua") includes("xmake/rules/cxx_sccache.lua") +includes("libs/il2cpp") includes("mods") -target("il2cpp-checked-tests") - set_kind("binary") - set_default(false) - add_files("tests/il2cpp_checked.cc") - add_includedirs("mods/src") - add_packages("libil2cpp", "eastl") - set_exceptions("cxx") - if is_plat("windows") then - add_linkdirs("mods/src/il2cpp") - end -target("il2cpp-runtime-tests") +target("il2cpp-bindings-tests") set_kind("binary") set_default(false) - add_files("tests/il2cpp_runtime.cc") - add_includedirs("mods/src") - add_packages("libil2cpp", "eastl") set_exceptions("cxx") - if is_plat("windows") then - add_linkdirs("mods/src/il2cpp") - end + add_files("tests/il2cpp_bindings.cc") + add_deps("il2cpp") -target("il2cpp-class-lookup-tests") +target("il2cpp-gc-tests") set_kind("binary") set_default(false) - add_files("tests/il2cpp_class_lookup.cc") - add_includedirs("mods/src") - add_packages("libil2cpp", "eastl") set_exceptions("cxx") - if is_plat("windows") then - add_linkdirs("mods/src/il2cpp") - end + add_files("tests/il2cpp_gc.cc") + add_deps("il2cpp") --- Native regression fixture for the x64 trampoline used by the object tracker. if is_arch("x64", "x86_64") then target("spud-relocation-tests") set_kind("binary") From e2c0a3db553bcf0c342a5b056e80096ad656f440 Mon Sep 17 00:00:00 2001 From: tashcan Date: Thu, 15 Oct 2026 16:40:40 -0700 Subject: [PATCH 2/2] Move game callers onto typed bindings Replace repeated metadata searches, field offsets, invocation wrappers and manual GC cleanup with the binding library. The migration also corrects the ActionType value layout and the 64-bit profile and hull identifiers. A checker scans binding declarations and hook targets against dumped client metadata, so removed members and signature drift surface before a release. --- {mods/src => libs}/il2cpp/GameAssembly.lib | Bin libs/il2cpp/include/il2cpp/il2cpp.h | 120 ++- libs/il2cpp/src/il2cpp.cc | 17 +- mods/src/errormsg.h | 15 - mods/src/il2cpp/il2cpp-functions.cc | 40 - mods/src/il2cpp/il2cpp_checked.h | 70 -- mods/src/log_history.h | 20 - mods/src/patches/alliance_store_bindings.h | 416 ----------- mods/src/patches/battle_notify_parser.cc | 160 ++-- mods/src/patches/fleet_watch.cc | 12 +- mods/src/patches/key.cc | 35 +- mods/src/patches/notification_service.cc | 246 ++---- mods/src/patches/notification_service.h | 2 +- mods/src/patches/object_tracker.h | 29 + mods/src/patches/parts/alliance_store.cc | 650 +++++++++++----- mods/src/patches/parts/artifact_exchange.cc | 63 +- mods/src/patches/parts/audio_events.cc | 98 +-- mods/src/patches/parts/buff_fixes.cc | 18 +- mods/src/patches/parts/cargo_format.cc | 37 +- mods/src/patches/parts/chat.cc | 132 ++-- mods/src/patches/parts/chat_titles.cc | 14 +- .../patches/parts/daily_faction_bulk_claim.cc | 351 ++------- mods/src/patches/parts/disable_banners.cc | 26 +- .../patches/parts/double_click_assign_ship.cc | 14 +- mods/src/patches/parts/fix_pan.cc | 110 ++- mods/src/patches/parts/focus_search.cc | 29 +- .../parts/forbidden_tech_confirmation.cc | 83 +- .../patches/parts/forbidden_tech_ship_name.cc | 400 +++++----- mods/src/patches/parts/free_resize.cc | 37 +- .../patches/parts/galactic_anomaly_timer.cc | 588 ++++++--------- mods/src/patches/parts/haven_history.cc | 2 +- mods/src/patches/parts/hotkeys.cc | 707 +++++++----------- mods/src/patches/parts/instant_cargo.cc | 109 +-- .../parts/instant_warp_confirmation.cc | 50 +- mods/src/patches/parts/interior_zoom.cc | 6 +- mods/src/patches/parts/loading_screen.cc | 124 ++- .../src/patches/parts/loading_screen_common.h | 473 ++++-------- mods/src/patches/parts/loading_tip.cc | 82 +- mods/src/patches/parts/misc.cc | 264 ++----- mods/src/patches/parts/mission_hud_tweaks.cc | 218 +----- mods/src/patches/parts/object_tracker.cc | 62 +- mods/src/patches/parts/officer_sort.cc | 514 ++++--------- .../patches/parts/open_bulk_claim_gifts.cc | 149 +--- mods/src/patches/parts/pinned_ship_click.cc | 11 +- mods/src/patches/parts/pinned_ship_sort.cc | 282 +------ mods/src/patches/parts/reveal_confirmation.cc | 4 +- mods/src/patches/parts/ship_tile_click.cc | 17 +- mods/src/patches/parts/sync.cc | 297 +++----- mods/src/patches/parts/testing.cc | 169 +---- mods/src/patches/parts/transition_screen.cc | 405 ++++------ mods/src/patches/parts/ui_scale.cc | 109 +-- mods/src/patches/parts/warp_action_label.cc | 85 +-- mods/src/patches/parts/zoom.cc | 366 ++------- mods/src/patches/patches.cc | 25 +- mods/src/prime/ActionData.h | 78 +- mods/src/prime/ActionQueueManager.h | 105 +-- mods/src/prime/ActionRequirement.h | 31 +- mods/src/prime/ActivatedAbilityManager.h | 43 +- .../AllianceStarbaseObjectViewerWidget.h | 19 +- .../AnimatedRewardsScreenViewController.h | 55 +- mods/src/prime/ArmadaObjectViewerWidget.h | 78 +- .../prime/ArtifactHallDetailsViewController.h | 55 +- mods/src/prime/AspectRatioConstraintHandler.h | 17 +- mods/src/prime/AssignShipsWidget.h | 51 +- mods/src/prime/BattleResultHeader.h | 102 +-- mods/src/prime/BattleTargetData.h | 22 +- mods/src/prime/BlurController.h | 54 +- mods/src/prime/BookmarksManager.h | 93 +-- mods/src/prime/Bundle.h | 6 +- mods/src/prime/BundleDataWidget.h | 81 +- mods/src/prime/Button.h | 20 +- mods/src/prime/ButtonAndContextWrapper.h | 21 +- mods/src/prime/CallbackContainer.h | 90 --- mods/src/prime/Camera.h | 80 +- mods/src/prime/CanRepairRequirement.h | 18 +- mods/src/prime/Canvas.h | 29 +- mods/src/prime/CanvasController.h | 102 +-- mods/src/prime/CanvasScaler.h | 59 +- mods/src/prime/CelestialObjectViewerWidget.h | 19 +- mods/src/prime/ChatManager.h | 104 +-- .../ChatMessageListLocalViewController.h | 61 +- mods/src/prime/ChatMessageWidget.h | 23 +- mods/src/prime/ChatPreviewController.h | 88 +-- mods/src/prime/ClientModifierType.h | 4 +- mods/src/prime/Component.h | 16 + mods/src/prime/CourseData.h | 22 +- mods/src/prime/CoursePromptPopupWidget.h | 86 +-- mods/src/prime/DeploymentManager.h | 107 +-- .../src/prime/ElementSelectorViewController.h | 52 +- mods/src/prime/EmbassyObjectViewer.h | 19 +- mods/src/prime/EntityGroup.h | 72 +- mods/src/prime/EventSystem.h | 52 +- mods/src/prime/FleetBarViewController.h | 100 +-- mods/src/prime/FleetDeployedData.h | 59 +- mods/src/prime/FleetLiveData.h | 141 ++-- mods/src/prime/FleetLocalViewController.h | 77 +- mods/src/prime/FleetMeshSelector.h | 24 +- mods/src/prime/FleetPlayerData.h | 284 +++---- mods/src/prime/FleetsManager.h | 155 +--- mods/src/prime/FullScreenChatViewController.h | 36 +- mods/src/prime/GameObject.h | 92 +-- mods/src/prime/GenericButtonContext.h | 36 +- mods/src/prime/GenericButtonWidget.h | 30 +- .../GenericRewardsScreenViewController.h | 38 +- mods/src/prime/HousingObjectViewerWidget.h | 19 +- mods/src/prime/HttpJob.h | 58 +- mods/src/prime/HttpRequest.h | 20 +- mods/src/prime/HttpResponse.h | 37 +- mods/src/prime/Hub.h | 207 ++--- mods/src/prime/HullSpec.h | 63 +- mods/src/prime/IBuffComparer.h | 32 +- mods/src/prime/IBuffData.h | 25 +- mods/src/prime/IEnumerator.h | 7 +- mods/src/prime/IList.h | 39 - mods/src/prime/Image.h | 48 ++ mods/src/prime/InputFieldWidget.h | 45 +- mods/src/prime/InterstitialViewController.h | 19 +- mods/src/prime/InventoryForPopup.h | 61 +- mods/src/prime/InventoryListViewController.h | 51 +- mods/src/prime/InventoryUseRowWidget.h | 13 +- mods/src/prime/LanguageManager.h | 46 +- mods/src/prime/LocaleTextContext.h | 31 + mods/src/prime/ManagedRead.h | 84 --- mods/src/prime/MiningObjectViewerWidget.h | 50 +- mods/src/prime/MissionsObjectViewerWidget.h | 19 +- mods/src/prime/MonoSingleton.h | 26 +- mods/src/prime/NavigationDirector.h | 19 - mods/src/prime/NavigationFleetWidget.h | 52 +- .../prime/NavigationInteractionUIContext.h | 13 +- .../NavigationInteractionUIViewController.h | 80 +- mods/src/prime/NavigationLOD.h | 28 +- mods/src/prime/NavigationManager.h | 22 +- mods/src/prime/NavigationPan.h | 136 +--- mods/src/prime/NavigationSectionManager.h | 41 +- mods/src/prime/NavigationZoom.h | 330 ++------ mods/src/prime/ObjectViewerBaseWidget.h | 64 +- .../prime/OfficerAssignmentViewController.h | 51 +- mods/src/prime/OrbitFrameProvider.h | 23 +- .../prime/ParentObjectViewerViewController.h | 59 +- mods/src/prime/PlanetViewUtils.h | 41 +- mods/src/prime/PlanetaryBaseManager.h | 33 +- mods/src/prime/PopulatedSystemData.h | 39 +- mods/src/prime/PreScanStationTargetWidget.h | 15 +- mods/src/prime/PreScanTargetWidget.h | 87 +-- mods/src/prime/RealtimeDataPayload.h | 74 +- mods/src/prime/RecallRequirement.h | 18 +- mods/src/prime/RectTransform.h | 33 + mods/src/prime/RequestDispatcherBase.h | 23 - mods/src/prime/RewardsButtonWidget.h | 31 +- mods/src/prime/ScanEngageButtonsWidget.h | 43 +- mods/src/prime/ScanTargetViewController.h | 26 +- mods/src/prime/SceneManager.h | 20 +- mods/src/prime/ScreenManager.h | 56 +- mods/src/prime/SectionManager.h | 5 - mods/src/prime/SelectableList.h | 96 +-- mods/src/prime/SemaphoreButtonListener.h | 46 +- mods/src/prime/SemaphoreListenerBase.h | 15 +- mods/src/prime/ServiceResponse.h | 101 +-- .../ShipActionPanelLocalViewController.h | 20 + .../prime/ShipBarItemLocalViewController.h | 9 +- .../ShipManagementScreenStateViewController.h | 48 +- mods/src/prime/ShipManagementViewContext.h | 100 +-- mods/src/prime/ShipManagementViewController.h | 47 +- mods/src/prime/ShipTileWidget.h | 14 +- mods/src/prime/ShopSummaryDirector.h | 29 +- mods/src/prime/ShortcutsManager.h | 15 +- mods/src/prime/SpecService.h | 24 +- mods/src/prime/StarNodeObjectViewerWidget.h | 32 +- mods/src/prime/StateContainer.h | 40 +- mods/src/prime/SystemViewScaler.h | 30 +- mods/src/prime/TKTouch.h | 32 +- mods/src/prime/TMP_InputField.h | 53 +- mods/src/prime/TabBarViewController.h | 81 +- mods/src/prime/TextLocalizer.h | 35 + mods/src/prime/Toast.h | 37 +- mods/src/prime/Transform.h | 70 +- mods/src/prime/TransitionManager.h | 40 +- mods/src/prime/TransitionViewController.h | 46 +- mods/src/prime/UIBehaviour.h | 15 +- mods/src/prime/UnityWebRequest.h | 23 +- mods/src/prime/UserProfile.h | 50 +- mods/src/prime/UserProfileManager.h | 15 +- mods/src/prime/UserProfileWidget.h | 28 +- mods/src/prime/Vector2.h | 38 +- mods/src/prime/Vector3.h | 49 +- mods/src/prime/ViewController.h | 25 +- mods/src/prime/VisibilityController.h | 69 +- .../src/prime/WaveDefenseObjectViewerWidget.h | 42 +- mods/src/prime/Widget.h | 49 +- mods/src/ship_name_match.h | 44 +- mods/src/str_utils.h | 2 +- scripts/check-il2cpp-dump.py | 309 ++++++++ tests/alliance_store_runtime.cc | 528 ------------- tests/il2cpp_bindings.cc | 116 +-- tests/il2cpp_checked.cc | 134 ---- tests/il2cpp_class_lookup.cc | 71 -- tests/il2cpp_gc.cc | 10 + tests/il2cpp_runtime.cc | 87 --- tests/run-shortcut-hint-cache.ps1 | 16 +- 199 files changed, 4960 insertions(+), 11750 deletions(-) rename {mods/src => libs}/il2cpp/GameAssembly.lib (100%) delete mode 100644 mods/src/il2cpp/il2cpp-functions.cc delete mode 100644 mods/src/il2cpp/il2cpp_checked.h delete mode 100644 mods/src/log_history.h delete mode 100644 mods/src/patches/alliance_store_bindings.h create mode 100644 mods/src/patches/object_tracker.h delete mode 100644 mods/src/prime/CallbackContainer.h create mode 100644 mods/src/prime/Component.h delete mode 100644 mods/src/prime/IList.h create mode 100644 mods/src/prime/Image.h create mode 100644 mods/src/prime/LocaleTextContext.h delete mode 100644 mods/src/prime/ManagedRead.h delete mode 100644 mods/src/prime/NavigationDirector.h create mode 100644 mods/src/prime/RectTransform.h delete mode 100644 mods/src/prime/RequestDispatcherBase.h delete mode 100644 mods/src/prime/SectionManager.h create mode 100644 mods/src/prime/ShipActionPanelLocalViewController.h create mode 100644 mods/src/prime/TextLocalizer.h create mode 100755 scripts/check-il2cpp-dump.py delete mode 100644 tests/alliance_store_runtime.cc delete mode 100644 tests/il2cpp_checked.cc delete mode 100644 tests/il2cpp_class_lookup.cc delete mode 100644 tests/il2cpp_runtime.cc diff --git a/mods/src/il2cpp/GameAssembly.lib b/libs/il2cpp/GameAssembly.lib similarity index 100% rename from mods/src/il2cpp/GameAssembly.lib rename to libs/il2cpp/GameAssembly.lib diff --git a/libs/il2cpp/include/il2cpp/il2cpp.h b/libs/il2cpp/include/il2cpp/il2cpp.h index 92630c80d..13a37c4c2 100644 --- a/libs/il2cpp/include/il2cpp/il2cpp.h +++ b/libs/il2cpp/include/il2cpp/il2cpp.h @@ -12,11 +12,13 @@ #include #include #include +#include #include #include #include #include #include +#include namespace il2cpp { @@ -256,6 +258,68 @@ namespace detail return R(); } + // Invoke for TryCall: empty (false for void) when the method is missing, the receiver is null, or a managed + // exception stopped the call. Exceptions are reported as Invoke reports them. + template using Tried = std::conditional_t, bool, std::optional>; + template Tried TryInvoke(const MethodInfo* method, Il2CppObject* target, A... args) + { + const bool is_static = method && (method->flags & METHOD_ATTRIBUTE_STATIC); + if (!method || (!is_static && !target)) + return {}; + if (!is_static && (method->flags & METHOD_ATTRIBUTE_VIRTUAL)) { + method = il2cpp_object_get_virtual_method(target, method); + if (!method) + return {}; + } + try { + if constexpr (std::is_void_v) { + InvokeNative(method, target, args...); + return true; + } else { + return InvokeNative(method, target, args...); + } + } catch (const Il2CppExceptionWrapper& error) { + ReportException(method, error.ex); + } catch (...) { + ReportException(method, nullptr); + } + return {}; + } + + // Call<"Name", F>: F is either the result type, with parameters taken from the argument types, + // or a full signature R(P...). A signature converts arguments to P without narrowing, and a P of + // T& is a ref/out parameter that binds the caller's lvalue (std::ref in the deduced form). + template struct Param { + using Type = T; + template static T From(A&& value) + { return T{std::forward(value)}; } + }; + template struct Param { + using Type = std::reference_wrapper; + static Type From(T& value) + { return std::ref(value); } + }; + + template struct MethodCall; + template struct MethodCall { + static const MethodInfo* Resolve(Il2CppClass* klass, std::string_view name, bool is_static) + { return ResolveMethod(klass, name, SignatureOf::Type...>(), is_static); } + template static R Run(const MethodInfo* method, Il2CppObject* target, A&&... args) + { + static_assert(sizeof...(A) == sizeof...(P), "argument count does not match the signature"); + return Invoke::Type...>(method, target, Param

::From(std::forward(args))...); + } + template static Tried TryRun(const MethodInfo* method, Il2CppObject* target, A&&... args) + { + static_assert(sizeof...(A) == sizeof...(P), "argument count does not match the signature"); + return TryInvoke::Type...>(method, target, Param

::From(std::forward(args))...); + } + }; + template struct CallOf : MethodCall { + }; + template struct CallOf : MethodCall { + }; + template T ReadField(FieldInfo* field, const Il2CppObject* object) { T value{}; @@ -306,10 +370,11 @@ template struct Class { static il2cpp::Method Method(std::string_view name, std::initializer_list params) { return detail::FindMethod(Raw(), name, params); } - template static R Call(A... args) + template static auto Call(A&&... args) { - static const auto* method = detail::ResolveMethod(Raw(), N.View(), detail::SignatureOf(), true); - return detail::Invoke(method, nullptr, args...); + using Sig = detail::CallOf...>; + static const auto* method = Sig::Resolve(Raw(), N.View(), true); + return Sig::Run(method, nullptr, std::forward(args)...); } template static T Get() { @@ -364,13 +429,22 @@ struct Object : Il2CppObject { }); detail::WriteField(field, &self, value); } - // Overloads are selected by the C++ argument types. std::ref marks ref/out parameters. - template R Call(this Self& self, A... args) + // Call<"Name", R>(args) selects the overload by the C++ argument types; std::ref marks ref/out + // parameters. Call<"Name", R(P...)>(args) names the signature and converts the arguments to it. + template auto Call(this Self& self, A&&... args) { - auto* method = detail::Lookup(self, [](Il2CppClass* klass) { - return detail::ResolveMethod(klass, N.View(), detail::SignatureOf(), false); - }); - return detail::Invoke(method, &self, args...); + using Sig = detail::CallOf...>; + auto* method = detail::Lookup(self, [](Il2CppClass* klass) { return Sig::Resolve(klass, N.View(), false); }); + return Sig::Run(method, &self, std::forward(args)...); + } + // Call, except that a failed call is told apart from a result that reads as zero: empty, or false for a void + // method, when the method is missing or a managed exception (still reported) stopped it. For callers that undo + // their own changes when the game's code fails. + template auto TryCall(this Self& self, A&&... args) + { + using Sig = detail::CallOf...>; + auto* method = detail::Lookup(self, [](Il2CppClass* klass) { return Sig::Resolve(klass, N.View(), false); }); + return Sig::TryRun(method, &self, std::forward(args)...); } template bool Is() const @@ -506,6 +580,34 @@ class GcHandle Il2CppGCHandle handle_ = nullptr; }; +// Strong roots for the objects one operation touches, released together when it ends. Keep returns its argument, +// or null after marking the set unhealthy when the runtime could not root it. Null needs no root. +class GcRoots +{ +public: + Il2CppObject* Keep(Il2CppObject* object) + { + if (!object) + return nullptr; + auto root = GcHandle::Strong(object); + if (root.Get() != object) { + healthy_ = false; + return nullptr; + } + roots_.push_back(std::move(root)); + return object; + } + template T* Keep(T* object) + { return reinterpret_cast(Keep(reinterpret_cast(object))); } + // False once any Keep failed. + bool healthy() const + { return healthy_; } + +private: + std::vector roots_; + bool healthy_ = true; +}; + // Unity engine internal calls, e.g. "UnityEngine.Input::GetKeyInt(UnityEngine.KeyCode)". template Signature* ResolveICall(const char* name) { return reinterpret_cast(il2cpp_resolve_icall(name)); } diff --git a/libs/il2cpp/src/il2cpp.cc b/libs/il2cpp/src/il2cpp.cc index eed975555..334a8a519 100644 --- a/libs/il2cpp/src/il2cpp.cc +++ b/libs/il2cpp/src/il2cpp.cc @@ -34,7 +34,7 @@ namespace std::string TypeName(const Il2CppType* type) { - auto* raw = type ? il2cpp_type_get_name(type) : nullptr; + auto* raw = type ? il2cpp_type_get_name(type) : nullptr; std::string name = raw ? raw : "?"; if (raw) il2cpp_free(raw); @@ -51,7 +51,7 @@ namespace std::string Describe(const detail::Slot& slot) { - using Kind = detail::Slot::Kind; + using Kind = detail::Slot::Kind; std::string text = slot.byref ? "ref " : ""; if (slot.kind == Kind::kVoid) return text + "void"; @@ -96,11 +96,12 @@ namespace // Bindings may name a base or derived class of the declared type. If the binding's own class // is missing, nothing matches; guessing could pass an object of the wrong type. auto* expected = slot.klass(); - return expected && il2cpp_class_is_assignable_from(klass, expected) - || il2cpp_class_is_assignable_from(expected, klass); + return expected + && (il2cpp_class_is_assignable_from(klass, expected) || il2cpp_class_is_assignable_from(expected, klass)); } uint32_t alignment = 0; - if (!il2cpp_class_is_valuetype(klass) || static_cast(il2cpp_class_value_size(klass, &alignment)) != slot.size) + if (!il2cpp_class_is_valuetype(klass) + || static_cast(il2cpp_class_value_size(klass, &alignment)) != slot.size) return false; if (il2cpp_class_is_enum(klass) || IsIntegral(type->type)) return IsIntegral(slot.primitive); @@ -226,8 +227,7 @@ namespace detail return {}; const MethodInfo* found = nullptr; ForEachMethod(klass, name, [&](const MethodInfo* method) { - if (!Callable(method) || (method->flags & METHOD_ATTRIBUTE_ABSTRACT) - || method->parameters_count != params.size()) + if (!Callable(method) || (method->flags & METHOD_ATTRIBUTE_ABSTRACT) || method->parameters_count != params.size()) return true; size_t index = 0; for (auto param : params) { @@ -246,8 +246,7 @@ namespace detail return Method(found); } - const MethodInfo* ResolveMethod(Il2CppClass* klass, std::string_view name, const Signature& signature, - bool is_static) + const MethodInfo* ResolveMethod(Il2CppClass* klass, std::string_view name, const Signature& signature, bool is_static) { if (!klass) return nullptr; diff --git a/mods/src/errormsg.h b/mods/src/errormsg.h index 3fc14b2e5..acaa9d7f0 100644 --- a/mods/src/errormsg.h +++ b/mods/src/errormsg.h @@ -10,21 +10,6 @@ namespace ErrorMsg { -static auto MissingMethod(const char* classname, const char* methodname) -{ - spdlog::error("Unable to find method '{}->{}'", classname, methodname); -} - -static void MissingStaticMethod(const char* classname, const char* methodname) -{ - spdlog::error("Unable to find method '{}::{}'", classname, methodname); -} - -static void MissingHelper(const char* namespacename, const char* classname) -{ - spdlog::error("Unable to find helper '{}.{}'", namespacename, classname); -} - static void SyncMsg(const char* section, const std::string& msg) { spdlog::error("Failed to send {} sync data: {}", section, msg); diff --git a/mods/src/il2cpp/il2cpp-functions.cc b/mods/src/il2cpp/il2cpp-functions.cc deleted file mode 100644 index 0145bcec0..000000000 --- a/mods/src/il2cpp/il2cpp-functions.cc +++ /dev/null @@ -1,40 +0,0 @@ -#include "il2cpp-functions.h" - -#include - -#if !_WIN32 -#include -#include -#include - -#define PATH_MAX 1024 -#if defined(__cplusplus) -extern "C" { -#endif // __cplusplus -#define DO_API(r, n, p) n##_t n; -#define DO_API_NO_RETURN(r, n, p) DO_API(r, n, p) -#include "il2cpp-api-functions.h" -#undef DO_API -#undef DO_API_NORETURN -#if defined(__cplusplus) -} -#endif // __cplusplus -#endif - -void init_il2cpp_pointers() -{ -#if !_WIN32 - char buf[PATH_MAX]; - uint32_t bufsize = PATH_MAX; - _NSGetExecutablePath(buf, &bufsize); - - char assembly_path[PATH_MAX]; - snprintf(assembly_path, sizeof(assembly_path), "%s/%s", dirname(buf), "../Frameworks/GameAssembly.dylib"); - auto assembly = dlopen(assembly_path, RTLD_LAZY | RTLD_GLOBAL); -#define DO_API(r, n, p) n = (n##_t)dlsym(assembly, #n); -#define DO_API_NO_RETURN(r, n, p) DO_API(r, n, p) -#include "il2cpp-api-functions.h" -#undef DO_API -#undef DO_API_NORETURN -#endif -} diff --git a/mods/src/il2cpp/il2cpp_checked.h b/mods/src/il2cpp/il2cpp_checked.h deleted file mode 100644 index 59ebf056a..000000000 --- a/mods/src/il2cpp/il2cpp_checked.h +++ /dev/null @@ -1,70 +0,0 @@ -#pragma once - -#include "runtime.h" -#include -#include - -#include -#include - -// Optional UI features use these checked operations to stop on metadata drift -// or managed exceptions. Arguments are managed references (including null), not -// value types or byrefs. Existing unchecked helper behavior is unchanged. -namespace Il2CppChecked -{ -inline Il2CppObject* Invoke(Il2CppObject* object, const char* name, int count = 0, void** args = nullptr) -{ - if (!object) - return nullptr; - if (!object->klass || count < 0 || (count && !args)) - throw std::runtime_error("invalid instance invocation"); - auto* method = IL2CppClassHelper(object->klass).GetMethodInfo(name, count); - if (!method || method->parameters_count != count || !method->invoker_method - || (method->flags & METHOD_ATTRIBUTE_STATIC) || !method->return_type || method->return_type->byref) - throw std::runtime_error("missing instance method"); - for (int i = 0; i < count; ++i) { - auto* parameter = method->parameters ? method->parameters[i] : nullptr; - auto* expected = parameter ? il2cpp_class_from_type(parameter) : nullptr; - auto* argument = args ? static_cast(args[i]) : nullptr; - if (!expected || parameter->byref || il2cpp_class_is_valuetype(expected) - || (argument && !il2cpp_class_is_assignable_from(expected, argument->klass))) - throw std::runtime_error("reference argument contract changed"); - } - Il2CppObject* result = nullptr; - if (!Il2CppRuntime::TryInvoke(method, object, args, &result)) - throw std::runtime_error("managed invocation failed"); - return result; -} - -inline bool Boolean(Il2CppObject* result) -{ - bool value = false; - if (!Il2CppRuntime::TryBoolean(result, value)) - throw std::runtime_error("expected Boolean"); - return value; -} - -inline FieldInfo* Field(Il2CppObject* object, const char* name, const char* expected) -{ - auto* field = object && object->klass ? IL2CppClassHelper(object->klass).GetField(name).get_info() : nullptr; - auto* type = field && field->type ? il2cpp_class_from_type(field->type) : nullptr; - if (!field || !field->type || (field->type->attrs & FIELD_ATTRIBUTE_STATIC) || !type || !type->name - || std::strcmp(type->name, expected) != 0) - throw std::runtime_error("field contract changed"); - return field; -} - -inline Il2CppObject* ReferenceField(Il2CppObject* object, const char* name, const char* expected) -{ - if (!object) - return nullptr; - auto* field = Field(object, name, expected); - if (field->type->byref || il2cpp_class_is_valuetype(il2cpp_class_from_type(field->type))) - throw std::runtime_error("expected reference field"); - return il2cpp_field_get_value_object(field, object); -} - -inline bool BooleanField(Il2CppObject* object, const char* name) -{ return object && Boolean(il2cpp_field_get_value_object(Field(object, name, "Boolean"), object)); } - -} // namespace Il2CppChecked diff --git a/mods/src/log_history.h b/mods/src/log_history.h deleted file mode 100644 index 25f1ef70e..000000000 --- a/mods/src/log_history.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include -#include - -// Keep the extension intact: community_patch.log.1 is the newest previous session. -inline void RotateLogHistory(const std::filesystem::path& log) -{ - namespace fs = std::filesystem; - const auto backup = [&](int index) { return fs::path(log.string() + "." + std::to_string(index)); }; - fs::remove(backup(5)); - for (int index = 4; index >= 1; --index) { - if (fs::exists(backup(index))) { - fs::rename(backup(index), backup(index + 1)); - } - } - if (fs::exists(log)) { - fs::rename(log, backup(1)); - } -} diff --git a/mods/src/patches/alliance_store_bindings.h b/mods/src/patches/alliance_store_bindings.h deleted file mode 100644 index 57fa3c4b0..000000000 --- a/mods/src/patches/alliance_store_bindings.h +++ /dev/null @@ -1,416 +0,0 @@ -#pragma once -// Alliance Games-only checked runtime operations. No replacement binding engine. -#include -#include -#include -#include -#include -#include -#include - -namespace alliance_store_game -{ -using Object = Il2CppObject; -template struct Result { - bool succeeded = false; - T value{}; - explicit operator bool() const - { return succeeded; } -}; -template <> struct Result { - bool succeeded = false; - explicit operator bool() const - { return succeeded; } -}; -class Handle -{ -public: - Handle() = default; - Handle(const Handle&) = delete; - Handle& operator=(const Handle&) = delete; - Handle(Handle&& other) noexcept - : handle_(std::exchange(other.handle_, nullptr)) - { - } - Handle& operator=(Handle&& other) noexcept - { - if (this != &other) { - Reset(); - handle_ = std::exchange(other.handle_, nullptr); - } - return *this; - } - ~Handle() - { Reset(); } - static Handle Strong(Object* value) - { return Make(value, false); } - static Handle Weak(Object* value) - { return Make(value, true); } - Object* Get() const - { return handle_ ? il2cpp_gchandle_get_target(handle_) : nullptr; } - template T* Get() const - { return reinterpret_cast(Get()); } - explicit operator bool() const - { return handle_ != nullptr; } - void Reset() - { - if (handle_) - il2cpp_gchandle_free(std::exchange(handle_, nullptr)); - } - -private: - static Handle Make(Object* value, bool weak) - { - Handle result; - if (value) - result.handle_ = weak ? il2cpp_gchandle_new_weakref(value, false) : il2cpp_gchandle_new(value, false); - if (result.Get() != value) - result.Reset(); - return result; - } - Il2CppGCHandle handle_ = nullptr; -}; -struct Roots { - std::vector values; - bool healthy = true; - Object* Keep(Object* value) - { - if (!value) - return nullptr; - auto root = Handle::Strong(value); - if (!root || root.Get() != value) { - healthy = false; - return nullptr; - } - values.push_back(std::move(root)); - return value; - } -}; -inline Il2CppClass* Class(const char* space, const char* name, const char* assembly = "Assembly-CSharp") -{ return il2cpp_get_class_helper(assembly, space, name).get_cls(); } -inline Il2CppClass* TooltipClass() -{ return Class("Digit.Prime.Inventories", "InventoryTooltipTriggerWidget"); } -inline Il2CppClass* TriggerClass() -{ return Class("Digit.Client.Tooltip", "TooltipTrigger"); } -inline Il2CppClass* ActionClass() -{ return Class("Digit.Client.Tooltip", "TooltipActionTrigger"); } -inline bool Reference(const Il2CppType* type) -{ - auto* cls = type && !type->byref ? il2cpp_class_from_type(type) : nullptr; - return cls && !il2cpp_class_is_valuetype(cls) - && (type->type == IL2CPP_TYPE_CLASS || type->type == IL2CPP_TYPE_OBJECT || type->type == IL2CPP_TYPE_STRING - || type->type == IL2CPP_TYPE_GENERICINST || type->type == IL2CPP_TYPE_SZARRAY - || type->type == IL2CPP_TYPE_ARRAY); -} -struct Vector2 { - float x, y; -}; -struct OverridableBool { - bool serialized, current, modified; -}; -static_assert(sizeof(OverridableBool) == 3); -template bool ValueType(const Il2CppType* type) -{ - if (!type || type->byref) - return false; - if constexpr (std::is_same_v) - return type->type == IL2CPP_TYPE_VOID; - else if constexpr (std::is_pointer_v) - return Reference(type); - else if constexpr (std::is_same_v) - return type->type == IL2CPP_TYPE_BOOLEAN; - else if constexpr (std::is_same_v) - return type->type == IL2CPP_TYPE_I4; - else if constexpr (std::is_same_v) - return type->type == IL2CPP_TYPE_I8; - else if constexpr (std::is_same_v) - return type->type == IL2CPP_TYPE_R4; - else { - const char* name = - std::is_same_v ? "UnityEngine.Vector2" : "Digit.Client.Serialization.OverridableBool"; - uint32_t alignment = 0; - auto* cls = il2cpp_class_from_type(type); - return method_contract::Type(type, name) && cls && il2cpp_class_is_valuetype(cls) - && il2cpp_class_value_size(cls, &alignment) == sizeof(T); - } -} -struct Argument { - void* value; - const char* scalar = nullptr; -}; -inline Argument Ref(Object* value) -{ return {value}; } -template Argument Scalar(T& value, const char* name) -{ return {&value, name}; } -// Inherited/closed List and Widget accessors retain their concrete runtime metadata. -template bool Signature(const MethodInfo* method, bool is_static, std::initializer_list args) -{ - if (!method || method->is_generic || !method->invoker_method - || bool(method->flags & METHOD_ATTRIBUTE_STATIC) != is_static || method->parameters_count != args.size() - || !ValueType(method->return_type)) - return false; - size_t index = 0; - for (const auto& arg : args) { - const auto* type = method->parameters ? method->parameters[index++] : nullptr; - if (arg.scalar) { - if (!method_contract::Type(type, arg.scalar)) - return false; - } else { - auto* value = static_cast(arg.value); - if (!Reference(type) || (value && !il2cpp_class_is_assignable_from(il2cpp_class_from_type(type), value->klass))) - return false; - } - } - return true; -} -template -Result Call(Object* object, const char* name, std::initializer_list args = {}, - Il2CppClass* static_owner = nullptr) -{ - const MethodInfo* selected = nullptr; - for (auto* cls = object ? object->klass : static_owner; cls && !selected; cls = il2cpp_class_get_parent(cls)) { - void* iterator = nullptr; - while (auto* method = il2cpp_class_get_methods(cls, &iterator)) { - if (std::strcmp(method->name, name) || !Signature(method, !object, args)) - continue; - if (selected) - return {}; - selected = method; - } - } - if (!selected) - return {}; - if (object) - selected = il2cpp_object_get_virtual_method(object, selected); - if (!Signature(selected, !object, args)) - return {}; - std::vector parameters; - for (const auto& arg : args) - parameters.push_back(arg.value); - Object* result = nullptr; - if (!Il2CppRuntime::TryInvoke(selected, object, parameters.empty() ? nullptr : parameters.data(), &result)) - return {}; - if constexpr (std::is_same_v) - return {true}; - else if constexpr (std::is_pointer_v) { - if (result && !il2cpp_class_is_assignable_from(il2cpp_class_from_type(selected->return_type), result->klass)) - return {}; - return {true, reinterpret_cast(result)}; - } else { - auto box = Handle::Strong(result); - if (!box || !box.Get() || !ValueType(il2cpp_class_get_type(box.Get()->klass))) - return {}; - auto* data = il2cpp_object_unbox(box.Get()); - if (!data) - return {}; - T value; - std::memcpy(&value, data, sizeof(T)); - return {true, value}; - } -} -inline Result Alive(Object* object) -{ - return object ? Call(nullptr, "op_Implicit", {Ref(object)}, - Class("UnityEngine", "Object", "UnityEngine.CoreModule")) - : Result{true, false}; -} -template struct Field { - FieldInfo* field = nullptr; - bool Resolve(Il2CppClass* cls, const char* name, const char* expected = nullptr) - { - field = nullptr; - for (; cls && !field; cls = il2cpp_class_get_parent(cls)) - field = il2cpp_class_get_field_from_name(cls, name); - if (!field || (il2cpp_field_get_flags(field) & FIELD_ATTRIBUTE_STATIC) || field->offset < sizeof(Object) - || !field->parent || static_cast(field->offset) + sizeof(T) > il2cpp_class_instance_size(field->parent)) - return false; - if constexpr (std::is_same_v) { - auto* type = il2cpp_class_from_type(field->type); - if (!ValueType(field->type) - && !(type && il2cpp_class_is_enum(type) && ValueType(il2cpp_class_enum_basetype(type)))) - return false; - } else if (!ValueType(field->type)) - return false; - return !expected || method_contract::Type(field->type, expected); - } - T Read(Object* object) const - { - T value{}; - if (field && object && il2cpp_class_is_assignable_from(field->parent, object->klass)) - il2cpp_field_get_value(object, field, &value); - return value; - } - bool Write(Object* object, T value) const - { - if (!field || !object || !il2cpp_class_is_assignable_from(field->parent, object->klass)) - return false; - if constexpr (std::is_pointer_v) { - if (value && !il2cpp_class_is_assignable_from(il2cpp_class_from_type(field->type), value->klass)) - return false; - il2cpp_field_set_value(object, field, value); // Reference itself; runtime write barrier. - } else - il2cpp_field_set_value(object, field, &value); - const auto observed = Read(object); - return std::memcmp(&observed, &value, sizeof(T)) == 0; - } -}; -struct Fields { - Field trigger, action, search, locale, settings, header, description, target, description_override, loader; - Field scroller, spawned, bundle_tooltip, bundle_owner, spawning, initializing; - Field show_header, show_description; - Field override_locale, interactable, regenerating; - Field anchor; - Field size, offset; - bool Resolve() - { - auto* widget = TooltipClass(); - auto* trigger_type = TriggerClass(); - auto* shop = Class("Digit.Prime.Shop", "ShopListScrollerViewController"); - auto* smart = Class("Digit.Client.UI", "SmartScrollerBase"); - auto* bundle = Class("Digit.Prime.Shop", "BundleDataWidget"); - bool ok = true; - ok &= trigger.Resolve(widget, "_tooltipTrigger", "Digit.Client.Tooltip.TooltipTrigger"); - ok &= action.Resolve(widget, "_tooltipActionTrigger", "Digit.Client.Tooltip.TooltipActionTrigger"); - ok &= search.Resolve(widget, "_searchActionLocaleSetting", "Digit.Prime.Actions.ActionLocaleSetting"); - ok &= locale.Resolve(widget, "_actionLocaleSetting", "Digit.Prime.Actions.ActionLocaleSetting"); - ok &= settings.Resolve(widget, "_settings", "Digit.Prime.Actions.ActionLocaleSettingsList"); - ok &= show_header.Resolve(trigger_type, "_showHeader"); - ok &= show_description.Resolve(trigger_type, "_showDescription"); - ok &= header.Resolve(trigger_type, "_header", "Digit.Client.UI.LocaleTextContext"); - ok &= description.Resolve(trigger_type, "_description", "Digit.Client.UI.LocaleTextContext"); - ok &= override_locale.Resolve(trigger_type, "_overrideLocale"); - ok &= anchor.Resolve(trigger_type, "_anchorOverride"); - ok &= size.Resolve(trigger_type, "_sizeOverride"); - ok &= offset.Resolve(trigger_type, "_positionOverrideOffset"); - ok &= target.Resolve(trigger_type, "_positionOverrideTarget", "UnityEngine.Transform"); - ok &= interactable.Resolve(trigger_type, "_isInteractable"); - ok &= description_override.Resolve(trigger_type, "k__BackingField", "System.String"); - ok &= loader.Resolve(trigger_type, "_loader", "Digit.Client.Tooltip.TooltipLoadAndShow"); - ok &= scroller.Resolve(shop, "_smartScroller"); - ok &= spawned.Resolve(smart, "_spawnedItems"); - ok &= bundle_tooltip.Resolve(bundle, "_invTooltip", "Digit.Prime.Inventories.InventoryTooltipTriggerWidget"); - ok &= bundle_owner.Resolve(bundle, "_scrollerController", "Digit.Prime.Shop.ShopListScrollerViewController"); - ok &= regenerating.Resolve(smart, "_regeneratingContent"); - ok &= spawning.Resolve(smart, "_spawnerCoroutine"); - ok &= initializing.Resolve(smart, "_dataHeldWhilstInitializing"); - return ok; - } -}; -inline Fields& Members() -{ - static Fields fields; - return fields; -} -inline bool IsStore(Object* object) -{ - auto* type = Class("Digit.Prime.Alliances.Games", "AllianceGamesStoreScrollerViewController"); - return object && type && il2cpp_class_is_assignable_from(type, object->klass); -} -inline Object* TypeObject(Il2CppClass* type) -{ return type ? reinterpret_cast(il2cpp_type_get_object(il2cpp_class_get_type(type))) : nullptr; } -inline Result Eligible(Object* item) -{ - Roots roots; - if (!roots.Keep(item)) - return {}; - const auto resource = Call(item, "get_ResourceSpec"); - auto* spec = resource ? roots.Keep(resource.value) : nullptr; - const auto id = Call(spec, "get_Id"); - if (!id || id.value <= 0) - return {}; - const auto observed = Call(nullptr, "get_ResearchManager", {}, Class("Digit.Client.Core", "Hub")); - auto* manager = observed ? roots.Keep(observed.value) : nullptr; - auto resource_id = id.value; - int32_t tree = 0; - return manager ? Call( - manager, "IsResourceRequiredByVisibleResearches", - {Scalar(resource_id, "System.Int64"), Scalar(tree, "Digit.PrimeServer.Models.ResearchTreeType")}) - : Result{}; -} -inline bool Enabled(Object* trigger, bool value) -{ - static const auto* method = method_contract::Resolve(Class("UnityEngine", "Behaviour", "UnityEngine.CoreModule"), - "set_enabled", false, "System.Void", {"System.Boolean"}); - void* args[]{&value}; - return trigger && Il2CppRuntime::TryInvoke(method, trigger, args); -} -inline Result NormalizedPosition(Object* smart) -{ return Call(smart, "get_NormalizedPosition"); } -inline bool NotifyPosition(Object* smart, Vector2 value) -{ return Call(smart, "OnValueChangedCallback", {Scalar(value, "UnityEngine.Vector2")}).succeeded; } -inline Result CurrencyCount(Object* context, Roots& roots) -{ - const auto observed = Call(context, "get_StoreCurrency"); - auto* currencies = observed ? roots.Keep(observed.value) : nullptr; - const auto soft = Call(currencies, "get_SoftResources"); - return soft && roots.Keep(soft.value) ? Call(soft.value, "get_Count") : Result{}; -} -inline Object* Component(Object* source, const char* name, Il2CppClass* type, Roots& roots, bool children = false) -{ - auto* reflected = roots.Keep(TypeObject(type)); - if (!reflected) - return nullptr; - bool inactive = true; - const auto found = children ? Call(source, name, {Ref(reflected), Scalar(inactive, "System.Boolean")}) - : Call(source, name, {Ref(reflected)}); - return found ? roots.Keep(found.value) : nullptr; -} -inline Object* ActionComponent(Object* widget, bool create, Roots& roots) -{ - const auto observed = Call(widget, "get_gameObject"); - auto* object = observed ? roots.Keep(observed.value) : nullptr; - if (!object) - return nullptr; - auto* reflected = roots.Keep(TypeObject(ActionClass())); - if (!reflected) - return nullptr; - const auto found = Call(object, "GetComponent", {Ref(reflected)}); - if (!found) - return nullptr; - if (found.value) - return roots.Keep(found.value); - if (!create) - return nullptr; - const auto added = Call(object, "AddComponent", {Ref(reflected)}); - return added ? roots.Keep(added.value) : nullptr; -} -inline Object* PlainComponent(Object* widget, Roots& roots) -{ - auto* value = Component(widget, "GetComponent", TriggerClass(), roots); - return value && value->klass == TriggerClass() ? value : nullptr; -} -inline Object* Template(Roots& roots) -{ - auto* type = roots.Keep(TypeObject(TooltipClass())); - const auto found = type ? Call(nullptr, "FindObjectsOfTypeAll", {Ref(type)}, - Class("UnityEngine", "Resources", "UnityEngine.CoreModule")) - : Result{}; - auto* array = found ? roots.Keep(found.value) : nullptr; - const auto* metadata = array ? il2cpp_class_get_type(array->klass) : nullptr; - if (!metadata || metadata->type != IL2CPP_TYPE_SZARRAY || !array->klass->element_class - || il2cpp_class_is_valuetype(array->klass->element_class)) - return nullptr; - auto* items = reinterpret_cast(array); - for (il2cpp_array_size_t index = 0; index < items->max_length; ++index) { - auto* widget = reinterpret_cast(items->vector)[index]; - if (widget && il2cpp_class_is_assignable_from(TooltipClass(), widget->klass) && Members().action.Read(widget) - && Members().search.Read(widget)) - return roots.Keep(widget); - } - return nullptr; -} -inline bool CopyAppearance(Object* plain, Object* action, Roots& roots) -{ - auto& f = Members(); - auto copy = [&](const Field& field) { - const auto value = field.Read(plain); - if constexpr (std::is_pointer_v) - if (value && !roots.Keep(value)) - return false; - return field.Write(action, value); - }; - return copy(f.show_header) && copy(f.header) && copy(f.show_description) && copy(f.description) - && copy(f.override_locale) && copy(f.anchor) && copy(f.size) && copy(f.target) && copy(f.offset) - && copy(f.interactable) && copy(f.description_override) && copy(f.loader); -} -} // namespace alliance_store_game diff --git a/mods/src/patches/battle_notify_parser.cc b/mods/src/patches/battle_notify_parser.cc index d10b05605..903824a67 100644 --- a/mods/src/patches/battle_notify_parser.cc +++ b/mods/src/patches/battle_notify_parser.cc @@ -2,9 +2,10 @@ #include "str_utils.h" -#include +#include #include #include +#include #include #include #include @@ -42,94 +43,31 @@ static bool seh_call(Fn fn) // --------------------------------------------------------------------------- namespace { -struct LocaleCache { - Il2CppClass* ltc_class = nullptr; // LocaleTextContext - const MethodInfo* ltc_ctor = nullptr; // .ctor(string, string) - const MethodInfo* ltc_apply_id_params = nullptr; // ApplyIdentifierParameters(object[]) - const MethodInfo* locale_localize = nullptr; // LocaleUtilities.Localize(LTC, bool, bool) - Il2CppClass* object_array_class = nullptr; // System.Object[] - Il2CppClass* int64_class = nullptr; // System.Int64 - - bool ready() const { return ltc_class && ltc_ctor && ltc_apply_id_params && locale_localize && object_array_class; } -}; - -LocaleCache s_locale; - -void ensure_locale_cache() -{ - if (s_locale.ltc_class) return; - - auto ltc = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "LocaleTextContext"); - if (ltc.isValidHelper()) { - s_locale.ltc_class = ltc.get_cls(); - s_locale.ltc_ctor = ltc.GetMethodInfo(".ctor", 2); - s_locale.ltc_apply_id_params = ltc.GetMethodInfo("ApplyIdentifierParameters", 1); - } - if (!s_locale.ltc_class || !s_locale.ltc_ctor || !s_locale.ltc_apply_id_params) - spdlog::warn("[Notify] Could not resolve LocaleTextContext methods"); - - auto lu = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Localization", "LocaleUtilities"); - if (lu.isValidHelper()) { - auto* cls = lu.get_cls(); - if (cls) { - void* iter = nullptr; - while (auto* m = il2cpp_class_get_methods(cls, &iter)) { - if (std::string_view(il2cpp_method_get_name(m)) == "Localize" && - il2cpp_method_get_param_count(m) == 3) { - s_locale.locale_localize = m; - break; - } - } - } - } - if (!s_locale.locale_localize) - spdlog::warn("[Notify] Could not resolve LocaleUtilities.Localize — falling back to parse_hull_key"); - - auto obj = il2cpp_get_class_helper("mscorlib", "System", "Object"); - if (obj.isValidHelper()) - s_locale.object_array_class = il2cpp_array_class_get(obj.get_cls(), 1); - - auto i64 = il2cpp_get_class_helper("mscorlib", "System", "Int64"); - if (i64.isValidHelper()) - s_locale.int64_class = i64.get_cls(); -} +using SystemObject = il2cpp::Class<"mscorlib", "System", "Object">; +using SystemInt64 = il2cpp::Class<"mscorlib", "System", "Int64">; // Create a LocaleTextContext(identifier, category), apply the given parameter, // and call LocaleUtilities.Localize. Returns empty string on failure. std::string localize(const char* identifier, const char* category, Il2CppObject* param) { - ensure_locale_cache(); - if (!s_locale.ready() || !param) return {}; + auto* object_class = SystemObject::Raw(); + if (!object_class || !param) return {}; std::string result; if (!seh_call([&] { auto* idStr = il2cpp_string_new(identifier); auto* catStr = il2cpp_string_new(category); - if (!idStr || !catStr) return; - auto* arr = il2cpp_array_new_specific(s_locale.object_array_class, 1); + auto* arr = il2cpp::Array::New(object_class, 1); if (!arr) return; - reinterpret_cast(reinterpret_cast(arr)->vector)[0] = param; + arr->Set(0, param); - auto* ltc = il2cpp_object_new(s_locale.ltc_class); + auto* ltc = il2cpp::New(idStr, catStr); if (!ltc) return; - void* ctorParams[2] = {idStr, catStr}; - Il2CppException* exc = nullptr; - il2cpp_runtime_invoke(s_locale.ltc_ctor, ltc, ctorParams, &exc); - if (exc) return; - - void* applyParams[1] = {arr}; - exc = nullptr; - il2cpp_runtime_invoke(s_locale.ltc_apply_id_params, ltc, applyParams, &exc); - if (exc) return; - - bool localText = false, invariant = false; - void* locParams[3] = {ltc, &localText, &invariant}; - exc = nullptr; - auto* ret = il2cpp_runtime_invoke(s_locale.locale_localize, nullptr, locParams, &exc); - if (exc || !ret) return; - result = to_string(reinterpret_cast(ret)); + ltc->ApplyIdentifierParameters(arr); + if (auto* ret = LocaleUtilities::Localize(ltc, false, false)) + result = to_string(ret); })) spdlog::warn("[Notify] SEH: localize crashed for \"{}/{}\"", identifier, category); return result; @@ -138,9 +76,8 @@ std::string localize(const char* identifier, const char* category, Il2CppObject* // Box an int64 as Il2CppObject for use as a localization parameter. Il2CppObject* box_int64(int64_t value) { - ensure_locale_cache(); - if (!s_locale.int64_class) return nullptr; - return il2cpp_value_box(s_locale.int64_class, &value); + auto* int64_class = SystemInt64::Raw(); + return int64_class ? il2cpp_value_box(int64_class, &value) : nullptr; } } // namespace @@ -197,7 +134,7 @@ static std::string localize_hull_name(int64_t locaId) static std::string localize_hull_name(const std::string& locaStringId) { auto* strObj = il2cpp_string_new(locaStringId.c_str()); - return strObj ? localize("ship_name_{0}", "ships", reinterpret_cast(strObj)) : std::string{}; + return localize("ship_name_{0}", "ships", reinterpret_cast(strObj)); } // Enemy commander name via locaId: "marauder_name_only_{0}" / "navigation" @@ -210,24 +147,21 @@ static std::string localize_enemy_name(int64_t locaId) // --------------------------------------------------------------------------- // Resolve hull ID → display name via SpecService + game localization // --------------------------------------------------------------------------- -static std::string resolve_hull_name(BattleResultHeader* brh, long hullId) +static std::string resolve_hull_name(BattleResultHeader* brh, int64_t hullId) { if (hullId == 0) return ""; - auto* specSvc = reinterpret_cast(brh->get_SpecService()); + auto* specSvc = static_cast(brh->get_SpecService()); if (!specSvc) return fmt::format("Hull#{}", hullId); auto* hull = specSvc->GetHull(hullId); if (!hull) return fmt::format("Hull#{}", hullId); - // Extract IdRefs from HullSpec (offset 0x90) - auto* idRefs = *reinterpret_cast(reinterpret_cast(hull) + 0x90); std::string locaStringId; int64_t locaId = 0; - if (idRefs) { - auto* locaStrPtr = *reinterpret_cast(reinterpret_cast(idRefs) + 0x10); - if (locaStrPtr) locaStringId = to_string(locaStrPtr); - locaId = *reinterpret_cast(reinterpret_cast(idRefs) + 0x40); + if (auto* idRefs = hull->idRefs()) { + if (auto* locaStrPtr = idRefs->locaStringId()) locaStringId = to_string(locaStrPtr); + locaId = idRefs->locaId(); } // Try numeric locaId first, then string locaStringId, then manual parse @@ -242,7 +176,7 @@ static std::string resolve_hull_name(BattleResultHeader* brh, long hullId) return localized; } - auto* nameStr = hull->Name; + auto* nameStr = hull->Name(); if (!nameStr) return fmt::format("Hull#{}", hullId); return parse_hull_key(to_string(nameStr)); } @@ -302,23 +236,24 @@ static BattleSummaryData build_battle_data(Il2CppObject* data) BattleSummaryData result; if (!data) return result; - auto* brh = reinterpret_cast(data); + auto* brh = static_cast(data)->As(); + if (!brh) return result; if (!seh_call([&] { - auto* profile = reinterpret_cast(brh->get_PlayerUserProfile()); - if (profile && profile->Name) - result.playerName = to_string(profile->Name); + auto* profile = static_cast(brh->get_PlayerUserProfile()); + if (profile && profile->Name()) + result.playerName = to_string(profile->Name()); })) spdlog::warn("[Notify] SEH: get_PlayerUserProfile crashed"); if (!seh_call([&] { - auto* profile = reinterpret_cast(brh->get_EnemyUserProfile()); + auto* profile = static_cast(brh->get_EnemyUserProfile()); if (profile) { - if (profile->Name) - result.enemyName = to_string(profile->Name); + if (profile->Name()) + result.enemyName = to_string(profile->Name()); // NPC/marauder profiles have empty names — localize via LocaId if (result.enemyName.empty()) { - auto locaId = profile->LocaId; + auto locaId = profile->LocaId(); if (locaId != 0) { auto localized = localize_enemy_name(locaId); if (!localized.empty() && localized != "Unknown") @@ -343,50 +278,45 @@ static BattleSummaryData build_battle_data(Il2CppObject* data) spdlog::warn("[Notify] SEH: get_BattleType crashed"); if (!seh_call([&] { - result.playerShip = normalize_ship_name(resolve_hull_name(brh, brh->PlayerShipHullId)); + result.playerShip = normalize_ship_name(resolve_hull_name(brh, brh->PlayerShipHullId())); })) spdlog::warn("[Notify] SEH: PlayerShipHullId crashed"); if (!seh_call([&] { - result.enemyShip = normalize_ship_name(resolve_hull_name(brh, brh->EnemyShipHullId)); + result.enemyShip = normalize_ship_name(resolve_hull_name(brh, brh->EnemyShipHullId())); })) spdlog::warn("[Notify] SEH: EnemyShipHullId crashed"); return result; } -template -static T read_instance_field(Il2CppObject* obj, const char* name, T fallback = {}) +// Toast data types vary by state, so fields resolve on the receiver's runtime class. +template +static T read_instance_field(Il2CppObject* obj) { - if (!obj) return fallback; - auto* field = il2cpp_class_get_field_from_name(il2cpp_object_get_class(obj), name); - if (!field) return fallback; - - T value = fallback; - il2cpp_field_get_value(obj, field, &value); - return value; + return obj ? static_cast(obj)->Field() : T{}; } static std::string build_armada_created_body(Il2CppObject* data) { std::string result; if (!seh_call([&] { - auto* attack = read_instance_field(data, "ArmadaAttack"); - auto* alliance = read_instance_field(data, "Alliance"); - auto* owner = read_instance_field(attack, "k__BackingField"); - auto* target = read_instance_field(attack, "k__BackingField"); + auto* attack = read_instance_field<"ArmadaAttack", Il2CppObject*>(data); + auto* alliance = read_instance_field<"Alliance", Il2CppObject*>(data); + auto* owner = read_instance_field<"k__BackingField", Il2CppObject*>(attack); + auto* target = read_instance_field<"k__BackingField", Il2CppObject*>(attack); - auto* tag_value = read_instance_field(alliance, "tag_"); - auto* owner_value = read_instance_field(owner, "name_"); - auto* target_value = read_instance_field(target, "name_"); + auto* tag_value = read_instance_field<"tag_", Il2CppString*>(alliance); + auto* owner_value = read_instance_field<"name_", Il2CppString*>(owner); + auto* target_value = read_instance_field<"name_", Il2CppString*>(target); auto tag = tag_value ? to_string(tag_value) : std::string{}; auto owner_name = owner_value ? to_string(owner_value) : std::string{}; auto target_name = target_value ? to_string(target_value) : std::string{}; - auto target_level = read_instance_field(attack, "k__BackingField"); + auto target_level = read_instance_field<"k__BackingField", int32_t>(attack); if (target_name.empty()) { - auto loca_id = read_instance_field(target, "_locaId"); + auto loca_id = read_instance_field<"_locaId", int64_t>(target); if (loca_id != 0) target_name = localize_enemy_name(loca_id); } diff --git a/mods/src/patches/fleet_watch.cc b/mods/src/patches/fleet_watch.cc index d089e3c5a..e4189c28d 100644 --- a/mods/src/patches/fleet_watch.cc +++ b/mods/src/patches/fleet_watch.cc @@ -149,8 +149,8 @@ int occupied_slot_count() void dispatch_transition(int slot, FleetPlayerData* fleet, FleetState before, FleetState after) { const fleet_watch::Transition transition{ - .before = fleet_watch::Snapshot{slot, fleet->Id, before}, - .after = fleet_watch::Snapshot{slot, fleet->Id, after}, + .before = fleet_watch::Snapshot{slot, fleet->Id(), before}, + .after = fleet_watch::Snapshot{slot, fleet->Id(), after}, .fleet = fleet, }; CallbackScope callback_scope; @@ -190,8 +190,8 @@ void observe_fleet(FleetPlayerData* fleet, int requested_slot, bool publish) const auto slot = requested_slot; auto& previous = s_slots[slot]; - const auto fleet_id = fleet->Id; - const auto state = fleet->CurrentState; + const auto fleet_id = fleet->Id(); + const auto state = fleet->CurrentState(); const bool same_fleet = previous.occupied && previous.fleet_id == fleet_id; const bool same_state = same_fleet && previous.state == state; if (!same_fleet && !s_seed_pending) { @@ -260,7 +260,7 @@ ScanResult scan_manager(bool publish, bool only_fast_polling) continue; } const bool occupied = fleets[slot] != nullptr; - const auto fleet_id = occupied ? fleets[slot]->Id : 0; + const auto fleet_id = occupied ? fleets[slot]->Id() : 0; if (s_slots[slot].occupied != occupied || (occupied && s_slots[slot].fleet_id != fleet_id)) { result.topology_changed = true; if (result.changed_slot < 0) { @@ -412,7 +412,7 @@ void Tick() s_manager_probe_slot = (slot + 1) % kFleetSlotCount; auto* fleet = manager->GetFleetPlayerData(slot); const bool occupied = fleet != nullptr; - const auto fleet_id = occupied ? fleet->Id : 0; + const auto fleet_id = occupied ? fleet->Id() : 0; if (s_slots[slot].occupied != occupied || (occupied && s_slots[slot].fleet_id != fleet_id)) { restart_seed(slot, fleet_id); return; diff --git a/mods/src/patches/key.cc b/mods/src/patches/key.cc index 2708cb858..71c3b95d5 100644 --- a/mods/src/patches/key.cc +++ b/mods/src/patches/key.cc @@ -26,7 +26,7 @@ constexpr std::array directionalKeys = { int CurrentInputFrame() { - static auto GetFrameCount = il2cpp_resolve_icall_typed("UnityEngine.Time::get_frameCount()"); + static auto GetFrameCount = il2cpp::ResolveICall("UnityEngine.Time::get_frameCount()"); return GetFrameCount(); } } @@ -295,7 +295,7 @@ bool Key::Down(KeyCode key) EnsureCurrentFrame(); static auto GetKeyDownInt = - il2cpp_resolve_icall_typed("UnityEngine.Input::GetKeyDownInt(UnityEngine.KeyCode)"); + il2cpp::ResolveICall("UnityEngine.Input::GetKeyDownInt(UnityEngine.KeyCode)"); if (cacheKeyDown[(int)key] == 0) { cacheKeyDown[(int)key] = GetKeyDownInt(key) ? 1 : -1; @@ -308,8 +308,7 @@ bool Key::Pressed(KeyCode key) { EnsureCurrentFrame(); - static auto GetKeyInt = - il2cpp_resolve_icall_typed("UnityEngine.Input::GetKeyInt(UnityEngine.KeyCode)"); + static auto GetKeyInt = il2cpp::ResolveICall("UnityEngine.Input::GetKeyInt(UnityEngine.KeyCode)"); if (cacheKeyPressed[(int)key] == 0) { cacheKeyPressed[(int)key] = GetKeyInt(key) ? 1 : -1; @@ -327,15 +326,12 @@ bool Key::IsInputFocused() auto eventSystem = EventSystem::current(); if (eventSystem) { - auto n = eventSystem->currentSelectedGameObject; - try { - if (n) { - auto n2 = n->GetComponentFastPath2(); - if (n2 && n2->isFocused) { - cacheInputFocused = 1; - } + auto n = eventSystem->currentSelectedGameObject(); + if (n) { + auto n2 = n->GetComponent(); + if (n2 && n2->isFocused()) { + cacheInputFocused = 1; } - } catch (...) { } } } @@ -360,17 +356,12 @@ bool Key::HasCtrl() void Key::ClearInputFocus() { - try { - if (auto eventSystem = EventSystem::current(); eventSystem) { - if (auto n = eventSystem->currentSelectedGameObject; n) { - auto n2 = n->GetComponentFastPath2(); - if (n2 && n2->isFocused) { - eventSystem->SetSelectedGameObject(nullptr); - return; - } + if (auto eventSystem = EventSystem::current(); eventSystem) { + if (auto n = eventSystem->currentSelectedGameObject(); n) { + auto n2 = n->GetComponent(); + if (n2 && n2->isFocused()) { + eventSystem->SetSelectedGameObject(nullptr); } } - } catch (...) { - // } } diff --git a/mods/src/patches/notification_service.cc b/mods/src/patches/notification_service.cc index 293424f4c..e4f796d18 100644 --- a/mods/src/patches/notification_service.cc +++ b/mods/src/patches/notification_service.cc @@ -1,12 +1,12 @@ #include "patches/notification_service.h" #include "patches/battle_notify_parser.h" -#include "patches/notification_audio.h" #include "config.h" #include "str_utils.h" -#include +#include #include +#include #include #include @@ -19,12 +19,21 @@ #include #endif -// --------------------------------------------------------------------------- -// IL2CPP method cache -// --------------------------------------------------------------------------- -static const MethodInfo* s_localize_ltc = nullptr; // LanguageManager.Localize(out string, LocaleTextContext) — instance -static const MethodInfo* s_locale_utils_localize = nullptr; // LocaleUtilities.Localize(LocaleTextContext, bool, bool) — static -static const MethodInfo* s_object_tostring = nullptr; +namespace { + +// EventCategories is a struct wrapping one int32 (_flagValue). +struct EventCategories { + int32_t flagValue; +}; + +struct EventModel : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimePlatform.Models", "EventModel">; + + EventCategories category() + { return Field<"category_", EventCategories>(); } +}; + +} // namespace // --------------------------------------------------------------------------- // Toast state → human-readable title @@ -120,33 +129,6 @@ static void show_system_notification(const char* title, const char* body) } #endif -// --------------------------------------------------------------------------- -// Convert an IL2CPP object to string via virtual ToString() -// --------------------------------------------------------------------------- -static std::string il2cpp_object_to_string(Il2CppObject* obj) -{ - if (!obj) return {}; - - // Fast path: if it's a String, convert directly - auto* cls = il2cpp_object_get_class(obj); - if (cls) { - auto* name = il2cpp_class_get_name(cls); - if (name && std::string_view(name) == "String") - return to_string(reinterpret_cast(obj)); - } - - // Call virtual ToString() — resolve Object.ToString once, then dispatch per object - if (!s_object_tostring) return {}; - - auto* vm = il2cpp_object_get_virtual_method(obj, s_object_tostring); - if (!vm) return {}; - - Il2CppException* exc = nullptr; - auto* result = reinterpret_cast(il2cpp_runtime_invoke(vm, obj, nullptr, &exc)); - if (exc || !result) return {}; - return to_string(result); -} - // --------------------------------------------------------------------------- // SEH wrapper — catches access violations from bad IL2CPP pointers // --------------------------------------------------------------------------- @@ -177,11 +159,8 @@ static Il2CppString* seh_to_string_raw(Il2CppObject* obj) if (name && strcmp(name, "String") == 0) return reinterpret_cast(obj); } - if (!s_object_tostring) return nullptr; - auto* vm = il2cpp_object_get_virtual_method(obj, s_object_tostring); - if (!vm) return nullptr; - Il2CppException* exc = nullptr; - return reinterpret_cast(il2cpp_runtime_invoke(vm, obj, nullptr, &exc)); + // Virtual ToString(); boxed value types dispatch to their own override. + return static_cast(obj)->Call<"ToString", Il2CppString*>(); } static std::string safe_il2cpp_to_string(Il2CppObject* obj, il2cpp_array_size_t index) @@ -198,16 +177,16 @@ static std::string safe_il2cpp_to_string(Il2CppObject* obj, il2cpp_array_size_t // --------------------------------------------------------------------------- // Replace {N} placeholders in template with values from an IL2CPP object[] // --------------------------------------------------------------------------- -static std::string format_with_array(const std::string& tmpl, Il2CppArray* arr) +static std::string format_with_array(const std::string& tmpl, il2cpp::Array* arr) { if (!arr) return tmpl; - auto len = static_cast(reinterpret_cast(arr)->max_length); + auto len = arr->size(); if (len == 0) return tmpl; std::string result = tmpl; for (il2cpp_array_size_t i = 0; i < len; ++i) { - auto* elem = reinterpret_cast(reinterpret_cast(arr)->vector)[i]; + auto* elem = (*arr)[i]; if (!elem) continue; auto val = safe_il2cpp_to_string(elem, i); @@ -238,7 +217,8 @@ static std::string format_with_array(const std::string& tmpl, Il2CppArray* arr) // --------------------------------------------------------------------------- // Try every instance array field on an object. Toast parameter arrays have // moved and changed names between game builds, including generated backing -// fields, so runtime metadata is more reliable than names or offsets. +// fields, so runtime metadata is more reliable than names or offsets. This +// scan stays on the raw field API because no member name is known up front. // --------------------------------------------------------------------------- static std::string format_with_array_fields(const std::string& tmpl, Il2CppObject* obj, std::string& fields_seen) { @@ -248,20 +228,20 @@ static std::string format_with_array_fields(const std::string& tmpl, Il2CppObjec for (auto* cls = il2cpp_object_get_class(obj); cls; cls = il2cpp_class_get_parent(cls)) { void* iter = nullptr; while (auto* field = il2cpp_class_get_fields(cls, &iter)) { - if ((il2cpp_field_get_flags(field) & 0x10) != 0) continue; // FieldAttributes.Static + if ((il2cpp_field_get_flags(field) & FIELD_ATTRIBUTE_STATIC) != 0) continue; auto* type = il2cpp_field_get_type(field); auto kind = type ? il2cpp_type_get_type(type) : IL2CPP_TYPE_END; if (kind != IL2CPP_TYPE_SZARRAY && kind != IL2CPP_TYPE_ARRAY) continue; - Il2CppArray* arr = nullptr; + il2cpp::Array* arr = nullptr; il2cpp_field_get_value(obj, field, &arr); if (!fields_seen.empty()) fields_seen += ", "; fields_seen += il2cpp_field_get_name(field); - fields_seen += arr ? "[" + std::to_string(reinterpret_cast(arr)->max_length) + "]" : "[null]"; + fields_seen += arr ? "[" + std::to_string(arr->size()) + "]" : "[null]"; - if (!arr || reinterpret_cast(arr)->max_length == 0) continue; + if (!arr || arr->size() == 0) continue; auto candidate = format_with_array(tmpl, arr); if (std::ranges::count(candidate, '{') < std::ranges::count(result, '{')) result = std::move(candidate); @@ -274,22 +254,19 @@ static std::string format_with_array_fields(const std::string& tmpl, Il2CppObjec // Replace {N} placeholders using Toast.TextParameters, falling back to // LocaleTextContext._textParameters if the Toast array is empty. // --------------------------------------------------------------------------- -static std::string format_placeholders(const std::string& tmpl, Toast* toast, void* ltc) +static std::string format_placeholders(const std::string& tmpl, Toast* toast, LocaleTextContext* ltc) { // Try Toast.TextParameters first. auto* toast_arr = toast->get_TextParameters(); - if (toast_arr) { - auto len = static_cast(reinterpret_cast(toast_arr)->max_length); - if (len > 0) { - return format_with_array(tmpl, toast_arr); - } + if (toast_arr && toast_arr->size() > 0) { + return format_with_array(tmpl, toast_arr); } std::string fields_seen; - auto result = format_with_array_fields(tmpl, reinterpret_cast(toast), fields_seen); + auto result = format_with_array_fields(tmpl, toast, fields_seen); if (result != tmpl) return result; - result = format_with_array_fields(tmpl, reinterpret_cast(ltc), fields_seen); + result = format_with_array_fields(tmpl, ltc, fields_seen); if (result != tmpl) return result; if (tmpl.find('{') != std::string::npos) { @@ -304,7 +281,7 @@ static std::string format_placeholders(const std::string& tmpl, Toast* toast, vo // --------------------------------------------------------------------------- static std::string resolve_toast_text(Toast* toast) { - auto* ltc = toast->get_TextLocaleTextContext(); + auto* ltc = static_cast(toast->get_TextLocaleTextContext()); if (!ltc) { spdlog::debug("[Notify] No TextLocaleTextContext for toast state {}", toast->get_State()); return {}; @@ -312,65 +289,35 @@ static std::string resolve_toast_text(Toast* toast) // Try the static LocaleUtilities.Localize(LTC, bool, bool) first — it handles // parameter substitution internally and returns a fully formatted string. - if (s_locale_utils_localize) { - void* params[3] = { ltc, nullptr, nullptr }; - // localizeTextParams = true, invariantCulture = false - bool localizeTextParams = true; - bool invariantCulture = false; - params[1] = &localizeTextParams; - params[2] = &invariantCulture; - - Il2CppException* exc = nullptr; - auto* ret = il2cpp_runtime_invoke(s_locale_utils_localize, nullptr, params, &exc); - - if (exc) { - spdlog::warn("[Notify] LocaleUtilities.Localize threw exception"); - } else if (ret) { - auto result = to_string(reinterpret_cast(ret)); - // LocaleUtilities.Localize may return a template with unresolved {N} - // placeholders if the LTC's own _textParameters are empty. The actual - // parameter values may be in Toast.TextParameters, so run - // format_placeholders to substitute them. - if (result.find('{') != std::string::npos) - result = format_placeholders(result, toast, ltc); - return result; - } else { - spdlog::debug("[Notify] LocaleUtilities.Localize returned null for toast state {}", toast->get_State()); - } + if (auto* ret = LocaleUtilities::Localize(ltc, true, false)) { + auto result = to_string(ret); + // LocaleUtilities.Localize may return a template with unresolved {N} + // placeholders if the LTC's own _textParameters are empty. The actual + // parameter values may be in Toast.TextParameters, so run + // format_placeholders to substitute them. + if (result.find('{') != std::string::npos) + result = format_placeholders(result, toast, ltc); + return result; } + spdlog::debug("[Notify] LocaleUtilities.Localize returned null for toast state {}", toast->get_State()); // Fall back to instance LanguageManager.Localize(out string, LTC) - if (s_localize_ltc) { - auto* langMgr = LanguageManager::Instance(); - if (!langMgr) return {}; - - Il2CppString* resolved = nullptr; - void* params[2] = { &resolved, ltc }; - Il2CppException* exc = nullptr; - auto* ret = il2cpp_runtime_invoke(s_localize_ltc, langMgr, params, &exc); - - if (exc) { - spdlog::warn("[Notify] LanguageManager.Localize threw exception"); - return {}; - } - - bool success = ret ? (*static_cast(il2cpp_object_unbox(ret))) : false; - if (!success || !resolved) { - spdlog::debug("[Notify] LanguageManager.Localize returned false for toast state {}", toast->get_State()); - return {}; - } + auto* langMgr = LanguageManager::Instance(); + if (!langMgr) return {}; - auto tmpl = to_string(resolved); - return format_placeholders(tmpl, toast, ltc); + Il2CppString* resolved = nullptr; + if (!langMgr->Localize(resolved, ltc) || !resolved) { + spdlog::debug("[Notify] LanguageManager.Localize returned false for toast state {}", toast->get_State()); + return {}; } - return {}; + auto tmpl = to_string(resolved); + return format_placeholders(tmpl, toast, ltc); } // --------------------------------------------------------------------------- -// Resolve the event category from Toast.Data (EventModel) and map it to a -// human-readable name. EventModel.category_ is at offset 0x1E8 and holds an -// EventCategories struct whose sole field (_flagValue) is an int32. +// Resolve the event category from Toast.Data (EventModel.category_) and map +// it to a human-readable name. // --------------------------------------------------------------------------- static const char* event_category_name(int32_t cat) { @@ -411,19 +358,11 @@ static const char* event_category_name(int32_t cat) static std::string resolve_event_category(Toast* toast) { auto* data = toast->get_Data(); - if (!data) return {}; - - std::string result; - if (!seh_call([&] { - // EventModel.category_ is at offset 0x1E8 (int32 _flagValue) - auto cat = *reinterpret_cast(reinterpret_cast(data) + 0x1E8); - auto* name = event_category_name(cat); - if (name) result = name; - })) { - spdlog::warn("[Notify] SEH: reading EventModel.category_ crashed"); - } + auto* event = data ? static_cast(data)->As() : nullptr; + if (!event) return {}; - return result; + auto* name = event_category_name(event->category().flagValue); + return name ? name : std::string{}; } // --------------------------------------------------------------------------- @@ -450,57 +389,8 @@ static std::string strip_unity_rich_text(const std::string& s) void notification_init() { + // IL2CPP members resolve on first use; only the platform side needs setup. #if _WIN32 - // Resolve LanguageManager::Localize(out string, LocaleTextContext) — the - // 2-parameter overload that takes an LTC and returns a localized string. - auto lm_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Localization", "LanguageManager"); - if (lm_helper.isValidHelper()) { - auto* cls = lm_helper.get_cls(); - if (cls) { - void* iter = nullptr; - while (auto* method = il2cpp_class_get_methods(cls, &iter)) { - auto name = std::string_view(il2cpp_method_get_name(method)); - auto pc = il2cpp_method_get_param_count(method); - if (name == "Localize" && pc == 2) { - s_localize_ltc = method; - break; - } - } - } - } - - if (!s_localize_ltc) { - spdlog::warn("[Notify] Could not resolve LanguageManager::Localize — falling back to LocaleUtilities"); - } - - // Resolve LocaleUtilities::Localize(LocaleTextContext, bool, bool) — static method - // that handles parameter substitution internally and returns a fully formatted string. - auto lu_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Localization", "LocaleUtilities"); - if (lu_helper.isValidHelper()) { - auto* cls = lu_helper.get_cls(); - if (cls) { - void* iter = nullptr; - while (auto* method = il2cpp_class_get_methods(cls, &iter)) { - auto name = std::string_view(il2cpp_method_get_name(method)); - auto pc = il2cpp_method_get_param_count(method); - if (name == "Localize" && pc == 3) { - s_locale_utils_localize = method; - break; - } - } - } - } - - if (!s_locale_utils_localize) { - spdlog::warn("[Notify] Could not resolve LocaleUtilities::Localize — will use LanguageManager fallback"); - } - - // Resolve System.Object::ToString() for converting IL2CPP objects to strings - s_object_tostring = il2cpp_get_class_helper("mscorlib", "System", "Object").GetMethodInfo("ToString", 0); - if (!s_object_tostring) { - spdlog::warn("[Notify] Could not resolve Object::ToString — placeholder formatting may be incomplete"); - } - try { winrt::init_apartment(winrt::apartment_type::single_threaded); spdlog::info("[Notify] Windows notification service initialized"); @@ -510,8 +400,6 @@ void notification_init() } catch (...) { spdlog::warn("[Notify] Windows notification service failed (unknown error)"); } -#elif __APPLE__ - spdlog::info("[Notify] macOS audio notification service ready"); #else spdlog::info("[Notify] Notification service: platform not supported (no-op)"); #endif @@ -519,16 +407,16 @@ void notification_init() void notification_handle_toast(Toast* toast) { - if (!toast) return; - - const auto& config = Config::Get(); - const auto state = toast->get_State(); - notification_audio_play(config.NotificationSoundForToast(state)); - -#if _WIN32 + if (!toast) + return; + notification_audio_play(Config::Get().NotificationSoundForToast(static_cast(toast->get_State()))); +#if !_WIN32 + return; // No notification delivery on non-Windows platforms yet +#else + auto state = toast->get_State(); // Check if this toast type is in the user's notify list - const auto& notify_types = config.notify_banner_types; + const auto& notify_types = Config::Get().notify_banner_types; if (std::ranges::find(notify_types, state) == notify_types.end()) { return; } diff --git a/mods/src/patches/notification_service.h b/mods/src/patches/notification_service.h index 2ebad087a..f848c5492 100644 --- a/mods/src/patches/notification_service.h +++ b/mods/src/patches/notification_service.h @@ -2,7 +2,7 @@ struct Toast; -// Initialize the notification service (resolve IL2CPP methods, init platform). +// Initialize the notification platform (WinRT on Windows). // Call once during InstallToastBannerHooks(). void notification_init(); diff --git a/mods/src/patches/object_tracker.h b/mods/src/patches/object_tracker.h new file mode 100644 index 000000000..5db105514 --- /dev/null +++ b/mods/src/patches/object_tracker.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include + +// Game objects registered by the mod's Unity lifecycle hooks. These are borrowed pointers. +extern eastl::unordered_map> tracked_objects; + +template class ObjectFinder +{ +public: + static T* Get() + { + auto& objects = tracked_objects[T::Class::Raw()]; + if (objects.empty()) { + // TODO: assert? + return nullptr; + } + return reinterpret_cast(objects.back()); + } + + static eastl::span GetAll() + { + auto& objects = tracked_objects[T::Class::Raw()]; + return {reinterpret_cast(objects.data()), reinterpret_cast(objects.data()) + objects.size()}; + } +}; diff --git a/mods/src/patches/parts/alliance_store.cc b/mods/src/patches/parts/alliance_store.cc index ba6393df2..cdcec5867 100644 --- a/mods/src/patches/parts/alliance_store.cc +++ b/mods/src/patches/parts/alliance_store.cc @@ -1,9 +1,18 @@ // Alliance Games store: native material research tooltips and currency refresh // after bundle readiness. One feature owner, no claim-trace or file probe. -#include "patches/alliance_store_bindings.h" #include "patches/native_hook_extent.h" #include "patches/screen_update_hook.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + #include #if _WIN32 #include @@ -11,59 +20,328 @@ #include #include +#include #include #include +#include +#include #include +#include namespace { -namespace store_game = alliance_store_game; -using Clock = std::chrono::steady_clock; -using StoreInventoryTooltip = Il2CppObject; -using StoreTooltipTrigger = Il2CppObject; -using StoreTooltipAction = Il2CppObject; -using StoreActionLocale = Il2CppObject; -using StoreActionLocales = Il2CppObject; -using AllianceStoreScroller = Il2CppObject; -using InventoryItem = Il2CppObject; -using AllianceStoreContext = Il2CppObject; -using ShopListScrollerViewController = Il2CppObject; -using BundleDataWidget = Il2CppObject; -using Bundle = Il2CppObject; +using Clock = std::chrono::steady_clock; + +struct UnityObject { + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Object">; +}; +struct UnityResources { + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Resources">; +}; + +// Digit.Client.Serialization.OverridableBool. +struct OverridableBool { + bool serialized, current, modified; +}; +static_assert(sizeof(OverridableBool) == 3); + +struct ActionLocaleSetting : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Actions", "ActionLocaleSetting">; +}; +struct ActionLocaleSettingsList : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Actions", "ActionLocaleSettingsList">; +}; +struct TooltipLoadAndShow : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Tooltip", "TooltipLoadAndShow">; +}; + +// A MonoBehaviour; the appearance fields are copied onto an action trigger that replaces it. +struct TooltipTrigger : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Tooltip", "TooltipTrigger">; + + OverridableBool showHeader() + { return Field<"_showHeader", OverridableBool>(); } + LocaleTextContext* header() + { return Field<"_header", LocaleTextContext*>(); } + OverridableBool showDescription() + { return Field<"_showDescription", OverridableBool>(); } + LocaleTextContext* description() + { return Field<"_description", LocaleTextContext*>(); } + bool overrideLocale() + { return Field<"_overrideLocale", bool>(); } + // Digit.Client.Tooltip.TooltipAnchor; -1 is Undefined. + int32_t anchorOverride() + { return Field<"_anchorOverride", int32_t>(); } + Vector2 sizeOverride() + { return Field<"_sizeOverride", Vector2>(); } + Transform* positionOverrideTarget() + { return Field<"_positionOverrideTarget", Transform*>(); } + Vector2 positionOverrideOffset() + { return Field<"_positionOverrideOffset", Vector2>(); } + bool isInteractable() + { return Field<"_isInteractable", bool>(); } + Il2CppString* descriptionOverrideText() + { return Field<"k__BackingField", Il2CppString*>(); } + TooltipLoadAndShow* loader() + { return Field<"_loader", TooltipLoadAndShow*>(); } + + // Behaviour.enabled, distinguishing a failed call from the value. + std::optional Enabled() + { return TryCall<"get_enabled", bool()>(); } + bool SetEnabled(bool value) + { return TryCall<"set_enabled", void(bool)>(value); } +}; + +struct TooltipActionTrigger : TooltipTrigger { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Tooltip", "TooltipActionTrigger">; + + void CopyAppearance(TooltipTrigger* plain) + { + SetField<"_showHeader">(plain->showHeader()); + SetField<"_header">(plain->header()); + SetField<"_showDescription">(plain->showDescription()); + SetField<"_description">(plain->description()); + SetField<"_overrideLocale">(plain->overrideLocale()); + SetField<"_anchorOverride">(plain->anchorOverride()); + SetField<"_sizeOverride">(plain->sizeOverride()); + SetField<"_positionOverrideTarget">(plain->positionOverrideTarget()); + SetField<"_positionOverrideOffset">(plain->positionOverrideOffset()); + SetField<"_isInteractable">(plain->isInteractable()); + SetField<"k__BackingField">(plain->descriptionOverrideText()); + SetField<"_loader">(plain->loader()); + } +}; + +// The tooltip on a shop card. Its context is the card's inventory item. +struct InventoryTooltipTriggerWidget : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Inventories", "InventoryTooltipTriggerWidget">; + + il2cpp::Object* Context() + { return Get<"Context", il2cpp::Object*>(); } + GameObject* gameObject() + { return Get<"gameObject", GameObject*>(); } + + TooltipTrigger* tooltipTrigger() + { return Field<"_tooltipTrigger", TooltipTrigger*>(); } + void SetTooltipTrigger(TooltipTrigger* value) + { SetField<"_tooltipTrigger">(value); } + TooltipActionTrigger* tooltipActionTrigger() + { return Field<"_tooltipActionTrigger", TooltipActionTrigger*>(); } + void SetTooltipActionTrigger(TooltipActionTrigger* value) + { SetField<"_tooltipActionTrigger">(value); } + ActionLocaleSetting* searchActionLocaleSetting() + { return Field<"_searchActionLocaleSetting", ActionLocaleSetting*>(); } + void SetSearchActionLocaleSetting(ActionLocaleSetting* value) + { SetField<"_searchActionLocaleSetting">(value); } + ActionLocaleSetting* actionLocaleSetting() + { return Field<"_actionLocaleSetting", ActionLocaleSetting*>(); } + void SetActionLocaleSetting(ActionLocaleSetting* value) + { SetField<"_actionLocaleSetting">(value); } + ActionLocaleSettingsList* settings() + { return Field<"_settings", ActionLocaleSettingsList*>(); } + void SetSettings(ActionLocaleSettingsList* value) + { SetField<"_settings">(value); } + + bool UpdateTooltipDescription() + { return TryCall<"UpdateTooltipDescription", void()>(); } + bool SetupActionTooltipTrigger() + { return TryCall<"SetupActionTooltipTrigger", void()>(); } +}; + +// Digit.Client.UI.SmartScrollerBase, the store's card pool. +struct SmartScrollerBase : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "SmartScrollerBase">; + + il2cpp::List* spawnedItems() + { return Field<"_spawnedItems", il2cpp::List*>(); } + bool regeneratingContent() + { return Field<"_regeneratingContent", bool>(); } + il2cpp::Object* spawnerCoroutine() + { return Field<"_spawnerCoroutine", il2cpp::Object*>(); } + il2cpp::Object* dataHeldWhilstInitializing() + { return Field<"_dataHeldWhilstInitializing", il2cpp::Object*>(); } + + std::optional NormalizedPosition() + { return TryCall<"get_NormalizedPosition", Vector2()>(); } + bool OnValueChangedCallback(Vector2 value) + { return TryCall<"OnValueChangedCallback", void(Vector2)>(value); } + std::optional VisibleItemsCount() + { return TryCall<"get_VisibleItemsCount", int32_t()>(); } + std::optional ScrollPosition() + { return TryCall<"get_ScrollPosition", float()>(); } +}; + +struct ShopListScrollerViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Shop", "ShopListScrollerViewController">; + + // Declared as some scroller; only a SmartScrollerBase is used. + il2cpp::Object* smartScroller() + { return Field<"_smartScroller", il2cpp::Object*>(); } +}; + +// The store's context; its members resolve on the runtime class. +struct AllianceGamesStoreContext : il2cpp::Object { + bool IsShopViewActive() + { return Get<"IsShopViewActive", bool>(); } + std::optional IsFetchingBundlesInProgress() + { return TryCall<"get_IsFetchingBundlesInProgress", bool()>(); } + int32_t BundlesCount() + { return Get<"BundlesCount", int32_t>(); } + il2cpp::Object* StoreCurrency() + { return Get<"StoreCurrency", il2cpp::Object*>(); } + bool UpdateStoreCurrency() + { return TryCall<"UpdateStoreCurrency", void()>(); } +}; + +struct AllianceGamesStoreScrollerViewController : ShopListScrollerViewController { + using Class = + il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Alliances.Games", "AllianceGamesStoreScrollerViewController">; + + AllianceGamesStoreContext* AllianceGamesContext() + { return Get<"AllianceGamesContext", AllianceGamesStoreContext*>(); } +}; + +InventoryTooltipTriggerWidget* BundleTooltip(BundleDataWidget* card) +{ return card->Field<"_invTooltip", InventoryTooltipTriggerWidget*>(); } +ShopListScrollerViewController* BundleOwner(BundleDataWidget* card) +{ return card->Field<"_scrollerController", ShopListScrollerViewController*>(); } + +// Upstream refuses to install when the native field contract changed: the hooks write these fields, and a missing +// one would read as zero. +template bool StoreHasField() +{ return il2cpp::detail::ResolveField(Binding::Class::Raw(), N.View(), il2cpp::detail::SlotOf(), false); } + +bool StoreFieldsResolve() +{ + using Widget = InventoryTooltipTriggerWidget; + using Trigger = TooltipTrigger; + using Scroller = SmartScrollerBase; + bool ok = true; + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasFieldk__BackingField", Il2CppString*>(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField*>(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + ok &= StoreHasField(); + return ok; +} + +// Unity's bool(Object): false for null and for destroyed native objects. +bool Alive(il2cpp::Object* object) +{ return object && UnityObject::Class::Call<"op_Implicit", bool>(object); } + +bool IsStore(il2cpp::Object* object) +{ return object && object->Is(); } + +// Native research eligibility of the card's resource. Empty when it cannot be determined. +std::optional Eligible(il2cpp::Object* item, il2cpp::GcRoots& roots) +{ + auto* spec = roots.Keep(item->Get<"ResourceSpec", il2cpp::Object*>()); + auto id = spec ? spec->Get<"Id", int64_t>() : 0; + if (id <= 0) + return std::nullopt; + auto* manager = roots.Keep(Hub::Class::Get<"ResearchManager", il2cpp::Object*>()); + // Digit.PrimeServer.Models.ResearchTreeType; 0 is the main tree. + enum class ResearchTreeType : int32_t {}; + return manager ? manager->TryCall<"IsResourceRequiredByVisibleResearches", bool(int64_t, ResearchTreeType)>( + id, ResearchTreeType{}) + : std::nullopt; +} + +std::optional CurrencyCount(AllianceGamesStoreContext* context, il2cpp::GcRoots& roots) +{ + auto* currencies = roots.Keep(context->StoreCurrency()); + auto* soft = currencies ? roots.Keep(currencies->Get<"SoftResources", il2cpp::List*>()) : nullptr; + return soft ? soft->TryCall<"get_Count", int32_t()>() : std::nullopt; +} + +// The action trigger on the widget's GameObject; with `create`, added when there is none. Null when the lookup +// failed, so a failed lookup never adds a second component. +TooltipActionTrigger* ActionComponent(InventoryTooltipTriggerWidget* widget, bool create, il2cpp::GcRoots& roots) +{ + auto* object = roots.Keep(widget->gameObject()); + auto* type = object ? roots.Keep(TooltipActionTrigger::Class::TypeObject()) : nullptr; + if (!type) + return nullptr; + const auto found = object->TryCall<"GetComponent", TooltipActionTrigger*(Il2CppReflectionType*)>(type); + if (!found) + return nullptr; + if (*found) + return roots.Keep(*found); + return create ? roots.Keep(object->AddComponent()) : nullptr; +} + +// The widget's own plain trigger, not an action trigger. +TooltipTrigger* PlainComponent(InventoryTooltipTriggerWidget* widget, il2cpp::GcRoots& roots) +{ + auto* object = roots.Keep(widget->gameObject()); + auto* value = object ? roots.Keep(object->GetComponent()) : nullptr; + return value && value->klass == TooltipTrigger::Class::Raw() ? value : nullptr; +} + +// A loaded tooltip widget that carries native research actions, whose locale settings the store cards borrow. +InventoryTooltipTriggerWidget* Template(il2cpp::GcRoots& roots) +{ + auto* type = roots.Keep(InventoryTooltipTriggerWidget::Class::TypeObject()); + auto* found = type ? roots.Keep(UnityResources::Class::Call<"FindObjectsOfTypeAll", il2cpp::Array*>( + type)) + : nullptr; + if (!found) + return nullptr; + for (auto* object : *found) { + auto* widget = object ? object->As() : nullptr; + if (widget && widget->tooltipActionTrigger() && widget->searchActionLocaleSetting()) + return roots.Keep(widget); + } + return nullptr; +} + bool alliance_store_active = false; std::thread::id alliance_store_thread; bool StoreThread() { return alliance_store_active && std::this_thread::get_id() == alliance_store_thread; } struct AllianceStoreWork { - store_game::Handle source; - uint64_t epoch = 0; - bool pending = false, currency = false, refreshing_currency = false, layout = false; - Clock::time_point due{}; + il2cpp::GcHandle source; + uint64_t epoch = 0; + bool pending = false, currency = false, refreshing_currency = false, layout = false; + Clock::time_point due{}; } alliance_store_work; enum class ResearchUpdate { Unchanged, Upgraded, Restored, Unavailable, Failed }; -bool SameStoreItem(StoreInventoryTooltip* tooltip, InventoryItem* item) -{ - const auto alive = store_game::Alive(tooltip); - if (!alive || !alive.value) - return false; - const auto current = store_game::Call(tooltip, "get_Context"); - return current && current.value == item; -} +bool SameStoreItem(InventoryTooltipTriggerWidget* tooltip, il2cpp::Object* item) +{ return Alive(tooltip) && tooltip->Context() == item; } // Weak component identity survives pooled reuse without retaining a scene. Only // the original serialized assets stay rooted while our replacement is bound. struct OwnedStoreTooltip { - store_game::Handle widget, plain, action, store; - store_game::Handle search, locale, settings; - bool bound = false, plain_enabled = true, retiring = false; - uint64_t generation = 0; + il2cpp::GcHandle widget, plain, action, store; + il2cpp::GcHandle search, locale, settings; + bool bound = false, plain_enabled = true, retiring = false; + uint64_t generation = 0; }; std::vector> alliance_store_tooltips; -std::shared_ptr OwnedTooltip(StoreInventoryTooltip* tooltip) +std::shared_ptr OwnedTooltip(InventoryTooltipTriggerWidget* tooltip) { for (auto& entry : alliance_store_tooltips) if (entry->widget.Get() == tooltip) @@ -71,6 +349,14 @@ std::shared_ptr OwnedTooltip(StoreInventoryTooltip* tooltip) return nullptr; } +void ReleaseStoreAssets(OwnedStoreTooltip& entry) +{ + entry.bound = false; + entry.search.Reset(); + entry.locale.Reset(); + entry.settings.Reset(); +} + bool RestoreStoreTooltip(OwnedStoreTooltip& entry) { if (!entry.bound) @@ -84,111 +370,95 @@ bool RestoreStoreTooltip(OwnedStoreTooltip& entry) ~ClearRetiring() { value = false; } } clear{entry.retiring}; - store_game::Roots roots; - auto* widget = roots.Keep(entry.widget.Get()); - auto* plain = roots.Keep(entry.plain.Get()); - auto* action = roots.Keep(entry.action.Get()); - const auto alive = store_game::Alive(widget); - if (!roots.healthy || !widget || !plain || !action || !alive) + il2cpp::GcRoots roots; + auto* widget = roots.Keep(entry.widget.Get()); + auto* plain = roots.Keep(entry.plain.Get()); + auto* action = roots.Keep(entry.action.Get()); + if (!roots.healthy() || !widget || !plain || !action) return false; - if (!alive.value) { - entry.bound = false; - entry.search.Reset(); - entry.locale.Reset(); - entry.settings.Reset(); + if (!Alive(widget)) { + ReleaseStoreAssets(entry); return true; } - auto& f = store_game::Members(); - if ((f.trigger.Read(widget) != plain && f.trigger.Read(widget) != action) - || (f.action.Read(widget) && f.action.Read(widget) != action)) { + auto* trigger = widget->tooltipTrigger(); + if ((trigger != plain && trigger != action) + || (widget->tooltipActionTrigger() && widget->tooltipActionTrigger() != action)) { // A new native owner replaced our arrangement. Disable only our component; // never overwrite the replacement owner's fields or enabled state. - (void)store_game::Enabled(action, false); - entry.bound = false; - entry.search.Reset(); - entry.locale.Reset(); - entry.settings.Reset(); + (void)action->SetEnabled(false); + ReleaseStoreAssets(entry); return true; } - bool ok = store_game::Enabled(action, false); - ok &= f.trigger.Write(widget, plain); - ok &= f.action.Write(widget, nullptr); - ok &= f.search.Write(widget, entry.search.Get()); - ok &= f.locale.Write(widget, entry.locale.Get()); - ok &= f.settings.Write(widget, entry.settings.Get()); - ok &= store_game::Call(widget, "UpdateTooltipDescription").succeeded; - ok &= store_game::Enabled(plain, entry.plain_enabled); - if (ok) { - entry.bound = false; - entry.search.Reset(); - entry.locale.Reset(); - entry.settings.Reset(); - } + bool ok = action->SetEnabled(false); + widget->SetTooltipTrigger(plain); + widget->SetTooltipActionTrigger(nullptr); + widget->SetSearchActionLocaleSetting(entry.search.Get()); + widget->SetActionLocaleSetting(entry.locale.Get()); + widget->SetSettings(entry.settings.Get()); + ok &= widget->UpdateTooltipDescription(); + ok &= plain->SetEnabled(entry.plain_enabled); + if (ok) + ReleaseStoreAssets(entry); return ok; } -ResearchUpdate ReconcileStoreTooltip(StoreInventoryTooltip* tooltip, StoreInventoryTooltip*& donor, - store_game::Roots& pass_roots) +ResearchUpdate ReconcileStoreTooltip(InventoryTooltipTriggerWidget* tooltip, InventoryTooltipTriggerWidget*& donor, + il2cpp::GcRoots& pass_roots) { - store_game::Roots roots; + il2cpp::GcRoots roots; if (!roots.Keep(tooltip)) return ResearchUpdate::Unavailable; const auto epoch = alliance_store_work.epoch; - auto* store = roots.Keep(alliance_store_work.source.Get()); - const auto alive = store_game::Alive(tooltip); - const auto observed = alive && alive.value ? store_game::Call(tooltip, "get_Context") - : store_game::Result{}; - auto* item = observed ? roots.Keep(observed.value) : nullptr; + auto* store = roots.Keep(alliance_store_work.source.Get()); + auto* item = Alive(tooltip) ? roots.Keep(tooltip->Context()) : nullptr; auto same_item = [&] { if (!store || alliance_store_work.epoch != epoch || alliance_store_work.source.Get() != store) return false; - const auto live_store = store_game::Alive(store); - return live_store && live_store.value && SameStoreItem(tooltip, item) && alliance_store_work.epoch == epoch + return Alive(store) && SameStoreItem(tooltip, item) && alliance_store_work.epoch == epoch && alliance_store_work.source.Get() == store; }; - auto* plain = item ? store_game::PlainComponent(tooltip, roots) : nullptr; + auto* plain = item ? PlainComponent(tooltip, roots) : nullptr; if (!plain) return ResearchUpdate::Unchanged; - auto& f = store_game::Members(); - auto* old_action = roots.Keep(f.action.Read(tooltip)); - auto* old_trigger = roots.Keep(f.trigger.Read(tooltip)); + auto* old_action = roots.Keep(tooltip->tooltipActionTrigger()); + auto* old_trigger = roots.Keep(tooltip->tooltipTrigger()); auto owned = OwnedTooltip(tooltip); // Existing native action components and unrelated arrangements remain native. - if (!roots.healthy || (old_action && (!owned || owned->action.Get() != old_action)) + if (!roots.healthy() || (old_action && (!owned || owned->action.Get() != old_action)) || (old_action ? old_trigger != old_action : old_trigger != plain)) return ResearchUpdate::Unchanged; - const auto eligible = store_game::Eligible(item); + const auto eligible = Eligible(item, roots); if (!eligible || !same_item()) return ResearchUpdate::Unavailable; - if (!eligible.value) + if (!*eligible) return owned && owned->bound ? (RestoreStoreTooltip(*owned) ? ResearchUpdate::Restored : ResearchUpdate::Failed) : ResearchUpdate::Unchanged; if (!donor) - donor = store_game::Template(pass_roots); + donor = Template(pass_roots); if (!donor) return ResearchUpdate::Unavailable; - auto* search = roots.Keep(f.search.Read(donor)); - auto* locale = roots.Keep(f.locale.Read(donor)); - auto* settings = roots.Keep(f.settings.Read(donor)); - auto* loader = roots.Keep(f.loader.Read(plain)); - const auto plain_enabled = store_game::Call(plain, "get_enabled"); - if (!roots.healthy || !search || !loader || !plain_enabled || !same_item()) + auto* search = roots.Keep(donor->searchActionLocaleSetting()); + auto* locale = roots.Keep(donor->actionLocaleSetting()); + auto* settings = roots.Keep(donor->settings()); + auto* loader = roots.Keep(plain->loader()); + const auto plain_enabled = plain->Enabled(); + if (!roots.healthy() || !search || !loader || !plain_enabled || !same_item()) return ResearchUpdate::Unavailable; - auto* action = store_game::ActionComponent(tooltip, false, roots); + auto* action = ActionComponent(tooltip, false, roots); // Reuse only the disabled component this feature previously added. if (action && (!owned || owned->action.Get() != action)) return ResearchUpdate::Unchanged; if (!action) - action = store_game::ActionComponent(tooltip, true, roots); + action = ActionComponent(tooltip, true, roots); if (!action) return ResearchUpdate::Unavailable; if (!owned) { auto entry = std::make_shared(); - entry->widget = store_game::Handle::Weak(tooltip); - entry->plain = store_game::Handle::Weak(plain); - entry->action = store_game::Handle::Weak(action); - if (!entry->widget || !entry->plain || !entry->action) { - store_game::Enabled(action, false); + entry->widget = il2cpp::GcHandle::Weak(tooltip); + entry->plain = il2cpp::GcHandle::Weak(plain); + entry->action = il2cpp::GcHandle::Weak(action); + if (entry->widget.Get() != tooltip || entry->plain.Get() != plain || entry->action.Get() != action) { + (void)action->SetEnabled(false); return ResearchUpdate::Unavailable; } alliance_store_tooltips.push_back(std::move(entry)); @@ -196,27 +466,23 @@ ResearchUpdate ReconcileStoreTooltip(StoreInventoryTooltip* tooltip, StoreInvent } const bool previously_bound = owned->bound; if (!owned->bound) { - auto retain = [](T* value, store_game::Handle& handle) { - handle = store_game::Handle::Strong(value); - return !value || (handle && handle.Get() == value); + auto retain = [](il2cpp::Object* value, il2cpp::GcHandle& handle) { + handle = il2cpp::GcHandle::Strong(value); + return !value || handle.Get() == value; }; - if (!retain(f.search.Read(tooltip), owned->search) || !retain(f.locale.Read(tooltip), owned->locale) - || !retain(f.settings.Read(tooltip), owned->settings)) { - owned->search.Reset(); - owned->locale.Reset(); - owned->settings.Reset(); - store_game::Enabled(action, false); + if (!retain(tooltip->searchActionLocaleSetting(), owned->search) + || !retain(tooltip->actionLocaleSetting(), owned->locale) || !retain(tooltip->settings(), owned->settings)) { + ReleaseStoreAssets(*owned); + (void)action->SetEnabled(false); return ResearchUpdate::Unavailable; } - owned->store = store_game::Handle::Weak(store); - if (!owned->store || owned->store.Get() != store) { - owned->search.Reset(); - owned->locale.Reset(); - owned->settings.Reset(); - store_game::Enabled(action, false); + owned->store = il2cpp::GcHandle::Weak(store); + if (owned->store.Get() != store) { + ReleaseStoreAssets(*owned); + (void)action->SetEnabled(false); return ResearchUpdate::Unavailable; } - owned->plain_enabled = plain_enabled.value; + owned->plain_enabled = *plain_enabled; owned->bound = true; } const auto generation = ++owned->generation; @@ -231,30 +497,35 @@ ResearchUpdate ReconcileStoreTooltip(StoreInventoryTooltip* tooltip, StoreInvent } } rollback{owned}; - if (!store_game::Enabled(action, false) || !store_game::CopyAppearance(plain, action, roots) || !current() - || !f.search.Write(tooltip, search) || !f.locale.Write(tooltip, locale) || !f.settings.Write(tooltip, settings) - || !f.trigger.Write(tooltip, action) || !f.action.Write(tooltip, action) - || !store_game::Call(tooltip, "UpdateTooltipDescription") || !current() - || !store_game::Call(tooltip, "SetupActionTooltipTrigger") || !current() - || !store_game::Enabled(plain, false) || !store_game::Enabled(action, true) || !current()) + if (!action->SetEnabled(false)) return ResearchUpdate::Failed; - const auto enabled = store_game::Call(action, "get_enabled"); - if (!current() || !enabled || !enabled.value || f.trigger.Read(tooltip) != action || f.action.Read(tooltip) != action) + action->CopyAppearance(plain); + if (!current()) + return ResearchUpdate::Failed; + tooltip->SetSearchActionLocaleSetting(search); + tooltip->SetActionLocaleSetting(locale); + tooltip->SetSettings(settings); + tooltip->SetTooltipTrigger(action); + tooltip->SetTooltipActionTrigger(action); + if (!tooltip->UpdateTooltipDescription() || !current() || !tooltip->SetupActionTooltipTrigger() || !current() + || !plain->SetEnabled(false) || !action->SetEnabled(true) || !current()) + return ResearchUpdate::Failed; + const auto enabled = action->Enabled(); + if (!current() || !enabled || !*enabled || tooltip->tooltipTrigger() != action + || tooltip->tooltipActionTrigger() != action) return ResearchUpdate::Failed; rollback.committed = true; return previously_bound ? ResearchUpdate::Unchanged : ResearchUpdate::Upgraded; } -void QueueStore(AllianceStoreScroller* source, bool currency) +void QueueStore(AllianceGamesStoreScrollerViewController* source, bool currency) { - if (!store_game::IsStore(source)) - return; auto& work = alliance_store_work; if (work.source.Get() != source) { - work.source = store_game::Handle::Weak(source); + work.source = il2cpp::GcHandle::Weak(source); ++work.epoch; } - if (!work.source) + if (work.source.Get() != source) return; // Debounce through separate-frame native card spawning. work.due = Clock::now() + std::chrono::milliseconds(250); @@ -263,7 +534,7 @@ void QueueStore(AllianceStoreScroller* source, bool currency) work.layout |= currency && !work.refreshing_currency; } -void AllianceStoreScroller_OnDidBindCanvasContext_Hook(auto original, AllianceStoreScroller* self) +void AllianceStoreScroller_OnDidBindCanvasContext_Hook(auto original, AllianceGamesStoreScrollerViewController* self) { if (!StoreThread()) { original(self); @@ -271,10 +542,12 @@ void AllianceStoreScroller_OnDidBindCanvasContext_Hook(auto original, AllianceSt } original(self); std::erase_if(alliance_store_tooltips, [](auto& entry) { return !entry->widget.Get(); }); - QueueStore(self, true); + if (IsStore(self)) + QueueStore(self, true); } -void AllianceStoreScroller_OnAboutToReleaseCanvasContext_Hook(auto original, AllianceStoreScroller* self) +void AllianceStoreScroller_OnAboutToReleaseCanvasContext_Hook(auto original, + AllianceGamesStoreScrollerViewController* self) { if (!StoreThread()) { original(self); @@ -303,8 +576,8 @@ void ShopListScrollerViewController_RegenerateSortedBundles_Hook(auto original, return; } original(self, instant); - if (store_game::IsStore(self) && !alliance_store_work.refreshing_currency) - QueueStore(reinterpret_cast(self), true); + if (IsStore(self) && !alliance_store_work.refreshing_currency) + QueueStore(static_cast(self), true); } void BundleDataWidget_OnAboutToReleaseContext_Hook(auto original, BundleDataWidget* self) @@ -313,8 +586,7 @@ void BundleDataWidget_OnAboutToReleaseContext_Hook(auto original, BundleDataWidg original(self); return; } - auto* tooltip = store_game::Members().bundle_tooltip.Read(self); - if (auto entry = OwnedTooltip(tooltip)) + if (auto entry = OwnedTooltip(BundleTooltip(self))) (void)RestoreStoreTooltip(*entry); original(self); } @@ -323,9 +595,9 @@ void QueueStoreCard(BundleDataWidget* self) { // Native OnDidBindContext sets this AFTER SetupTooltip. Never use the // RefreshBundle argument or a pre-bind owner as current-card identity. - auto* owner = store_game::Members().bundle_owner.Read(self); - if (owner && alliance_store_work.source.Get() == owner) - QueueStore(reinterpret_cast(owner), false); + auto* owner = BundleOwner(self); + if (owner && alliance_store_work.source.Get() == owner && IsStore(owner)) + QueueStore(static_cast(owner), false); } void BundleDataWidget_RefreshBundle_Hook(auto original, BundleDataWidget* self, Bundle* bundle) { @@ -346,6 +618,9 @@ void BundleDataWidget_OnDidBindContext_Hook(auto original, BundleDataWidget* sel QueueStoreCard(self); } +std::string StoreCount(const auto& value) +{ return value ? std::to_string(*value) : "unavailable"; } + void AllianceStoreFrame() { if (!StoreThread()) @@ -353,25 +628,20 @@ void AllianceStoreFrame() auto& work = alliance_store_work; if (!work.pending || Clock::now() < work.due) return; - const bool currency = std::exchange(work.currency, false); - const bool layout = std::exchange(work.layout, false); - work.pending = false; - const auto epoch = work.epoch; - store_game::Roots roots; - auto* source = roots.Keep(work.source.Get()); - const auto alive = store_game::Alive(source); - const auto observed = source && alive && alive.value - ? store_game::Call(source, "get_AllianceGamesContext") - : store_game::Result{}; - auto* context = observed ? roots.Keep(observed.value) : nullptr; - const auto active = context ? store_game::Call(context, "get_IsShopViewActive") : store_game::Result{}; - if (!context || !active || !active.value) + const bool currency = std::exchange(work.currency, false); + const bool layout = std::exchange(work.layout, false); + work.pending = false; + const auto epoch = work.epoch; + il2cpp::GcRoots roots; + auto* source = roots.Keep(work.source.Get()); + auto* context = source && Alive(source) ? roots.Keep(source->AllianceGamesContext()) : nullptr; + if (!context || !context->IsShopViewActive()) return; if (work.epoch != epoch || work.source.Get() != source) return; - auto& fields = store_game::Members(); - auto* smart = roots.Keep(fields.scroller.Read(source)); - if (smart && (fields.regenerating.Read(smart) || fields.spawning.Read(smart) || fields.initializing.Read(smart))) { + auto* scroller = roots.Keep(source->smartScroller()); + auto* smart = scroller ? scroller->As() : nullptr; + if (smart && (smart->regeneratingContent() || smart->spawnerCoroutine() || smart->dataHeldWhilstInitializing())) { work.pending = true; work.currency |= currency; work.layout |= layout; @@ -380,36 +650,34 @@ void AllianceStoreFrame() } bool position_notified = false; if (layout && smart) { - const auto position = store_game::NormalizedPosition(smart); - const auto live_smart = store_game::Alive(smart); - if (position && live_smart && live_smart.value && work.epoch == epoch && work.source.Get() == source - && fields.scroller.Read(source) == smart) { - position_notified = store_game::NotifyPosition(smart, position.value); + const auto position = smart->NormalizedPosition(); + if (position && Alive(smart) && work.epoch == epoch && work.source.Get() == source + && source->smartScroller() == smart) { + position_notified = smart->OnValueChangedCallback(*position); } } - if (work.epoch != epoch || work.source.Get() != source || fields.scroller.Read(source) != smart) + if (work.epoch != epoch || work.source.Get() != source || source->smartScroller() != scroller) return; - auto* pool = smart ? roots.Keep(fields.spawned.Read(smart)) : nullptr; - const auto count = pool ? store_game::Call(pool, "get_Count") : store_game::Result{}; - if (!count || count.value < 0) + auto* pool = smart ? roots.Keep(smart->spawnedItems()) : nullptr; + const auto count = pool ? pool->TryCall<"get_Count", int32_t()>() : std::nullopt; + if (!count || *count < 0) return; - int upgraded = 0, restored = 0, unavailable = 0, failed = 0; - StoreInventoryTooltip* donor = nullptr; - auto* tooltip_type = roots.Keep(store_game::TypeObject(store_game::TooltipClass())); + int upgraded = 0, restored = 0, unavailable = 0, failed = 0; + InventoryTooltipTriggerWidget* donor = nullptr; + auto* tooltip_type = roots.Keep(InventoryTooltipTriggerWidget::Class::TypeObject()); if (!tooltip_type) return; - for (int32_t index = 0; index < count.value; ++index) { + for (int32_t index = 0; index < *count; ++index) { if (work.epoch != epoch || work.source.Get() != source) return; - store_game::Roots card_roots; - const auto entry = store_game::Call(pool, "get_Item", {store_game::Scalar(index, "System.Int32")}); - auto* rect = entry ? card_roots.Keep(entry.value) : nullptr; - const auto live = store_game::Alive(rect); - if (!rect || !live || !live.value) + il2cpp::GcRoots card_roots; + auto* rect = card_roots.Keep((*pool)[index]); + if (!rect || !Alive(rect)) continue; - const auto tooltip = store_game::Result{ - true, store_game::Component(rect, "GetComponentInChildren", store_game::TooltipClass(), card_roots, true)}; - switch (tooltip && tooltip.value ? ReconcileStoreTooltip(tooltip.value, donor, roots) : ResearchUpdate::Unchanged) { + auto* tooltip = + card_roots.Keep(rect->Call<"GetComponentInChildren", InventoryTooltipTriggerWidget*(Il2CppReflectionType*, bool)>( + tooltip_type, true)); + switch (tooltip ? ReconcileStoreTooltip(tooltip, donor, roots) : ResearchUpdate::Unchanged) { case ResearchUpdate::Upgraded: ++upgraded; break; @@ -426,40 +694,34 @@ void AllianceStoreFrame() break; } } - bool refreshed = false; - store_game::Result currency_before, currency_after; + bool refreshed = false; + std::optional currency_before, currency_after; // Native bundle-derived discovery, not a fixed list of currency IDs. An // empty/loading store waits for its real data/regeneration event. - const auto fetching = store_game::Call(context, "get_IsFetchingBundlesInProgress"); - const auto bundles = store_game::Call(context, "get_BundlesCount"); - if (currency && work.epoch == epoch && work.source.Get() == source && fetching && !fetching.value && bundles - && bundles.value > 0) { + const auto fetching = context->IsFetchingBundlesInProgress(); + if (currency && work.epoch == epoch && work.source.Get() == source && fetching && !*fetching + && context->BundlesCount() > 0) { work.refreshing_currency = true; struct Reset { bool& value; ~Reset() { value = false; } } reset{work.refreshing_currency}; - currency_before = store_game::CurrencyCount(context, roots); - const auto current = store_game::Call(source, "get_AllianceGamesContext"); - const auto live_source = store_game::Alive(source); - if (current && current.value == context && live_source && live_source.value && work.epoch == epoch + currency_before = CurrencyCount(context, roots); + if (source->AllianceGamesContext() == context && Alive(source) && work.epoch == epoch && work.source.Get() == source) { - refreshed = store_game::Call(context, "UpdateStoreCurrency").succeeded; - currency_after = store_game::CurrencyCount(context, roots); + refreshed = context->UpdateStoreCurrency(); + currency_after = CurrencyCount(context, roots); } } if (layout) spdlog::info("[AllianceGamesStore] settled layout notification={} (position unchanged)", position_notified); - const auto visible = store_game::Call(smart, "get_VisibleItemsCount"); - const auto position = store_game::Call(smart, "get_ScrollPosition"); + const auto visible = smart ? smart->VisibleItemsCount() : std::nullopt; + const auto position = smart ? smart->ScrollPosition() : std::nullopt; spdlog::info("[AllianceGamesStore] pool={} upgraded={} restored={} unavailable={} failed={} currency_refresh={} " "currencies={}->{} visible={} position={}", - count.value, upgraded, restored, unavailable, failed, refreshed, - currency_before ? std::to_string(currency_before.value) : "unavailable", - currency_after ? std::to_string(currency_after.value) : "unavailable", - visible ? std::to_string(visible.value) : "unavailable", - position ? std::to_string(position.value) : "unavailable"); + *count, upgraded, restored, unavailable, failed, refreshed, StoreCount(currency_before), + StoreCount(currency_after), StoreCount(visible), StoreCount(position)); } bool StoreHookFits(const MethodInfo* method) @@ -490,9 +752,9 @@ void InstallAllianceGamesStoreHooks() static bool attempted = false; if (std::exchange(attempted, true)) return; - auto* store = store_game::Class("Digit.Prime.Alliances.Games", "AllianceGamesStoreScrollerViewController"); - auto* shop = store_game::Class("Digit.Prime.Shop", "ShopListScrollerViewController"); - auto* card = store_game::Class("Digit.Prime.Shop", "BundleDataWidget"); + auto* store = AllianceGamesStoreScrollerViewController::Class::Raw(); + auto* shop = ShopListScrollerViewController::Class::Raw(); + auto* card = BundleDataWidget::Class::Raw(); const std::array targets{ method_contract::Resolve(store, "OnDidBindCanvasContext", false, "System.Void", {}), method_contract::Resolve(store, "OnAboutToReleaseCanvasContext", false, "System.Void", {}), @@ -500,7 +762,7 @@ void InstallAllianceGamesStoreHooks() method_contract::Resolve(card, "RefreshBundle", false, "System.Void", {"Digit.PrimePlatform.Content.Bundle"}), method_contract::Resolve(card, "OnAboutToReleaseContext", false, "System.Void", {}), method_contract::Resolve(card, "OnDidBindContext", false, "System.Void", {})}; - if (!store_game::Members().Resolve()) { + if (!StoreFieldsResolve()) { spdlog::warn("[AllianceGamesStore] unavailable: native field contract changed"); return; } diff --git a/mods/src/patches/parts/artifact_exchange.cc b/mods/src/patches/parts/artifact_exchange.cc index 71b5258af..7335a728e 100644 --- a/mods/src/patches/parts/artifact_exchange.cc +++ b/mods/src/patches/parts/artifact_exchange.cc @@ -1,59 +1,36 @@ +// disable_exchange_all: hides the artifact shards popup's Exchange All button. Individual exchanges stay available. + #include "config.h" -#include -#include -#include +#include "prime/Component.h" + +#include #include #include namespace { -FieldInfo* convert_all_field = nullptr; -const MethodInfo* get_game_object = nullptr; -const MethodInfo* set_active = nullptr; +struct InventoryUsePopupViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Inventories", "InventoryUsePopupViewController">; + + // Only artifact bulk conversion uses this button, not the inventory entry button or single exchanges. + Component* convertAllButton() + { return Field<"_convertAllButton", Component*>(); } +}; -void InventoryUsePopup_Bind_Hook(auto original, void* controller) +void InventoryUsePopupViewController_OnDidBindCanvasContext_Hook(auto original, InventoryUsePopupViewController* _this) { - original(controller); - if (!Config::Get().disable_exchange_all || !controller) + original(_this); + if (!_this || !Config::Get().disable_exchange_all) return; - Il2CppObject* button = nullptr; - il2cpp_field_get_value(static_cast(controller), convert_all_field, &button); - Il2CppObject* object = nullptr; - if (!button || !Il2CppRuntime::TryInvoke(get_game_object, button, nullptr, &object) || !object) - return; - bool active = false; - void* args[]{&active}; - Il2CppRuntime::TryInvoke(set_active, object, args); + auto* button = _this->convertAllButton(); + if (auto* object = button ? button->gameObject() : nullptr) + object->SetActive(false); } } // namespace void InstallArtifactExchangeHooks() { - // This dedicated field belongs to artifact bulk conversion, not the inventory - // entry button or individual exchanges. Preserve native binding first. - auto controller = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Inventories", "InventoryUsePopupViewController"); - auto component = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Component"); - auto game_object = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "GameObject"); - auto* cls = controller.get_cls(); - auto* field = cls ? il2cpp_class_get_field_from_name(cls, "_convertAllButton") : nullptr; - auto* field_class = field && field->type ? il2cpp_class_from_type(field->type) : nullptr; - const auto* bind = method_contract::Resolve(cls, "OnDidBindCanvasContext", false, "System.Void", {}); - get_game_object = method_contract::Resolve(component.get_cls(), "get_gameObject", false, "UnityEngine.GameObject", {}); - set_active = method_contract::Resolve(game_object.get_cls(), "SetActive", false, "System.Void", {"System.Boolean"}); - if (!cls || !component.get_cls() || !game_object.get_cls() || !field || !field->type || field->type->byref - || (il2cpp_field_get_flags(field) & FIELD_ATTRIBUTE_STATIC) || !field_class - || !il2cpp_class_is_assignable_from(component.get_cls(), field_class) - || !bind || !get_game_object || !set_active - || bind->has_full_generic_sharing_signature || get_game_object->has_full_generic_sharing_signature - || set_active->has_full_generic_sharing_signature) { - spdlog::warn("[ArtifactExchange] required popup API unavailable; leaving game unchanged"); - return; - } - convert_all_field = field; - if (SPUD_STATIC_DETOUR(bind->methodPointer, InventoryUsePopup_Bind_Hook)) - spdlog::info("[ArtifactExchange] installed popup bind hook"); - else - spdlog::warn("[ArtifactExchange] popup bind hook was not installed"); + if (auto m = InventoryUsePopupViewController::Class::Method("OnDidBindCanvasContext", 0)) + SPUD_STATIC_DETOUR(m.Pointer(), InventoryUsePopupViewController_OnDidBindCanvasContext_Hook); } diff --git a/mods/src/patches/parts/audio_events.cc b/mods/src/patches/parts/audio_events.cc index 89b53b2bc..4b109e975 100644 --- a/mods/src/patches/parts/audio_events.cc +++ b/mods/src/patches/parts/audio_events.cc @@ -1,9 +1,7 @@ #include "config.h" -#include "errormsg.h" #include "str_utils.h" -#include -#include +#include #include #include @@ -14,40 +12,6 @@ struct FabricEventManager { }; -namespace -{ -enum class FabricEventAction : int { - PlaySound = 0, - StopSound = 1, - UnpauseSound = 3, - AdvanceSequence = 16, - StopAll = 21, - UnloadAudio = 23, - PlayScheduled = 33, -}; - -constexpr bool can_start_audio(int event_action) -{ - switch (static_cast(event_action)) { - case FabricEventAction::PlaySound: - case FabricEventAction::UnpauseSound: - case FabricEventAction::AdvanceSequence: - case FabricEventAction::PlayScheduled: - return true; - default: - return false; - } -} - -constexpr bool should_suppress_audio_event(bool disable_all, bool event_is_disabled, int event_action) -{ return disable_all ? can_start_audio(event_action) : event_is_disabled; } - -static_assert(should_suppress_audio_event(true, false, static_cast(FabricEventAction::PlaySound))); -static_assert(!should_suppress_audio_event(true, false, static_cast(FabricEventAction::StopSound))); -static_assert(should_suppress_audio_event(false, true, static_cast(FabricEventAction::StopAll))); -static_assert(!should_suppress_audio_event(true, true, static_cast(FabricEventAction::UnloadAudio))); -} // namespace - // Fabric.EventManager.PostEvent(string, EventAction, object, GameObject, // InitialiseParameters, bool, OnEventNotify) bool FabricEventManager_PostEvent_Hook(auto original, FabricEventManager* _this, Il2CppString* event_name, @@ -55,60 +19,32 @@ bool FabricEventManager_PostEvent_Hook(auto original, FabricEventManager* _this, Il2CppObject* initialise_parameters, bool add_to_queue, Il2CppObject* on_event_notify) { - auto& config = Config::Get(); - if (!event_name || (!config.trace_audio_events && !config.disable_all_audio_events - && config.disabled_audio_events.empty())) { + if (event_name == nullptr) { return original(_this, event_name, event_action, parameter, parent_game_object, initialise_parameters, add_to_queue, on_event_notify); } - try { - const auto event = to_string(event_name); - if (config.trace_audio_events) - spdlog::info("Audio event: {}", event); - const bool event_is_disabled = - std::ranges::find(config.disabled_audio_events, event) != config.disabled_audio_events.end(); - if (should_suppress_audio_event(config.disable_all_audio_events, event_is_disabled, event_action)) { - spdlog::debug("Suppressed audio event: {}", event); - return false; - } - } catch (...) { - // Filter processing failure preserves the native event path. + const auto event = to_string(event_name); + auto& config = Config::Get(); + + if (config.trace_audio_events) { + spdlog::info("Audio event: {}", event); + } + + if ((config.disable_all_audio_events ? event_action == 0 || event_action == 3 || event_action == 16 || event_action == 33 : std::ranges::find(config.disabled_audio_events, event) != config.disabled_audio_events.end())) { + spdlog::debug("Suppressed audio event: {}", event); + return false; } + return original(_this, event_name, event_action, parameter, parent_game_object, initialise_parameters, add_to_queue, on_event_notify); } void InstallAudioEventHooks() { - auto helper = il2cpp_get_class_helper("Fabric.Core", "Fabric", "EventManager"); - if (!helper.isValidHelper()) { - ErrorMsg::MissingHelper("Fabric", "EventManager"); - return; - } + using EventManager = il2cpp::Class<"Fabric.Core", "Fabric", "EventManager">; - // String overloads funnel here. Require the complete instance ABI to avoid - // the integer-ID overload and incompatible future signatures. - const auto* method = method_contract::Resolve(helper.get_cls(), "PostEvent", false, "System.Boolean", - {"System.String", "Fabric.EventAction", "System.Object", "UnityEngine.GameObject", - "Fabric.InitialiseParameters", "System.Boolean", "Fabric.OnEventNotify"}); - if (!method || method->has_full_generic_sharing_signature) { - ErrorMsg::MissingMethod("EventManager", "PostEvent(string, EventAction, object, GameObject, InitialiseParameters, bool, OnEventNotify)"); - return; - } - for (const auto index : {0, 2, 3, 4, 6}) { - auto* cls = il2cpp_class_from_type(method->parameters[index]); - if (!cls || il2cpp_class_is_valuetype(cls)) { - spdlog::error("Fabric PostEvent reference argument is incompatible"); - return; - } - } - auto* action = il2cpp_class_from_type(method->parameters[1]); - const auto* action_type = action && il2cpp_class_is_enum(action) ? il2cpp_class_enum_basetype(action) : nullptr; - if (!action_type || action_type->byref || action_type->type != IL2CPP_TYPE_I4) { - spdlog::error("Fabric PostEvent action enum is incompatible"); - return; - } - if (!SPUD_STATIC_DETOUR(method->methodPointer, FabricEventManager_PostEvent_Hook)) - spdlog::error("Fabric named audio event hook was not installed"); + // The shorter PostEvent overloads funnel into this seven-argument overload. + if (auto method = EventManager::Method("PostEvent", 7)) + SPUD_STATIC_DETOUR(method.Pointer(), FabricEventManager_PostEvent_Hook); } diff --git a/mods/src/patches/parts/buff_fixes.cc b/mods/src/patches/parts/buff_fixes.cc index 75d76cd4c..89e1bedbf 100644 --- a/mods/src/patches/parts/buff_fixes.cc +++ b/mods/src/patches/parts/buff_fixes.cc @@ -1,8 +1,7 @@ #include "config.h" -#include "errormsg.h" #include "prime_types.h" -#include +#include #include #include @@ -25,17 +24,10 @@ static bool BuffService_IsBuffConditionMet(auto original, void* _this, BuffCondi void InstallBuffFixHooks() { + using BuffService = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "BuffService">; + if (Config::Get().use_out_of_dock_power) { - auto buffHelper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "BuffService"); - if (!buffHelper.isValidHelper()) { - ErrorMsg::MissingHelper("Services", "BuffService"); - } else { - if (const auto ptr = buffHelper.GetMethod("IsBuffConditionMet"); ptr == nullptr) { - ErrorMsg::MissingMethod("BuffService", "IsBuffConditionMet"); - } else { - SPUD_STATIC_DETOUR(ptr, BuffService_IsBuffConditionMet); - } - } + if (auto method = BuffService::Method("IsBuffConditionMet")) + SPUD_STATIC_DETOUR(method.Pointer(), BuffService_IsBuffConditionMet); } } diff --git a/mods/src/patches/parts/cargo_format.cc b/mods/src/patches/parts/cargo_format.cc index 7d896e146..ab5092f1e 100644 --- a/mods/src/patches/parts/cargo_format.cc +++ b/mods/src/patches/parts/cargo_format.cc @@ -1,7 +1,5 @@ #include "config.h" -#include "errormsg.h" - -#include +#include "prime/TextLocalizer.h" #include #include @@ -13,7 +11,7 @@ static constexpr char kCargoIdentifier[] = "shared_x_of_y_x_coloured"; static constexpr size_t kCargoIdentifierLen = sizeof(kCargoIdentifier) - 1; -void ColourTextLocalizer_SetLocalTextParameters_Hook(auto original, void* _this, bool parseID, void* args) +void ColourTextLocalizer_SetLocalTextParameters_Hook(auto original, ColourTextLocalizer* _this, bool parseID, void* args) { if (!_this || !Config::Get().format_cargo_values) { original(_this, parseID, args); @@ -24,17 +22,7 @@ void ColourTextLocalizer_SetLocalTextParameters_Hook(auto original, void* _this, if (desired_decimals < 0) desired_decimals = 0; if (desired_decimals > 6) desired_decimals = 6; - static auto text_localizer_h = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "TextLocalizer"); - static auto identifier_field = text_localizer_h.GetField("m_identifier"); - static auto sig_decimals_field = text_localizer_h.GetField("m_significantDecimals"); - - if (!identifier_field.isValidHelper() || !sig_decimals_field.isValidHelper()) { - original(_this, parseID, args); - return; - } - - auto* identifier_ptr = *reinterpret_cast((char*)_this + identifier_field.offset()); + auto* identifier_ptr = _this->identifier(); if (!identifier_ptr) { original(_this, parseID, args); return; @@ -55,30 +43,19 @@ void ColourTextLocalizer_SetLocalTextParameters_Hook(auto original, void* _this, } } - auto* sig_decimals_ptr = reinterpret_cast((char*)_this + sig_decimals_field.offset()); - if (*sig_decimals_ptr != desired_decimals) { - *sig_decimals_ptr = desired_decimals; - } + _this->SetSignificantDecimals(desired_decimals); original(_this, parseID, args); } void InstallCargoFormatHooks() { - auto colour_text_localizer_h = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "ColourTextLocalizer"); - if (!colour_text_localizer_h.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Client.UI", "ColourTextLocalizer"); - return; - } - - auto ptr = colour_text_localizer_h.GetMethod("SetLocalTextParameters"); - if (!ptr) { - ErrorMsg::MissingMethod("ColourTextLocalizer", "SetLocalTextParameters"); + auto method = ColourTextLocalizer::Class::Method("SetLocalTextParameters"); + if (!method) { return; } - SPUD_STATIC_DETOUR(ptr, ColourTextLocalizer_SetLocalTextParameters_Hook); + SPUD_STATIC_DETOUR(method.Pointer(), ColourTextLocalizer_SetLocalTextParameters_Hook); int applied = Config::Get().cargo_significant_decimals; if (applied < 0) applied = 0; if (applied > 6) applied = 6; diff --git a/mods/src/patches/parts/chat.cc b/mods/src/patches/parts/chat.cc index 6b63c2b12..de2ea8d18 100644 --- a/mods/src/patches/parts/chat.cc +++ b/mods/src/patches/parts/chat.cc @@ -3,21 +3,21 @@ #include "prime/GenericButtonContext.h" #include "config.h" -#include "errormsg.h" #include #include #include #include #include -#include "il2cpp/il2cpp_checked.h" +#include #include "patches/chat_startup.h" #include "patches/screen_update_hook.h" #include "prime/Hub.h" +#include "prime/ScreenManager.h" namespace { -Il2CppGCHandle preview_handle = {}; +il2cpp::GcHandle preview_handle; ChatStartup::Gate startup; bool width_applied = false; bool startup_failed = false; @@ -26,11 +26,9 @@ void ObserveChatPreview(ChatPreviewController* preview) { if (!Config::Get().auto_open_chat) return; - if (preview_handle && il2cpp_gchandle_get_target(preview_handle) == reinterpret_cast(preview)) + if (preview_handle && preview_handle.Get() == preview) return; - if (preview_handle) - il2cpp_gchandle_free(preview_handle); - preview_handle = il2cpp_gchandle_new_weakref(reinterpret_cast(preview), false); + preview_handle = il2cpp::GcHandle::Weak(preview); startup.ready_since = -1; spdlog::info("[ChatOptions] observed chat preview"); } @@ -45,7 +43,7 @@ void UpdateChatOptions() auto* sections = Hub::get_SectionManager(); if (!sections) return; - const auto section = sections->CurrentSection; + const auto section = sections->CurrentSection(); if (section == SectionID::Login || section == SectionID::AppInit) { startup = {}; width_applied = false; @@ -56,35 +54,28 @@ void UpdateChatOptions() } if ((!Config::Get().auto_open_chat || startup.completed) && (!custom_width || width_applied)) return; - auto* manager = reinterpret_cast(ChatManager::Instance()); + auto* manager = ChatManager::Instance(); if (!manager) return; - const bool side_open = Il2CppChecked::Boolean(Il2CppChecked::Invoke(manager, "get_IsSideChatOpen")); + const bool side_open = manager->IsSideChatOpen(); float width = configured_width; // Apply once per login, then leave manual resizing to the game. if (side_open && !width_applied && std::isfinite(width) && width > 0) { - static auto screen_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "ScreenManager"); - static auto singleton = screen_helper.GetParent("MonoSingleton`1"); - auto* screen = singleton.GetProperty("Instance").GetRaw(nullptr); - auto* frame = Il2CppChecked::ReferenceField(screen, "_frameManager", "UIFrameManager"); - if (frame) { - auto* method = IL2CppClassHelper(frame->klass).GetMethodInfo("ResizeSideFrame", 1); - if (!method || !method->parameters || method->parameters[0]->type != IL2CPP_TYPE_R4) - throw std::runtime_error("side frame resize signature changed"); - void* args[] = {&width}; - if (!Il2CppRuntime::TryInvoke(method, frame, args)) + auto* screen = ScreenManager::Instance(); + if (auto* frame = screen ? screen->frameManager() : nullptr) { + if (!frame->ResizeSideFrame(width)) throw std::runtime_error("side frame resize failed"); width_applied = true; } } if (!Config::Get().auto_open_chat || startup.completed) return; - auto* preview = preview_handle ? il2cpp_gchandle_get_target(preview_handle) : nullptr; + auto* preview = preview_handle.Get(); const bool station = section == SectionID::Starbase_Interior || section == SectionID::Starbase_Exterior; // Section completion is set by the game after its visibility refresh. - const bool section_ready = preview && Il2CppChecked::BooleanField(preview, "_isSectionChangedCompleted"); - const bool context_ready = Il2CppChecked::BooleanField(manager, "_isContextInitialised"); - const bool side_allowed = Il2CppChecked::Boolean(Il2CppChecked::Invoke(manager, "get_IsSideChatAllowed")); + const bool section_ready = preview && preview->isSectionChangedCompleted(); + const bool context_ready = manager->isContextInitialised(); + const bool side_allowed = manager->IsSideChatAllowed(); const bool ready = station && section_ready && context_ready && side_allowed; // Log changes only, so a pending startup identifies its exact blocking gate. const int status = (station ? 1 : 0) | (preview ? 2 : 0) | (section_ready ? 4 : 0) @@ -101,7 +92,7 @@ void UpdateChatOptions() const double now = std::chrono::duration(std::chrono::steady_clock::now().time_since_epoch()).count(); if (startup.Tick(ready, side_open || Hub::IsInChat(), now)) { spdlog::info("[ChatOptions] readiness settled; opening alliance side chat"); - reinterpret_cast(manager)->OpenChannel(ChatChannelCategory::Alliance, ChatViewMode::Side); + manager->OpenChannel(ChatChannelCategory::Alliance, ChatViewMode::Side); } else if (startup.completed) { spdlog::info("[ChatOptions] startup consumed by existing/manual chat"); } @@ -138,36 +129,30 @@ void DisableButtons(FullScreenChatViewController* _this) const auto [cadetChatIdx, galaxyChatIdx, veilChatIdx, allianceChatIdx] = GetChatTabIndices(); - const auto viewController = _this->_categoriesTabBarViewController; + const auto viewController = _this->categoriesTabBarViewController(); if (!viewController) { return; } - const auto tabBar = viewController->_tabBar; + const auto tabBar = viewController->tabBar(); if (!tabBar) { return; } - if (const auto list = tabBar->_listContainer; !list) { - return; - } - - const auto listData = tabBar->_data; + const auto listData = tabBar->data(); if (!listData) { return; } if (disableGalaxyChat) { - if (const auto elm = listData->Get(galaxyChatIdx)) { - const auto button = reinterpret_cast(elm); - button->Interactable = false; + if (const auto elm = (*listData)[galaxyChatIdx]) { + static_cast(elm)->SetInteractable(false); } } if (disableVeilChat && veilChatIdx != -1) { - if (const auto elm = listData->Get(veilChatIdx)) { - const auto button = reinterpret_cast(elm); - button->Interactable = false; + if (const auto elm = (*listData)[veilChatIdx]) { + static_cast(elm)->SetInteractable(false); } } } @@ -197,10 +182,10 @@ void ChatPreviewController_AboutToShow(auto original, ChatPreviewController* _th if (Config::Get().disable_galaxy_chat || Config::Get().disable_veil_chat) { const auto allianceChatIdx = std::get<3>(GetChatTabIndices()); - _this->_focusedPanel = ChatChannelCategory::Alliance; + _this->SetFocusedPanel(ChatChannelCategory::Alliance); - if (_this->_swipeScroller->_currentContentIndex != allianceChatIdx) { - _this->_swipeScroller->FocusOnInstantly(allianceChatIdx); + if (_this->swipeScroller()->currentContentIndex() != allianceChatIdx) { + _this->swipeScroller()->FocusOnInstantly(allianceChatIdx); } } } @@ -218,11 +203,11 @@ void ChatPreviewController_OnPanelFocused(auto original, ChatPreviewController* const auto [cadetChatIdx, galaxyChatIdx, veilChatIdx, allianceChatIdx] = GetChatTabIndices(); if (disableGalaxyChat || (veilChatIdx != -1 && disableVeilChat)) { - _this->_focusedPanel = ChatChannelCategory::Alliance; + _this->SetFocusedPanel(ChatChannelCategory::Alliance); original(_this, allianceChatIdx); - if (_this->_swipeScroller->_currentContentIndex != allianceChatIdx) { - _this->_swipeScroller->FocusOnInstantly(allianceChatIdx); + if (_this->swipeScroller()->currentContentIndex() != allianceChatIdx) { + _this->swipeScroller()->FocusOnInstantly(allianceChatIdx); } return; @@ -252,50 +237,17 @@ void InstallChatPatches() if (install_screen_manager_update_hook() && register_screen_manager_update_callback(UpdateChatOptions)) spdlog::info("[ChatOptions] lifecycle v2 registered: auto_open={} width={}", Config::Get().auto_open_chat, Config::Get().default_chat_width); - if (auto fullscreen_controller = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Chat", "FullScreenChatViewController"); - !fullscreen_controller.isValidHelper()) { - ErrorMsg::MissingHelper("Chat", "FullScreenChatViewController"); - } else { - if (const auto ptr = fullscreen_controller.GetMethod("AboutToShow"); ptr == nullptr) { - ErrorMsg::MissingMethod("FullScreenChatViewController", "AboutToShow"); - } else { - SPUD_STATIC_DETOUR(ptr, FullScreenChatViewController_AboutToShow); - } - - if (const auto ptr = fullscreen_controller.GetMethod("OnDidChangeSelectedTab"); ptr == nullptr) { - ErrorMsg::MissingMethod("FullScreenChatViewController", "OnDidChangeSelectedTab"); - } else { - SPUD_STATIC_DETOUR(ptr, FullScreenChatViewController_OnDidChangeSelectedTab); - } - } - - if (auto preview_controller = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Chat", "ChatPreviewController"); - !preview_controller.isValidHelper()) { - ErrorMsg::MissingHelper("Chat", "ChatPreviewController"); - } else { - if (const auto ptr = preview_controller.GetMethod("AboutToShow"); ptr == nullptr) { - ErrorMsg::MissingMethod("ChatPreviewController", "AboutToShow"); - } else { - SPUD_STATIC_DETOUR(ptr, ChatPreviewController_AboutToShow); - } - - if (const auto ptr = preview_controller.GetMethod("OnPanelFocused"); ptr == nullptr) { - ErrorMsg::MissingMethod("ChatPreviewController", "OnPanelFocused"); - } else { - SPUD_STATIC_DETOUR(ptr, ChatPreviewController_OnPanelFocused); - } - - if (const auto ptr = preview_controller.GetMethod("OnGlobalMessageReceived"); ptr == nullptr) { - ErrorMsg::MissingMethod("ChatPreviewController", "OnGlobalMessageReceived"); - } else { - SPUD_STATIC_DETOUR(ptr, ChatPreviewController_OnGlobalMessageReceived); - } - - if (const auto ptr = preview_controller.GetMethod("OnRegionalMessageReceived"); ptr == nullptr) { - ErrorMsg::MissingMethod("ChatPreviewController", "OnRegionalMessageReceived"); - } else { - SPUD_STATIC_DETOUR(ptr, ChatPreviewController_OnRegionalMessageReceived); - } - } + if (auto m = FullScreenChatViewController::Class::Method("AboutToShow")) + SPUD_STATIC_DETOUR(m.Pointer(), FullScreenChatViewController_AboutToShow); + if (auto m = FullScreenChatViewController::Class::Method("OnDidChangeSelectedTab")) + SPUD_STATIC_DETOUR(m.Pointer(), FullScreenChatViewController_OnDidChangeSelectedTab); + + if (auto m = ChatPreviewController::Class::Method("AboutToShow")) + SPUD_STATIC_DETOUR(m.Pointer(), ChatPreviewController_AboutToShow); + if (auto m = ChatPreviewController::Class::Method("OnPanelFocused")) + SPUD_STATIC_DETOUR(m.Pointer(), ChatPreviewController_OnPanelFocused); + if (auto m = ChatPreviewController::Class::Method("OnGlobalMessageReceived")) + SPUD_STATIC_DETOUR(m.Pointer(), ChatPreviewController_OnGlobalMessageReceived); + if (auto m = ChatPreviewController::Class::Method("OnRegionalMessageReceived")) + SPUD_STATIC_DETOUR(m.Pointer(), ChatPreviewController_OnRegionalMessageReceived); } diff --git a/mods/src/patches/parts/chat_titles.cc b/mods/src/patches/parts/chat_titles.cc index f648014dd..2e97e6cba 100644 --- a/mods/src/patches/parts/chat_titles.cc +++ b/mods/src/patches/parts/chat_titles.cc @@ -40,7 +40,7 @@ bool IsChatRow(void* profile_widget) if (!gameObject) { return false; } - if (reinterpret_cast(gameObject)->GetComponentFastPath2()) { + if (reinterpret_cast(gameObject)->GetComponent()) { return true; } transform = InvokeSelf(transform, "get_parent"); @@ -64,7 +64,7 @@ void ApplyTitleDisplay(void* profile_widget) } const auto show = Config::Get().show_player_title; - auto* title = widget->_playerTitleWidget; + auto* title = widget->playerTitleWidget(); if (show || title == nullptr) { return; } @@ -97,7 +97,7 @@ void PlayerTitleWidget_BindPath_Hook(auto original, void* _this) for (int hops = 0; hops < 32 && transform && !owner; ++hops) { auto* gameObject = reinterpret_cast(InvokeSelf(transform, "get_gameObject")); if (gameObject) { - owner = gameObject->GetComponentFastPath2(); + owner = gameObject->GetComponent(); } transform = InvokeSelf(transform, "get_parent"); } @@ -141,21 +141,21 @@ void InstallChatTitleDisplayHooks() if (auto profile_widget = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.PlayerProfile", "UserProfileWidget"); !profile_widget.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.PlayerProfile", "UserProfileWidget"); + spdlog::warn("Missing class {}.{}", "Digit.Prime.PlayerProfile", "UserProfileWidget"); } else if ((update_player_title = profile_widget.GetMethod("UpdatePlayerTitle", 0)) == nullptr) { - ErrorMsg::MissingMethod("UserProfileWidget", "UpdatePlayerTitle"); + spdlog::warn("Missing method {}.{}", "UserProfileWidget", "UpdatePlayerTitle"); } void* set_widget_data = nullptr; void* on_did_bind_context = nullptr; if (auto title_widget = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.PlayerAvatars", "PlayerTitleWidget"); !title_widget.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.PlayerAvatars", "PlayerTitleWidget"); + spdlog::warn("Missing class {}.{}", "Digit.Prime.PlayerAvatars", "PlayerTitleWidget"); } else { set_widget_data = title_widget.GetMethod("SetWidgetData", 0); on_did_bind_context = title_widget.GetMethod("OnDidBindContext", 0); if (!set_widget_data || !on_did_bind_context) { - ErrorMsg::MissingMethod("PlayerTitleWidget", set_widget_data ? "OnDidBindContext" : "SetWidgetData"); + spdlog::warn("Missing method {}.{}", "PlayerTitleWidget", set_widget_data ? "OnDidBindContext" : "SetWidgetData"); set_widget_data = nullptr; on_did_bind_context = nullptr; } diff --git a/mods/src/patches/parts/daily_faction_bulk_claim.cc b/mods/src/patches/parts/daily_faction_bulk_claim.cc index 35fcdb4ed..426c9cba4 100644 --- a/mods/src/patches/parts/daily_faction_bulk_claim.cc +++ b/mods/src/patches/parts/daily_faction_bulk_claim.cc @@ -1,12 +1,12 @@ #include "config.h" -#include "errormsg.h" #include "str_utils.h" +#include +#include #include -#include #include -#include +#include #include #include @@ -19,74 +19,53 @@ namespace { constexpr std::string_view kFactionPointResourcePrefix = "Resource_FactionPoint_"; -class ResourceSpec -{ -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "ResourceSpec"); - return class_helper; - } +struct ResourceSpec : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "ResourceSpec">; -public: - static IL2CppClassHelper& ClassHelper() - { - return get_class_helper(); - } + Il2CppString* resourceId() + { return Field<"resourceId_", Il2CppString*>(); } }; -class InventoryItem -{ -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "InventoryItem"); - return class_helper; - } +struct InventoryItem : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "InventoryItem">; -public: - static IL2CppClassHelper& ClassHelper() - { - return get_class_helper(); - } + ResourceSpec* resourceSpec() + { return Field<"_resourceSpec", ResourceSpec*>(); } }; -class MissionViewController -{ -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Missions", "MissionViewController"); - return class_helper; - } +struct PointerEventData : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.UI", "UnityEngine.EventSystems", "PointerEventData">; +}; -public: - static IL2CppClassHelper& ClassHelper() - { - return get_class_helper(); - } +struct Toggle : Component { + using Class = il2cpp::Class<"UnityEngine.UI", "UnityEngine.UI", "Toggle">; - Il2CppObject* ClaimAllToggle() + bool isOn() + { return Get<"isOn", bool>(); } + void OnPointerClick(PointerEventData* event) + { Call<"OnPointerClick">(event); } + + // Goes through the pointer handler instead of the isOn setter, so interactable checks and + // listeners behave as for a real click. + void SimulateClick() { - static auto field = get_class_helper().GetField("_claimAllToggle"); - return field.isValidHelper() ? *(Il2CppObject**)((ptrdiff_t)this + field.offset()) : nullptr; + if (auto* event = il2cpp::New(EventSystem::current())) + OnPointerClick(event); } }; -Transform* GetTransformOf(Il2CppObject* component) -{ - if (component == nullptr || component->klass == nullptr) { - return nullptr; - } - auto helper = IL2CppClassHelper{component->klass}.GetParent("Component"); - if (!helper.isValidHelper()) { - return nullptr; - } - return helper.GetProperty("transform").GetRaw(component); -} +struct MissionViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Missions", "MissionViewController">; + + Toggle* ClaimAllToggle() + { return Field<"_claimAllToggle", Toggle*>(); } +}; + +// GetClaimableDailiesList returns List; the items are EventModel today. +using TournamentList = il2cpp::List; + +Transform* GetTransformOf(Component* component) +{ return component ? component->transform() : nullptr; } Transform* FindChildByName(Transform* root, const char* name) { @@ -94,12 +73,12 @@ Transform* FindChildByName(Transform* root, const char* name) return nullptr; } - auto* go = root->gameObject; + auto* go = root->gameObject(); if (go != nullptr && go->Name() == name) { return root; } - for (int32_t i = 0; i < root->childCount; ++i) { + for (int32_t i = 0; i < root->childCount(); ++i) { if (auto* found = FindChildByName(root->GetChild(i), name)) { return found; } @@ -125,7 +104,7 @@ void ApplyFactionIconVisibility(Transform* toggle_tf) return; } - auto* parent = toggle_tf->parent; + auto* parent = toggle_tf->parent(); if (parent == nullptr) { spdlog::debug("[DailyFactionBulkClaim] ApplyFactionIconVisibility: toggle has no parent Transform"); return; @@ -140,7 +119,7 @@ void ApplyFactionIconVisibility(Transform* toggle_tf) continue; } - auto* go = icon_transform->gameObject; + auto* go = icon_transform->gameObject(); if (go == nullptr) { continue; } @@ -151,114 +130,9 @@ void ApplyFactionIconVisibility(Transform* toggle_tf) } } -class UnityEventSystem -{ -public: - static Il2CppObject* Current() - { - static auto class_helper = - il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.EventSystems", "EventSystem"); - static auto get_current = class_helper.GetMethodInfo("get_current", 0); - if (get_current == nullptr) { - return nullptr; - } - Il2CppException* exception = nullptr; - auto* result = il2cpp_runtime_invoke(get_current, nullptr, nullptr, &exception); - return exception != nullptr ? nullptr : result; - } -}; - -class UnityToggle -{ -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "Toggle"); - return class_helper; - } - -public: - static IL2CppClassHelper& ClassHelper() - { - return get_class_helper(); - } - - static bool IsOn(Il2CppObject* toggle) - { - if (toggle == nullptr) { - return false; - } - static auto get_is_on = get_class_helper().GetMethodInfo("get_isOn", 0); - if (get_is_on == nullptr) { - return false; - } - Il2CppException* exception = nullptr; - auto* result = il2cpp_runtime_invoke(get_is_on, toggle, nullptr, &exception); - if (exception != nullptr || result == nullptr) { - return false; - } - return *(bool*)il2cpp_object_unbox(result); - } - - static void SimulateClick(Il2CppObject* toggle) - { - if (toggle == nullptr) { - return; - } - - static auto on_pointer_click = get_class_helper().GetMethodInfoSpecial("OnPointerClick", [](auto count, auto) { - return count == 1; - }); - if (on_pointer_click == nullptr) { - spdlog::debug("[DailyFactionBulkClaim] UnityToggle::SimulateClick: OnPointerClick(PointerEventData) not " - "found"); - return; - } - - static auto event_data_helper = - il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.EventSystems", "PointerEventData"); - if (event_data_helper.get_cls() == nullptr) { - spdlog::debug("[DailyFactionBulkClaim] UnityToggle::SimulateClick: PointerEventData class not found"); - return; - } - static auto ctor = event_data_helper.GetMethodInfoSpecial(".ctor", [](auto count, auto) { return count == 1; }); - if (ctor == nullptr) { - spdlog::debug("[DailyFactionBulkClaim] UnityToggle::SimulateClick: PointerEventData(EventSystem) ctor not " - "found"); - return; - } - - auto* event_data = il2cpp_object_new(event_data_helper.get_cls()); - if (event_data == nullptr) { - spdlog::debug("[DailyFactionBulkClaim] UnityToggle::SimulateClick: PointerEventData allocation failed"); - return; - } - - Il2CppException* exception = nullptr; - auto* event_system = UnityEventSystem::Current(); - void* ctor_args[1] = {event_system}; - il2cpp_runtime_invoke(ctor, event_data, ctor_args, &exception); - if (exception != nullptr) { - char buffer[256] = {}; - il2cpp_format_exception(exception, buffer, sizeof(buffer)); - spdlog::debug("[DailyFactionBulkClaim] UnityToggle::SimulateClick: PointerEventData ctor threw: {}", buffer); - return; - } - - exception = nullptr; - void* args[1] = {event_data}; - il2cpp_runtime_invoke(on_pointer_click, toggle, args, &exception); - if (exception != nullptr) { - char buffer[256] = {}; - il2cpp_format_exception(exception, buffer, sizeof(buffer)); - spdlog::debug("[DailyFactionBulkClaim] UnityToggle::SimulateClick: OnPointerClick invoke threw: {}", buffer); - } - } -}; - -bool g_applied_default_toggle_on = false; -int g_default_toggle_attempts = 0; -Il2CppObject* g_active_mission_view_controller = nullptr; +bool g_applied_default_toggle_on = false; +int g_default_toggle_attempts = 0; +MissionViewController* g_active_mission_view_controller = nullptr; constexpr int kMaxDefaultToggleAttempts = 300; @@ -269,7 +143,7 @@ void ApplyDefaultToggleOnOnce(MissionViewController* controller) return; } - if (UnityToggle::IsOn(toggle)) { + if (toggle->isOn()) { g_applied_default_toggle_on = true; return; } @@ -281,9 +155,9 @@ void ApplyDefaultToggleOnOnce(MissionViewController* controller) return; } - UnityToggle::SimulateClick(toggle); + toggle->SimulateClick(); - if (UnityToggle::IsOn(toggle)) { + if (toggle->isOn()) { spdlog::debug("[DailyFactionBulkClaim] daily_bulk_claim_toggle_default_on: forced Claim All toggle on " "(attempt {})", g_default_toggle_attempts); @@ -308,45 +182,15 @@ bool IsMainFactionDaily(const std::string& faction_name) return std::ranges::any_of(kFactionIconNames, [&](const auto& entry) { return lowered == entry.faction; }); } -std::string FactionNameOfTournament(Il2CppObject* tournament) +std::string FactionNameOfTournament(il2cpp::Object* tournament) { - if (tournament == nullptr || tournament->klass == nullptr) { + auto* rewards = tournament ? tournament->Field<"_factionPointRewards", il2cpp::List*>() : nullptr; + if (!rewards) return {}; - } - - auto list_field = IL2CppClassHelper{tournament->klass}.GetField("_factionPointRewards"); - if (!list_field.isValidHelper()) { - return {}; - } - - auto* list_obj = *(Il2CppObject**)((char*)tournament + list_field.offset()); - if (list_obj == nullptr) { - return {}; - } - - auto* list = (IList*)list_obj; - for (int32_t i = 0; i < list->Count; ++i) { - auto* item = list->Get(i); - if (item == nullptr || item->klass == nullptr) { - continue; - } - - auto spec_field = IL2CppClassHelper{item->klass}.GetField("_resourceSpec"); - if (!spec_field.isValidHelper()) { - continue; - } - auto* spec = *(Il2CppObject**)((char*)item + spec_field.offset()); - if (spec == nullptr || spec->klass == nullptr) { - continue; - } - - auto id_field = IL2CppClassHelper{spec->klass}.GetField("resourceId_"); - if (!id_field.isValidHelper()) { - continue; - } - - auto* id_str = *(Il2CppString**)((char*)spec + id_field.offset()); + for (auto* item : *rewards) { + auto* spec = item ? item->resourceSpec() : nullptr; + auto* id_str = spec ? spec->resourceId() : nullptr; if (id_str == nullptr) { continue; } @@ -360,20 +204,20 @@ std::string FactionNameOfTournament(Il2CppObject* tournament) return {}; } -void FilterClaimableDailiesByFaction(IList* list, const std::vector& configured) +void FilterClaimableDailiesByFaction(TournamentList* list, const std::vector& configured) { if (list == nullptr) { spdlog::debug("[DailyFactionBulkClaim] GetClaimableDailiesList returned null; nothing to filter"); return; } - const auto total = list->Count; + const auto total = list->Count(); spdlog::debug("[DailyFactionBulkClaim] filtering {} claimable daily(ies) against configured factions [{}]", total, fmt::join(configured, ", ")); int kept = 0; for (int32_t i = total - 1; i >= 0; --i) { - auto* tournament = list->Get(i); + auto* tournament = (*list)[i]; auto faction_name = FactionNameOfTournament(tournament); const bool is_main_faction_daily = IsMainFactionDaily(faction_name); auto matches = FactionMatches(faction_name, configured); @@ -393,12 +237,11 @@ void FilterClaimableDailiesByFaction(IList* list, const std::vector spdlog::debug("[DailyFactionBulkClaim] kept {}/{} daily(ies) for claiming", kept, total); } -IList* MissionViewController_GetClaimableDailiesList(auto original, Il2CppObject* _this) +TournamentList* MissionViewController_GetClaimableDailiesList(auto original, MissionViewController* _this) { - auto* list = (IList*)original(_this); + auto* list = original(_this); - auto* controller = (MissionViewController*)_this; - ApplyFactionIconVisibility(GetTransformOf(controller->ClaimAllToggle())); + ApplyFactionIconVisibility(GetTransformOf(_this->ClaimAllToggle())); const auto& configured = Config::Get().daily_bulk_claim_factions; if (configured.empty()) { @@ -409,20 +252,16 @@ IList* MissionViewController_GetClaimableDailiesList(auto original, Il2CppObject return list; } -void MissionViewController_AboutToShow(auto original, Il2CppObject* _this) +void MissionViewController_AboutToShow(auto original, MissionViewController* _this) { original(_this); - auto* controller = (MissionViewController*)_this; - auto* toggle = controller->ClaimAllToggle(); - auto* toggle_tf = GetTransformOf(toggle); - - ApplyFactionIconVisibility(toggle_tf); + ApplyFactionIconVisibility(GetTransformOf(_this->ClaimAllToggle())); g_active_mission_view_controller = _this; } -void MissionViewController_AboutToHide(auto original, Il2CppObject* _this) +void MissionViewController_AboutToHide(auto original, MissionViewController* _this) { if (g_active_mission_view_controller == _this) { g_active_mission_view_controller = nullptr; @@ -435,65 +274,17 @@ void MissionViewController_AboutToHide(auto original, Il2CppObject* _this) void DailyFactionBulkClaimUpdate() { if (g_active_mission_view_controller != nullptr) { - ApplyDefaultToggleOnOnce((MissionViewController*)g_active_mission_view_controller); + ApplyDefaultToggleOnOnce(g_active_mission_view_controller); } } void InstallDailyFactionBulkClaimHooks() { - auto resource_spec = ResourceSpec::ClassHelper(); - if (!resource_spec.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.PrimeServer.Models", "ResourceSpec"); - } else if (!resource_spec.GetField("resourceId_").isValidHelper()) { - ErrorMsg::MissingMethod("ResourceSpec", "resourceId_"); - } - - auto inventory_item = InventoryItem::ClassHelper(); - if (!inventory_item.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.PrimeServer.Models", "InventoryItem"); - } else if (!inventory_item.GetField("_resourceSpec").isValidHelper()) { - ErrorMsg::MissingMethod("InventoryItem", "_resourceSpec"); - } - - auto unity_toggle = UnityToggle::ClassHelper(); - if (!unity_toggle.isValidHelper()) { - ErrorMsg::MissingHelper("UnityEngine.UI", "Toggle"); - } else { - if (unity_toggle.GetMethodInfo("get_isOn", 0) == nullptr) { - ErrorMsg::MissingMethod("Toggle", "get_isOn"); - } - if (unity_toggle.GetMethodInfoSpecial("OnPointerClick", [](auto count, auto) { return count == 1; }) - == nullptr) { - ErrorMsg::MissingMethod("Toggle", "OnPointerClick"); - } - } - - if (il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.EventSystems", "PointerEventData").get_cls() - == nullptr) { - ErrorMsg::MissingHelper("UnityEngine.EventSystems", "PointerEventData"); - } - - auto mission_view_controller = MissionViewController::ClassHelper(); - if (!mission_view_controller.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.Missions", "MissionViewController"); - return; - } - - if (auto ptr = mission_view_controller.GetMethod("GetClaimableDailiesList", 0); ptr == nullptr) { - ErrorMsg::MissingMethod("MissionViewController", "GetClaimableDailiesList"); - } else { - SPUD_STATIC_DETOUR(ptr, MissionViewController_GetClaimableDailiesList); - } - - if (auto ptr = mission_view_controller.GetMethod("AboutToShow", 0); ptr == nullptr) { - ErrorMsg::MissingMethod("MissionViewController", "AboutToShow"); - } else { - SPUD_STATIC_DETOUR(ptr, MissionViewController_AboutToShow); - } - - if (auto ptr = mission_view_controller.GetMethod("AboutToHide", 0); ptr == nullptr) { - ErrorMsg::MissingMethod("MissionViewController", "AboutToHide"); - } else { - SPUD_STATIC_DETOUR(ptr, MissionViewController_AboutToHide); - } + using Controller = MissionViewController::Class; + if (auto m = Controller::Method("GetClaimableDailiesList", 0)) + SPUD_STATIC_DETOUR(m.Pointer(), MissionViewController_GetClaimableDailiesList); + if (auto m = Controller::Method("AboutToShow", 0)) + SPUD_STATIC_DETOUR(m.Pointer(), MissionViewController_AboutToShow); + if (auto m = Controller::Method("AboutToHide", 0)) + SPUD_STATIC_DETOUR(m.Pointer(), MissionViewController_AboutToHide); } diff --git a/mods/src/patches/parts/disable_banners.cc b/mods/src/patches/parts/disable_banners.cc index 555c6d25b..e7be0daf1 100644 --- a/mods/src/patches/parts/disable_banners.cc +++ b/mods/src/patches/parts/disable_banners.cc @@ -1,13 +1,13 @@ #include "config.h" -#include "errormsg.h" #include "patches/notification_service.h" -#include +#include #include #include -struct ToastObserver { +struct ToastObserver : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.HUD", "ToastObserver">; }; void ToastObserver_EnqueueToast_Hook(auto original, ToastObserver *_this, Toast *toast) @@ -38,20 +38,8 @@ void InstallToastBannerHooks() { notification_init(); - if (auto helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.HUD", "ToastObserver"); - !helper.isValidHelper()) { - ErrorMsg::MissingHelper("HUD", "ToastObserver"); - } else { - if (const auto ptr = helper.GetMethod("EnqueueToast"); ptr == nullptr) { - ErrorMsg::MissingMethod("ToastObserver", "EnqueueToast"); - } else { - SPUD_STATIC_DETOUR(ptr, ToastObserver_EnqueueToast_Hook); - } - - if (const auto ptr = helper.GetMethod("EnqueueOrCombineToast"); ptr == nullptr) { - ErrorMsg::MissingMethod("ToastObserver", "EnqueueOrCombineToast"); - } else { - SPUD_STATIC_DETOUR(ptr, ToastObserver_EnqueueOrCombineToast_Hook); - } - } + if (auto m = ToastObserver::Class::Method("EnqueueToast")) + SPUD_STATIC_DETOUR(m.Pointer(), ToastObserver_EnqueueToast_Hook); + if (auto m = ToastObserver::Class::Method("EnqueueOrCombineToast")) + SPUD_STATIC_DETOUR(m.Pointer(), ToastObserver_EnqueueOrCombineToast_Hook); } diff --git a/mods/src/patches/parts/double_click_assign_ship.cc b/mods/src/patches/parts/double_click_assign_ship.cc index 1ee4d764e..9a2058d8a 100644 --- a/mods/src/patches/parts/double_click_assign_ship.cc +++ b/mods/src/patches/parts/double_click_assign_ship.cc @@ -1,5 +1,6 @@ #include "config.h" #include "patches/key.h" +#include "patches/object_tracker.h" #include "double_click_assign_ship.h" #include "prime/AssignShipsWidget.h" @@ -7,7 +8,8 @@ #include "prime/KeyCode.h" #include "ship_tile_click.h" -#include +#include +#include "patches/object_tracker.h" #include @@ -24,12 +26,12 @@ void PressAssignButton() if (!widget) continue; auto canvas = GetCanvasControllerFromComponent(widget); - if (!canvas || !canvas->Visible() || !widget->isActiveAndEnabled) continue; + if (!canvas || !canvas->Visible() || !widget->isActiveAndEnabled()) continue; - auto* buttonWrapper = widget->_assignButton; - auto* buttonWidget = buttonWrapper ? buttonWrapper->Widget : nullptr; - auto* listener = buttonWidget ? buttonWidget->SemaphoreListener : nullptr; - auto* button = listener ? listener->TheButton : nullptr; + auto* buttonWrapper = widget->assignButton(); + auto* buttonWidget = buttonWrapper ? buttonWrapper->Widget() : nullptr; + auto* listener = buttonWidget ? buttonWidget->SemaphoreListener() : nullptr; + auto* button = listener ? listener->TheButton() : nullptr; if (button) { button->Press(); } diff --git a/mods/src/patches/parts/fix_pan.cc b/mods/src/patches/parts/fix_pan.cc index 569e0c8e6..2956718f1 100644 --- a/mods/src/patches/parts/fix_pan.cc +++ b/mods/src/patches/parts/fix_pan.cc @@ -1,11 +1,12 @@ #include "config.h" -#include "errormsg.h" -#include +#include +#include #include #include #include +#include #include #include @@ -17,75 +18,80 @@ TKTouch *TKTouch_populateWithPosition_Hook(auto original, TKTouch *_this, uintptr_t pos, TouchPhase phase) { auto r = original(_this, pos, phase); - if (r->phase == TouchPhase::Stationary) { - r->phase = TouchPhase::Moved; + if (r->phase() == TouchPhase::Stationary) { + r->SetPhase(TouchPhase::Moved); } return r; } +namespace +{ +struct TutorialManager : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Tutorial", "TutorialManager">; + + bool IsSceneInputAllowed() + { return Call<"IsSceneInputAllowed", bool>(); } +}; +} // namespace + // Retain the native tutorial and modal/text-input gates while replacing the // keyboard source through the existing LateUpdate hook, without new detours. bool KeyboardPanAllowed() { - static auto hub = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Core", "Hub"); - static auto tutorial = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Tutorial", "TutorialManager"); - static auto shortcuts = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.GameInput", "ShortcutsManager"); - static auto getTutorial = hub.GetMethod("get_TutorialManager", 0); - static auto sceneAllowed = tutorial.GetMethod("IsSceneInputAllowed", 0); - static auto shortcutsAllowed = shortcuts.GetMethod("get_CanUseShortcuts", 0); - if (!getTutorial || !sceneAllowed || !shortcutsAllowed) - return false; - auto* manager = getTutorial(); - return manager && sceneAllowed(manager) && shortcutsAllowed() && !Key::IsInputFocused(); + auto* manager = Hub::Class::Get<"TutorialManager", TutorialManager*>(); + return manager && manager->IsSceneInputAllowed() && ShortcutsManager::CanUseShortcuts() && !Key::IsInputFocused(); } bool NavigationPan_LateUpdate_Hook(auto original, NavigationPan* _this) { - auto d = _this->_lastDelta; + auto d = _this->lastDelta(); const auto& config = Config::Get(); const bool disable_target_camera_lock = config.disable_target_camera_lock; - if (disable_target_camera_lock && _this->_trackingPOI) { - _this->_trackingPOI = nullptr; + if (disable_target_camera_lock && _this->trackingPOI()) { + _this->SetTrackingPOI(nullptr); } - static auto GetMouseButton = il2cpp_resolve_icall_typed("UnityEngine.Input::GetMouseButton(System.Int32)"); - static auto GetTouchCount = il2cpp_resolve_icall_typed("UnityEngine.Input::get_touchCount()"); + static auto GetMouseButton = il2cpp::ResolveICall("UnityEngine.Input::GetMouseButton(System.Int32)"); + static auto GetTouchCount = il2cpp::ResolveICall("UnityEngine.Input::get_touchCount()"); // The native recognizer owns held-drag movement, including when the legacy // Input API does not report the pointer. Never replay its delta as momentum. - const bool dragging = GetMouseButton(0) || GetTouchCount() > 0 || _this->_panningStarted; + const bool dragging = GetMouseButton(0) || GetTouchCount() > 0 || _this->panningStarted(); bool by_keys = false; bool native_handled = false; if (!config.disable_move_keys && config.hotkeys_enabled && !Key::IsDirectionalInputClaimed()) { if (config.use_scopely_hotkeys || !config.installHotkeyHooks) { original(_this); + d = _this->lastDelta(); native_handled = true; - } else if (!_this->BlockPan() && !_this->_trackingPOI && !dragging - && KeyboardPanAllowed()) { + } else if (!_this->BlockPan() && !_this->trackingPOI() && !dragging && KeyboardPanAllowed()) { // Opposite bindings cancel; normalize diagonals like the native 2D composite. float x = float(MapKey::IsPressed(GameFunction::MoveRight)) - float(MapKey::IsPressed(GameFunction::MoveLeft)); float y = float(MapKey::IsPressed(GameFunction::MoveUp)) - float(MapKey::IsPressed(GameFunction::MoveDown)); const float length = std::sqrt(x * x + y * y); if (length > 0.0f) { - d->x = -x / length * _this->_keyboardDeltaScaler; - d->y = -y / length * _this->_keyboardDeltaScaler; - _this->MoveCamera(vec2{d->x * _this->_deltaScaler, d->y * _this->_deltaScaler}, true); + d.x = -x / length * _this->keyboardDeltaScaler(); + d.y = -y / length * _this->keyboardDeltaScaler(); + _this->SetLastDelta(d); + _this->MoveCamera(vec2{d.x * _this->deltaScaler(), d.y * _this->deltaScaler()}, true); by_keys = true; } } } - if (_this->BlockPan() || (_this->_trackingPOI && !disable_target_camera_lock)) { - d->x = 0.0f; - d->y = 0.0f; + if (_this->BlockPan() || (_this->trackingPOI() && !disable_target_camera_lock)) { + d.x = 0.0f; + d.y = 0.0f; } else if (!dragging && !by_keys) { - d->x = d->x * Config::Get().system_pan_momentum_falloff; - d->y = d->y * Config::Get().system_pan_momentum_falloff; + d.x = d.x * Config::Get().system_pan_momentum_falloff; + d.y = d.y * Config::Get().system_pan_momentum_falloff; + _this->SetLastDelta(d); if (!native_handled) { - _this->MoveCamera(vec2{d->x, d->y}, true); + _this->MoveCamera(vec2{d.x, d.y}, true); } } - _this->_farMagRadiusRatioSystemExtended = _this->_farMagRadiusRatioSystemNormal; + _this->SetLastDelta(d); + _this->SetFarMagRadiusRatioSystemExtended(_this->farMagRadiusRatioSystemNormal()); return true; } @@ -95,50 +101,30 @@ void OrbitFrameProvider_UpdateInputData_Hook(auto original, OrbitFrameProvider* auto section_manager = Hub::get_SectionManager(); if (!Config::Get().hotkeys_enabled || Config::Get().use_scopely_hotkeys || Config::Get().disable_move_keys - || !section_manager || section_manager->CurrentSection != SectionID::Starbase_Exterior || Key::IsInputFocused()) { + || !section_manager || section_manager->CurrentSection() != SectionID::Starbase_Exterior || Key::IsInputFocused()) { return; } - static auto GetDeltaTime = il2cpp_resolve_icall_typed("UnityEngine.Time::get_deltaTime()"); + static auto GetDeltaTime = il2cpp::ResolveICall("UnityEngine.Time::get_deltaTime()"); constexpr float keyboard_rotation_speed = 45.0f; const auto frame_delta = keyboard_rotation_speed * GetDeltaTime(); if (MapKey::IsPressed(GameFunction::MoveLeft)) { - _this->RotationAngleDelta() -= frame_delta; + _this->SetRotationAngleDelta(_this->RotationAngleDelta() - frame_delta); } if (MapKey::IsPressed(GameFunction::MoveRight)) { - _this->RotationAngleDelta() += frame_delta; + _this->SetRotationAngleDelta(_this->RotationAngleDelta() + frame_delta); } } void InstallPanHooks() { - if (auto& orbit_helper = OrbitFrameProvider::get_class_helper(); !orbit_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Client.CameraController", "OrbitFrameProvider"); - } else if (const auto ptr = orbit_helper.GetMethod("UpdateInputData"); ptr == nullptr) { - ErrorMsg::MissingMethod("OrbitFrameProvider", "UpdateInputData"); - } else { - SPUD_STATIC_DETOUR(ptr, OrbitFrameProvider_UpdateInputData_Hook); - } + if (auto method = OrbitFrameProvider::Class::Method("UpdateInputData")) + SPUD_STATIC_DETOUR(method.Pointer(), OrbitFrameProvider_UpdateInputData_Hook); - if (auto touchHelper = il2cpp_get_class_helper("TouchKit", "", "TKTouch"); !touchHelper.isValidHelper()) { - ErrorMsg::MissingHelper("", "TKTouch"); - } else { - if (const auto ptr = touchHelper.GetMethod("populateWithPosition"); ptr == nullptr) { - ErrorMsg::MissingMethod("TKTouch", "populateWithPosition"); - } else { - SPUD_STATIC_DETOUR(ptr, TKTouch_populateWithPosition_Hook); - } - } + if (auto method = TKTouch::Class::Method("populateWithPosition")) + SPUD_STATIC_DETOUR(method.Pointer(), TKTouch_populateWithPosition_Hook); - if (auto navHelper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationPan"); - !navHelper.isValidHelper()) { - ErrorMsg::MissingHelper("Navigation", "NavigationPan"); - } else { - if (const auto ptr = navHelper.GetMethod("LateUpdate"); ptr == nullptr) { - ErrorMsg::MissingMethod("NavigationPan", "LateUpdate"); - } else { - SPUD_STATIC_DETOUR(ptr, NavigationPan_LateUpdate_Hook); - } - } + if (auto method = NavigationPan::Class::Method("LateUpdate")) + SPUD_STATIC_DETOUR(method.Pointer(), NavigationPan_LateUpdate_Hook); } diff --git a/mods/src/patches/parts/focus_search.cc b/mods/src/patches/parts/focus_search.cc index b35893315..076ca5a4d 100644 --- a/mods/src/patches/parts/focus_search.cc +++ b/mods/src/patches/parts/focus_search.cc @@ -1,4 +1,5 @@ #include "focus_search.h" +#include "patches/object_tracker.h" #include "config.h" @@ -8,7 +9,7 @@ #include "prime/InventoryListViewController.h" #include "prime/OfficerAssignmentViewController.h" -#include "il2cpp/il2cpp_helper.h" +#include #include @@ -19,7 +20,7 @@ bool FocusSearchBox() } // Guard chat input so we don't steal focus from the chat text box - if (auto chat = ObjectFinder::Get(); chat && chat->_inputFieldSelected) { + if (auto chat = ObjectFinder::Get(); chat && chat->inputFieldSelected()) { #ifdef _MODDBG spdlog::info("[FocusSearch] blocked: chat input is selected"); #endif @@ -29,21 +30,21 @@ bool FocusSearchBox() // InventoryListViewController covers OfficerRosterViewController and other inventory subclasses auto inventoryControllers = ObjectFinder::GetAll(); for (auto controller : inventoryControllers) { - if (!controller || !controller->_inputField) { + if (!controller || !controller->inputField()) { continue; } - auto canvas = controller->canvasController; + auto canvas = controller->canvasController(); bool visible = canvas && canvas->Visible(); - bool active = controller->isActiveAndEnabled && controller->_inputField->isActiveAndEnabled; + bool active = controller->isActiveAndEnabled() && controller->inputField()->isActiveAndEnabled(); spdlog::debug("[FocusSearch] inventory controller={} section={} canvas={} visible={} active={}", (void*)controller, - (int32_t)controller->_targetSection, (void*)canvas, visible, active); + (int32_t)controller->targetSection(), (void*)canvas, visible, active); if (visible && active) { #ifdef _MODDBG spdlog::info("[FocusSearch] focusing visible inventory search"); #endif - controller->_inputField->Focus(); + controller->inputField()->Focus(); return true; } } @@ -51,13 +52,13 @@ bool FocusSearchBox() // OfficerAssignmentViewController (separate from inventory list) auto officerControllers = ObjectFinder::GetAll(); for (auto controller : officerControllers) { - if (!controller || !controller->_inputField) { + if (!controller || !controller->inputField()) { continue; } - auto canvas = controller->canvasController; + auto canvas = controller->canvasController(); bool visible = canvas && canvas->Visible(); - bool active = controller->isActiveAndEnabled && controller->_inputField->isActiveAndEnabled; + bool active = controller->isActiveAndEnabled() && controller->inputField()->isActiveAndEnabled(); spdlog::debug("[FocusSearch] officer assignment controller={} canvas={} visible={} active={}", (void*)controller, (void*)canvas, visible, active); @@ -65,7 +66,7 @@ bool FocusSearchBox() #ifdef _MODDBG spdlog::info("[FocusSearch] focusing visible officer assignment search"); #endif - controller->_inputField->Focus(); + controller->inputField()->Focus(); return true; } } @@ -73,13 +74,13 @@ bool FocusSearchBox() // AssignShipsWidget (ship selection / assignment) auto assignShipWidgets = ObjectFinder::GetAll(); for (auto widget : assignShipWidgets) { - if (!widget || !widget->_inputField) { + if (!widget || !widget->inputField()) { continue; } auto canvas = GetCanvasControllerFromComponent(widget); bool visible = canvas && canvas->Visible(); - bool active = widget->isActiveAndEnabled && widget->_inputField->isActiveAndEnabled; + bool active = widget->isActiveAndEnabled() && widget->inputField()->isActiveAndEnabled(); spdlog::debug("[FocusSearch] assign ships widget={} canvas={} visible={} active={}", (void*)widget, (void*)canvas, visible, active); @@ -87,7 +88,7 @@ bool FocusSearchBox() #ifdef _MODDBG spdlog::info("[FocusSearch] focusing visible assign ships search"); #endif - widget->_inputField->Focus(); + widget->inputField()->Focus(); return true; } } diff --git a/mods/src/patches/parts/forbidden_tech_confirmation.cc b/mods/src/patches/parts/forbidden_tech_confirmation.cc index 11d03980c..2c67c5533 100644 --- a/mods/src/patches/parts/forbidden_tech_confirmation.cc +++ b/mods/src/patches/parts/forbidden_tech_confirmation.cc @@ -1,9 +1,8 @@ -#include "errormsg.h" #include "config.h" -#include -#include +#include #include +#include #include @@ -12,7 +11,15 @@ namespace constexpr int32_t ConfirmButtonResult = 1; constexpr int32_t YesButtonResult = 3; -const FieldInfo* on_selection_field = nullptr; +using MessageBox = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "MessageBox">; + +struct MessageBoxContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "MessageBoxContext">; + + // Action + Il2CppDelegate* OnSelection() + { return Field<"OnSelection", Il2CppDelegate*>(); } +}; bool IsForbiddenTechConfirmation(const Il2CppDelegate* callback) { @@ -46,16 +53,18 @@ bool ConfirmForbiddenTechUpgrade(Il2CppDelegate* callback) return false; } - int32_t result = ConfirmButtonResult; - void* args[] = {&result}; - Il2CppException* exception = nullptr; - il2cpp_runtime_invoke(callback->method, callback->target, args, &exception); - - if (exception != nullptr) { - spdlog::warn("ForbiddenTechConfirmation: failed to invoke the confirmation callback"); + // The closure was identified above, so call its method directly. If it throws, show the dialog + // instead so the upgrade is never lost silently. + using Closure = void (*)(Il2CppObject*, int32_t, const MethodInfo*); + if (callback->method->parameters_count != 1) + return false; + try { + reinterpret_cast(callback->method->methodPointer)(callback->target, ConfirmButtonResult, + callback->method); + } catch (...) { + spdlog::warn("ForbiddenTechConfirmation: confirmation callback threw; showing the dialog"); return false; } - return true; } @@ -79,23 +88,14 @@ bool ConfirmAllianceContribution(Il2CppDelegate* callback) return true; } -Il2CppDelegate* GetSelectionCallback(void* context) +void MessageBox_Show_Hook(auto original, MessageBoxContext* context) { - if (context == nullptr || on_selection_field == nullptr) { - return nullptr; - } - - return *reinterpret_cast(reinterpret_cast(context) + on_selection_field->offset); -} - -void MessageBox_Show_Hook(auto original, void* context) -{ - if (!ConfirmForbiddenTechUpgrade(GetSelectionCallback(context))) { + if (!context || !ConfirmForbiddenTechUpgrade(context->OnSelection())) { original(context); } } -void MessageBox_ShowWithCallback_Hook(auto original, void* context, Il2CppDelegate* callback) +void MessageBox_ShowWithCallback_Hook(auto original, MessageBoxContext* context, Il2CppDelegate* callback) { if (!ConfirmForbiddenTechUpgrade(callback) && !ConfirmAllianceContribution(callback)) { original(context, callback); @@ -105,35 +105,8 @@ void MessageBox_ShowWithCallback_Hook(auto original, void* context, Il2CppDelega void InstallForbiddenTechConfirmationHooks() { - auto message_box_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "MessageBox"); - if (!message_box_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Client.UI", "MessageBox"); - return; - } - - auto context_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "MessageBoxContext"); - if (!context_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Client.UI", "MessageBoxContext"); - return; - } - - on_selection_field = il2cpp_class_get_field_from_name(context_helper.get_cls(), "OnSelection"); - if (on_selection_field == nullptr) { - spdlog::error("Unable to find field 'MessageBoxContext->OnSelection'"); - return; - } - - const auto show = message_box_helper.GetMethod("Show", 1); - if (show == nullptr) { - ErrorMsg::MissingMethod("MessageBox", "Show"); - } else { - SPUD_STATIC_DETOUR(show, MessageBox_Show_Hook); - } - - const auto show_with_callback = message_box_helper.GetMethod("Show", 2); - if (show_with_callback == nullptr) { - ErrorMsg::MissingMethod("MessageBox", "Show"); - } else { - SPUD_STATIC_DETOUR(show_with_callback, MessageBox_ShowWithCallback_Hook); - } + if (auto m = MessageBox::Method("Show", 1)) + SPUD_STATIC_DETOUR(m.Pointer(), MessageBox_Show_Hook); + if (auto m = MessageBox::Method("Show", 2)) + SPUD_STATIC_DETOUR(m.Pointer(), MessageBox_ShowWithCallback_Hook); } diff --git a/mods/src/patches/parts/forbidden_tech_ship_name.cc b/mods/src/patches/parts/forbidden_tech_ship_name.cc index 44faf1242..eff78d869 100644 --- a/mods/src/patches/parts/forbidden_tech_ship_name.cc +++ b/mods/src/patches/parts/forbidden_tech_ship_name.cc @@ -1,219 +1,260 @@ #include "config.h" #include "ship_name_match.h" -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include #include #include #include +#include #include #include namespace { -struct Point { - float x, y; -}; constexpr float RowHeight = 30.0f; -// All value/byref calls below use runtime invocation: no platform-specific struct-return ABI. -Il2CppObject* Call(Il2CppObject* target, const char* name, int count = 0, void** args = nullptr) -{ - auto* method = target ? IL2CppClassHelper(target->klass).GetMethodInfo(name, count) : nullptr; - Il2CppObject* result = nullptr; - if (!method || !Il2CppRuntime::TryInvoke(method, target, args, &result)) - throw std::runtime_error(std::string("Forbidden Tech method unavailable: ") + name); - return result; -} +using UnityObject = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Object">; -template T Value(Il2CppObject* boxed, const char* type) -{ - if (!boxed || std::strcmp(boxed->klass->name, type) != 0) - throw std::runtime_error("Forbidden Tech value contract changed"); - return *static_cast(il2cpp_object_unbox(boxed)); -} +struct ForbiddenTech : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "ForbiddenTech">; -bool Alive(Il2CppObject* object) -{ - if (!object) - return false; - static auto helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Object"); - static auto* method = helper.GetMethodInfo("op_Implicit", 1); - void* args[] = {object}; - Il2CppObject* result = nullptr; - return Il2CppRuntime::TryInvoke(method, nullptr, args, &result) && Il2CppChecked::Boolean(result); -} + int64_t Id() + { return Get<"Id", int64_t>(); } +}; -Point GetPoint(Il2CppObject* rect, const char* property) -{ return Value(Call(rect, property), "Vector2"); } +struct ForbiddenTechInventoryItem : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "InventoryItem">; -void SetPoint(Il2CppObject* rect, const char* property, Point value) -{ - void* args[] = {&value}; - Call(rect, property, 1, args); -} + ForbiddenTech* forbiddenTech() + { return Get<"ForbiddenTech", ForbiddenTech*>(); } +}; + +struct ForbiddenTechSlotParams : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "ForbiddenTechSlotParams">; + + int64_t OwnerId() + { return Get<"OwnerId", int64_t>(); } +}; + +struct ForbiddenTechEntitySlot : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "EntitySlot">; + + ForbiddenTechSlotParams* forbiddenTechSlotParams() + { return Get<"ForbiddenTechSlotParams", ForbiddenTechSlotParams*>(); } +}; + +// GetShip returns an IShipModel, read on its runtime class. +struct ForbiddenTechFleetService : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "FleetService">; -void SetActive(Il2CppObject* component, bool active) + il2cpp::Object* GetShip(int64_t ship_id) + { return Call<"GetShip", il2cpp::Object*>(ship_id); } +}; + +struct ForbiddenTechManager : MonoSingleton { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ForbiddenTechs", "ForbiddenTechManager">; + + bool TryGetEntitySlotFromForbiddenTech(int64_t tech_id, ForbiddenTechEntitySlot*& slot) + { return Call<"TryGetEntitySlotFromForbiddenTech", bool>(tech_id, std::ref(slot)); } + + // _fleetServiceCache is a CachedService struct whose Service getter resolves the + // service lazily. The getter runs on a copy, so the manager's own cache is left as it was; the + // runtime takes the struct receiver unboxed. + ForbiddenTechFleetService* fleetService() + { + struct CachedService { + ForbiddenTechFleetService* service; + }; + auto cache = Field<"_fleetServiceCache", CachedService>(); + if (cache.service) + return cache.service; + static FieldInfo* const field = + Class::Raw() ? il2cpp_class_get_field_from_name(Class::Raw(), "_fleetServiceCache") : nullptr; + static const MethodInfo* const getter = + field ? il2cpp_class_get_method_from_name(il2cpp_class_from_type(il2cpp_field_get_type(field)), "get_Service", + 0) + : nullptr; + if (!getter) + return nullptr; + return reinterpret_cast(il2cpp::detail::InvokeBoxed(getter, &cache, nullptr)); + } +}; + +struct ForbiddenTechInventoryItemWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ForbiddenTechs", "ForbiddenTechInventoryItemWidget">; + + // ForbiddenTechInventoryItemWidget.WidgetType; 1 is the large selected-item panel. + int32_t widgetType() + { return Field<"_widgetType", int32_t>(); } + // SmartScroller + Component* buffListScroller() + { return Field<"_buffListScroller", Component*>(); } + TextLocalizer* name() + { return Field<"_name", TextLocalizer*>(); } +}; + +struct TMP_Text : il2cpp::Object { + using Class = il2cpp::Class<"Unity.TextMeshPro", "TMPro", "TMP_Text">; + + // TextAlignmentOptions + void SetAlignment(int32_t value) + { Set<"alignment">(value); } + void SetRaycastTarget(bool value) + { Set<"raycastTarget">(value); } + void SetRichText(bool value) + { Set<"richText">(value); } + void SetEnableWordWrapping(bool value) + { Set<"enableWordWrapping">(value); } + // TextOverflowModes + void SetOverflowMode(int32_t value) + { Set<"overflowMode">(value); } + void SetEnableAutoSizing(bool value) + { Set<"enableAutoSizing">(value); } + void SetFontSize(float value) + { Set<"fontSize">(value); } + void SetColor(color value) + { Set<"color">(value); } +}; + +// Unity's `bool(UnityEngine.Object)`: false for null and for destroyed native objects. +bool Alive(il2cpp::Object* object) +{ return object && UnityObject::Call<"op_Implicit", bool>(object); } + +RectTransform* RectOf(il2cpp::Object* component) { - auto* object = Call(component, "get_gameObject"); - void* args[] = {&active}; - Call(object, "SetActive", 1, args); + auto* as_component = component ? component->As() : nullptr; + auto* transform = as_component ? as_component->transform() : nullptr; + return transform ? transform->As() : nullptr; } -Il2CppObject* Manager() +void SetActive(il2cpp::Object* component, bool active) { - static auto helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ForbiddenTechs", "ForbiddenTechManager"); - static auto parent = helper.GetParent("MonoSingleton`1"); - static auto* instance = parent.GetMethodInfo("get_Instance", 0); - Il2CppObject* result = nullptr; - if (!Il2CppRuntime::TryInvoke(instance, nullptr, nullptr, &result)) - throw std::runtime_error("Forbidden Tech manager unavailable"); - return result; + auto* as_component = component->As(); + auto* object = as_component ? as_component->gameObject() : nullptr; + if (!object) + throw std::runtime_error("Forbidden Tech label object unavailable"); + object->SetActive(active); } -std::string EquippedShipName(Il2CppObject* widget) +std::string EquippedShipName(ForbiddenTechInventoryItemWidget* widget) { - auto* context = Call(widget, "get_Context"); - auto* tech = context ? Il2CppChecked::Invoke(context, "get_ForbiddenTech") : nullptr; + auto* context = widget->Context(); + auto* tech = context ? context->forbiddenTech() : nullptr; if (!tech) return {}; - auto id = Value(Call(tech, "get_Id"), "Int64"); - auto* manager = Manager(); + const auto id = tech->Id(); + auto* manager = ForbiddenTechManager::Instance(); if (!manager) return {}; // The fleet-index API returns -1 for a ship in the hangar. Resolve the slot's // OwnerId instead; it identifies the actual ship regardless of dock assignment. - Il2CppObject* slot = nullptr; - void* slot_args[] = {&id, &slot}; - if (!Il2CppChecked::Boolean(Call(manager, "TryGetEntitySlotFromForbiddenTech", 2, slot_args)) || !slot) + ForbiddenTechEntitySlot* slot = nullptr; + if (!manager->TryGetEntitySlotFromForbiddenTech(id, slot) || !slot) return {}; - auto* params = Il2CppChecked::Invoke(slot, "get_ForbiddenTechSlotParams"); + auto* params = slot->forbiddenTechSlotParams(); if (!params) return {}; - auto owner = Value(Call(params, "get_OwnerId"), "Int64"); - - auto* cache_field = Il2CppChecked::Field(manager, "_fleetServiceCache", "CachedService`1"); - auto* cache = il2cpp_field_get_value_object(cache_field, manager); - if (!cache || !il2cpp_class_is_valuetype(cache->klass)) - throw std::runtime_error("Forbidden Tech service cache contract changed"); - auto* getter = IL2CppClassHelper(cache->klass).GetMethodInfo("get_Service", 0); - Il2CppObject* service = nullptr; - if (!Il2CppRuntime::TryInvoke(getter, il2cpp_object_unbox(cache), nullptr, &service) || !service) + const auto owner = params->OwnerId(); + + auto* service = manager->fleetService(); + if (!service) return {}; - void* ship_args[] = {&owner}; - auto* ship = Call(service, "GetShip", 1, ship_args); - auto* hull = ship ? Il2CppChecked::Invoke(ship, "get_Hull") : nullptr; + auto* ship = service->GetShip(owner); + auto* hull = ship ? ship->Get<"Hull", HullSpec*>() : nullptr; if (!hull) return {}; // Hull IDs and localisation IDs differ on many ships. Mirror FleetPlayerData.GetLocaId. - auto* refs = Il2CppChecked::Invoke(hull, "get_IdRefs"); - const auto loca_id = Value(Call(refs ? refs : hull, refs ? "get_LocaId" : "get_Id"), "Int64"); + auto* refs = hull->idRefs(); + const auto loca_id = refs ? refs->locaId() : hull->Id(); return ShipNameMatch::LocalizedShipName(loca_id); } struct Row { - Il2CppGCHandle scroller = nullptr; - Il2CppGCHandle label = nullptr; - Point original_top{}; - bool visible = false; + il2cpp::GcHandle scroller; + il2cpp::GcHandle label; + Vector2 original_top{}; + bool visible = false; - Il2CppObject* Scroller() const { return il2cpp_gchandle_get_target(scroller); } - Il2CppObject* Label() const { return label ? il2cpp_gchandle_get_target(label) : nullptr; } + RectTransform* Scroller() const + { return scroller.Get(); } + TextLocalizer* Label() const + { return label.Get(); } void Hide() { if (Alive(Label())) SetActive(Label(), false); if (visible && Alive(Scroller())) - SetPoint(Scroller(), "set_offsetMax", original_top); + Scroller()->SetOffsetMax(original_top); visible = false; } - - void Free() - { - if (scroller) - il2cpp_gchandle_free(scroller); - if (label) - il2cpp_gchandle_free(label); - } }; std::vector rows; -void HideWidgetRow(Il2CppObject* widget) +void HideWidgetRow(ForbiddenTechInventoryItemWidget* widget) { - auto* scroller = Il2CppChecked::ReferenceField(widget, "_buffListScroller", "SmartScroller"); + auto* scroller = widget->buffListScroller(); if (!Alive(scroller)) return; - auto* rect = Call(scroller, "get_transform"); + auto* rect = RectOf(scroller); for (auto& row : rows) { if (row.Scroller() == rect) row.Hide(); } } -Il2CppObject* CloneLabel(Il2CppObject* source, Il2CppObject* parent) +TextLocalizer* CloneLabel(TextLocalizer* source, Transform* parent) { - static auto helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Object"); // This client exposes the two-argument overload only as Instantiate. // Use the non-generic three-argument overload so no generic inflation is required. - static auto* instantiate = helper.GetMethodInfoSpecial("Instantiate", [](int count, const Il2CppType** types) { - return count == 3 && types && types[0] && types[1] && types[2] - && !types[0]->byref && !types[1]->byref && !types[2]->byref - && il2cpp_class_from_type(types[0]) - && std::strcmp(il2cpp_class_from_type(types[0])->name, "Object") == 0 - && il2cpp_class_from_type(types[1]) - && std::strcmp(il2cpp_class_from_type(types[1])->name, "Transform") == 0 - && types[2]->type == IL2CPP_TYPE_BOOLEAN; - }); - if (!instantiate || instantiate->is_generic || !(instantiate->flags & METHOD_ATTRIBUTE_STATIC)) - throw std::runtime_error("Forbidden Tech label Instantiate(Object, Transform, Boolean) unavailable"); - bool world_position_stays = false; - void* args[] = {source, parent, &world_position_stays}; - Il2CppObject* result = nullptr; - if (!Il2CppRuntime::TryInvoke(instantiate, nullptr, args, &result) || !Alive(result)) + auto* clone = + UnityObject::Call<"Instantiate", il2cpp::Object*(il2cpp::Object*, Transform*, bool)>(source, parent, false); + if (!Alive(clone)) throw std::runtime_error("Forbidden Tech ship label could not be created"); - return result; + return static_cast(clone); } -void UpdateRow(Il2CppObject* widget) +void UpdateRow(ForbiddenTechInventoryItemWidget* widget) { // Only the large selected-item panel: inventory tiles retain their native dock badges. - auto* type_field = Il2CppChecked::Field(widget, "_widgetType", "WidgetType"); - int32_t type = 0; - il2cpp_field_get_value(widget, type_field, &type); - if (type != 1) + if (widget->widgetType() != 1) return; - auto* scroller = Il2CppChecked::ReferenceField(widget, "_buffListScroller", "SmartScroller"); + auto* scroller = widget->buffListScroller(); if (!Alive(scroller)) return; - auto* rect = Call(scroller, "get_transform"); - if (!rect || std::strcmp(rect->klass->name, "RectTransform") != 0) + auto* rect = RectOf(scroller); + if (!rect) throw std::runtime_error("Forbidden Tech buff layout unavailable"); - std::erase_if(rows, [](Row& row) { - if (Alive(row.Scroller())) - return false; - row.Free(); - return true; - }); + std::erase_if(rows, [](const Row& row) { return !Alive(row.Scroller()); }); auto found = std::find_if(rows.begin(), rows.end(), [rect](const Row& row) { return row.Scroller() == rect; }); if (found == rows.end()) { Row row; - row.original_top = GetPoint(rect, "get_offsetMax"); - row.scroller = il2cpp_gchandle_new_weakref(rect, false); + row.original_top = rect->offsetMax(); + row.scroller = il2cpp::GcHandle::Weak(rect); if (!row.scroller) return; - rows.push_back(row); + rows.push_back(std::move(row)); found = std::prev(rows.end()); } auto& row = *found; // Clear before resolving a new assignment so errors/unassigned tech cannot retain an old ship name. row.Hide(); // Native refresh may have resized the panel since the previous selection. - row.original_top = GetPoint(rect, "get_offsetMax"); + row.original_top = rect->offsetMax(); if (!Config::Get().forbidden_tech_ship_name) return; const auto name = EquippedShipName(widget); @@ -223,63 +264,45 @@ void UpdateRow(Il2CppObject* widget) try { auto* label = row.Label(); if (!Alive(label)) { - auto* source = Il2CppChecked::ReferenceField(widget, "_name", "TextLocalizer"); + auto* source = widget->name(); if (!Alive(source)) return; - label = CloneLabel(source, Call(rect, "get_parent")); + label = CloneLabel(source, rect->parent()); SetActive(label, false); - if (row.label) - il2cpp_gchandle_free(row.label); - row.label = il2cpp_gchandle_new_weakref(label, false); + row.label = il2cpp::GcHandle::Weak(label); if (!row.label) throw std::runtime_error("Forbidden Tech label handle unavailable"); } - auto* label_rect = Call(label, "get_transform"); - const auto min = GetPoint(rect, "get_offsetMin"); - const auto max = row.original_top; - const auto anchors = GetPoint(rect, "get_anchorMax"); - auto anchor_min = GetPoint(rect, "get_anchorMin"); - anchor_min.y = anchors.y; - SetPoint(label_rect, "set_anchorMin", anchor_min); - SetPoint(label_rect, "set_anchorMax", anchors); - SetPoint(label_rect, "set_pivot", {0.5f, 1.0f}); - SetPoint(label_rect, "set_offsetMin", {min.x, max.y - RowHeight}); - SetPoint(label_rect, "set_offsetMax", max); + auto* label_rect = RectOf(label); + if (!label_rect) + throw std::runtime_error("Forbidden Tech ship label layout unavailable"); + const auto min = rect->offsetMin(); + const auto max = row.original_top; + const auto anchors = rect->anchorMax(); + auto anchor_min = rect->anchorMin(); + anchor_min.y = anchors.y; + label_rect->SetAnchorMin(anchor_min); + label_rect->SetAnchorMax(anchors); + label_rect->SetPivot({0.5f, 1.0f}); + label_rect->SetOffsetMin({min.x, max.y - RowHeight}); + label_rect->SetOffsetMax(max); // Target resolves the graphic lazily, even when the source label was inactive at clone time. - auto* text = Il2CppChecked::Invoke(label, "get_Target"); - static auto tmp = il2cpp_get_class_helper("Unity.TextMeshPro", "TMPro", "TMP_Text"); - if (!text || !tmp.get_cls() || !il2cpp_class_is_assignable_from(tmp.get_cls(), text->klass)) + auto* target = label->Target(); + auto* text = target ? target->As() : nullptr; + if (!text) throw std::runtime_error("Forbidden Tech ship label text unavailable"); - int32_t alignment = 513; // TMP TextAlignmentOptions.Left (left, vertically centred). - bool raycast = false; - bool rich_text = false; // Ship names are plain text, never markup. - bool wrap = false; - bool auto_size = false; - float font_size = 20.0f; - struct { float r, g, b, a; } color{1.0f, 1.0f, 1.0f, 1.0f}; - int32_t overflow = 1; // TMP TextOverflowModes.Ellipsis; long names stay within this row. - void* align_args[] = {&alignment}; - void* raycast_args[] = {&raycast}; - void* rich_args[] = {&rich_text}; - void* wrap_args[] = {&wrap}; - void* overflow_args[] = {&overflow}; - void* auto_size_args[] = {&auto_size}; - void* font_args[] = {&font_size}; - void* color_args[] = {&color}; - Call(text, "set_alignment", 1, align_args); - Call(text, "set_raycastTarget", 1, raycast_args); - Call(text, "set_richText", 1, rich_args); - Call(text, "set_enableWordWrapping", 1, wrap_args); - Call(text, "set_overflowMode", 1, overflow_args); - Call(text, "set_enableAutoSizing", 1, auto_size_args); - Call(text, "set_fontSize", 1, font_args); - Call(text, "set_color", 1, color_args); - auto* value = il2cpp_string_new(("Equipped on: " + name).c_str()); - void* text_args[] = {value}; - Call(label, "OverrideLocalizedText", 1, text_args); + text->SetAlignment(513); // TMP TextAlignmentOptions.Left (left, vertically centred). + text->SetRaycastTarget(false); + text->SetRichText(false); // Ship names are plain text, never markup. + text->SetEnableWordWrapping(false); + text->SetOverflowMode(1); // TMP TextOverflowModes.Ellipsis; long names stay within this row. + text->SetEnableAutoSizing(false); + text->SetFontSize(20.0f); + text->SetColor({1.0f, 1.0f, 1.0f, 1.0f}); + label->OverrideLocalizedText(il2cpp_string_new(("Equipped on: " + name).c_str())); // Keep the panel's outer size, reserving one row from the scrollable buff area. - SetPoint(rect, "set_offsetMax", {max.x, max.y - RowHeight}); + rect->SetOffsetMax({max.x, max.y - RowHeight}); row.visible = true; SetActive(label, true); static bool reported = false; @@ -293,15 +316,15 @@ void UpdateRow(Il2CppObject* widget) } } -void SetWidgetData_Hook(auto original, Il2CppObject* widget) +void ForbiddenTechInventoryItemWidget_SetWidgetData_Hook(auto original, ForbiddenTechInventoryItemWidget* _this) { try { - HideWidgetRow(widget); + HideWidgetRow(_this); } catch (const std::exception&) { } - original(widget); + original(_this); try { - UpdateRow(widget); + UpdateRow(_this); } catch (const std::exception& error) { static bool warned = false; if (!warned) { @@ -311,36 +334,35 @@ void SetWidgetData_Hook(auto original, Il2CppObject* widget) } } -void ClearWidgetData_Hook(auto original, Il2CppObject* widget) +void ForbiddenTechInventoryItemWidget_ClearWidgetData_Hook(auto original, ForbiddenTechInventoryItemWidget* _this) { try { - HideWidgetRow(widget); + HideWidgetRow(_this); } catch (const std::exception&) { } - original(widget); + original(_this); } } // namespace void InstallForbiddenTechShipNameHooks() { - auto helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ForbiddenTechs", "ForbiddenTechInventoryItemWidget"); - auto* method = helper.GetMethodInfo("SetWidgetData", 0); - auto* clear = helper.GetMethodInfo("ClearWidgetData", 0); + auto method = ForbiddenTechInventoryItemWidget::Class::Method("SetWidgetData", 0); + auto clear = ForbiddenTechInventoryItemWidget::Class::Method("ClearWidgetData", 0); // Both methods perform substantive native UI updates, rather than forwarding through tiny wrappers. - const auto valid = [&helper](const MethodInfo* info) { - return info && info->klass == helper.get_cls() && info->methodPointer && !info->is_generic - && !info->is_inflated && !(info->flags & METHOD_ATTRIBUTE_STATIC) && info->parameters_count == 0 - && info->return_type && info->return_type->type == IL2CPP_TYPE_VOID; + // Only the widget's own overrides qualify; the Widget`1 base versions run for every widget. + const auto own = [](il2cpp::Method info) { + return info && info.Info()->klass == ForbiddenTechInventoryItemWidget::Class::Raw(); }; - if (!valid(method) || !valid(clear)) { + if (!own(method) || !own(clear)) { spdlog::warn("[ForbiddenTechShipName] widget refresh unavailable; native display retained"); return; } // Install cleanup first; failure leaves the native panel entirely untouched. - if (!SPUD_STATIC_DETOUR(clear->methodPointer, ClearWidgetData_Hook)) { + if (!SPUD_STATIC_DETOUR(clear.Pointer(), ForbiddenTechInventoryItemWidget_ClearWidgetData_Hook)) { spdlog::warn("[ForbiddenTechShipName] widget cleanup hook unavailable; native display retained"); return; } - const bool installed = SPUD_STATIC_DETOUR(method->methodPointer, SetWidgetData_Hook) != nullptr; + const bool installed = + SPUD_STATIC_DETOUR(method.Pointer(), ForbiddenTechInventoryItemWidget_SetWidgetData_Hook) != nullptr; spdlog::info("[ForbiddenTechShipName] widget refresh hook {}", installed ? "installed" : "unavailable"); } diff --git a/mods/src/patches/parts/free_resize.cc b/mods/src/patches/parts/free_resize.cc index abc1e262f..81706ab0b 100644 --- a/mods/src/patches/parts/free_resize.cc +++ b/mods/src/patches/parts/free_resize.cc @@ -2,13 +2,11 @@ #include #include "config.h" -#include "errormsg.h" #include "file.h" #include #include "prime/AspectRatioConstraintHandler.h" -#include "prime/IList.h" #include "windowtitle.h" static bool WndProcInstalled = false; @@ -101,15 +99,15 @@ struct Resolution { void AspectRatioConstraintHandler_Update(auto original, void* _this) { static auto set_title = true; - static auto get_fullscreen = il2cpp_resolve_icall_typed("UnityEngine.Screen::get_fullScreen()"); - static auto get_height = il2cpp_resolve_icall_typed("UnityEngine.Screen::get_height()"); - static auto get_width = il2cpp_resolve_icall_typed("UnityEngine.Screen::get_width()"); - static auto SetResolution = il2cpp_resolve_icall_typed( + static auto get_fullscreen = il2cpp::ResolveICall("UnityEngine.Screen::get_fullScreen()"); + static auto get_height = il2cpp::ResolveICall("UnityEngine.Screen::get_height()"); + static auto get_width = il2cpp::ResolveICall("UnityEngine.Screen::get_width()"); + static auto SetResolution = il2cpp::ResolveICall( "UnityEngine.Screen::SetResolution(System.Int32,System.Int32,UnityEngine.FullScreenMode,System.Int32)"); if (unityWindow) { if (get_fullscreen()) { - static auto get_currentResolution_Injected = il2cpp_resolve_icall_typed( + static auto get_currentResolution_Injected = il2cpp::ResolveICall( "UnityEngine.Screen::get_currentResolution_Injected(UnityEngine.Resolution&)"); auto height = get_height(); auto width = get_width(); @@ -153,25 +151,12 @@ intptr_t AspectRatioConstraintHandler_WndProc(auto original, HWND hWnd, uint32_t void InstallFreeResizeHooks() { - auto AspectRatioConstraintHandler_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Utils", "AspectRatioConstraintHandler"); - if (!AspectRatioConstraintHandler_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Utils", "AspectRatioConstraintHandler"); - } else { - auto ptr_update = AspectRatioConstraintHandler_helper.GetMethod("Update"); - if (ptr_update == nullptr) { - ErrorMsg::MissingMethod("AspectRatioConstraintHandler", "Update"); - return; - } + auto update = AspectRatioConstraintHandler::Class::Method("Update"); + auto wndproc = AspectRatioConstraintHandler::Class::Method("WndProc"); + if (!update || !wndproc) + return; - auto ptr_wndproc = AspectRatioConstraintHandler_helper.GetMethod("WndProc"); - if (ptr_wndproc == nullptr) { - ErrorMsg::MissingMethod("AspectRatioConstraintHandler", "WndProc"); - return; - } - - SPUD_STATIC_DETOUR(ptr_update, AspectRatioConstraintHandler_Update); - SPUD_STATIC_DETOUR(ptr_wndproc, AspectRatioConstraintHandler_WndProc); - } + SPUD_STATIC_DETOUR(update.Pointer(), AspectRatioConstraintHandler_Update); + SPUD_STATIC_DETOUR(wndproc.Pointer(), AspectRatioConstraintHandler_WndProc); } #endif diff --git a/mods/src/patches/parts/galactic_anomaly_timer.cc b/mods/src/patches/parts/galactic_anomaly_timer.cc index 19e65c399..b1727ff9b 100644 --- a/mods/src/patches/parts/galactic_anomaly_timer.cc +++ b/mods/src/patches/parts/galactic_anomaly_timer.cc @@ -1,376 +1,268 @@ #include "config.h" -#include "errormsg.h" #include "patches/screen_update_hook.h" -#include "prime/Color.h" -#include "prime/Hub.h" -#include "prime/Vector2.h" -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include #include #include -#include #include namespace { // All Unity work runs on ScreenManager's main-thread dispatcher. No additional detours. -// The shared class lookup dereferences missing assemblies. These optional UI -// classes can be absent after a client update, so check each lookup step. -Il2CppClass* Class(const char* assembly, const char* ns, const char* name) -{ - auto* domain = il2cpp_domain_get(); - auto* a = domain ? il2cpp_domain_assembly_open(domain, assembly) : nullptr; - auto* image = a ? il2cpp_assembly_get_image(a) : nullptr; - return image ? il2cpp_class_from_name(image, ns, name) : nullptr; -} - -bool TryInvoke(const MethodInfo* method, void* target, void** args, Il2CppObject** result = nullptr) -{ - if (!method) - return false; - Il2CppException* exception = nullptr; - auto* value = il2cpp_runtime_invoke(method, target, args, &exception); - if (exception) - return false; - if (result) - *result = value; - return true; -} -// Unity has Type/string and generic overloads with the same argument count. -const MethodInfo* TypeMethod(Il2CppClass* cls, const char* name, int argumentCount = 1) -{ - // The shared filtered lookup cannot inspect is_generic and only searches - // one class. Keep the overload check here so a generic overload cannot win. - void* iter = nullptr; - while (cls) { - while (auto* method = il2cpp_class_get_methods(cls, &iter)) { - if (method->is_generic || std::strcmp(method->name, name) || method->parameters_count != argumentCount) - continue; - if (argumentCount == 2) { - auto* second = il2cpp_method_get_param(method, 1); - if (!second || il2cpp_type_is_byref(second) || il2cpp_type_get_type(second) != IL2CPP_TYPE_BOOLEAN) - continue; - } - auto* param = il2cpp_class_from_type(il2cpp_method_get_param(method, 0)); - if (param && !std::strcmp(il2cpp_class_get_namespace(param), "System") - && !std::strcmp(il2cpp_class_get_name(param), "Type")) - return method; - } - cls = il2cpp_class_get_parent(cls); - iter = nullptr; - } - return nullptr; -} +using UnityObject = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Object">; -Il2CppObject* Get(Il2CppObject* object, const char* name) -{ - // Callers pass getter method names. Use the shared property helper for its - // virtual dispatch and exception handling, while accepting absent objects. - if (!object || std::strncmp(name, "get_", 4)) - return nullptr; - return IL2CppClassHelper(object->klass).GetProperty(name + 4).GetRaw(object); -} +struct TimeSpan { + int64_t ticks; +}; -template bool Value(Il2CppObject* object, const char* name, T& value) -{ - // Use the shared property helper for getters. CanShowGalacticAnomaly is an - // ordinary method, so invoke it directly. Check either boxed result's size. - if (!object) - return false; - Il2CppObject* boxed = nullptr; - if (!std::strncmp(name, "get_", 4)) - boxed = Get(object, name); - else if (!TryInvoke(IL2CppClassHelper(object->klass).GetMethodInfo(name, 0), object, nullptr, &boxed)) - return false; - uint32_t alignment = 0; - if (!boxed || !il2cpp_class_is_valuetype(boxed->klass) - || il2cpp_class_value_size(boxed->klass, &alignment) != sizeof(T)) - return false; - std::memcpy(&value, il2cpp_object_unbox(boxed), sizeof(T)); - return true; -} +struct TextMeshProUGUI : Component { + using Class = il2cpp::Class<"Unity.TextMeshPro", "TMPro", "TextMeshProUGUI">; + + // TMP_FontAsset + il2cpp::Object* font() + { return Get<"font", il2cpp::Object*>(); } + void SetFont(il2cpp::Object* value) + { Set<"font">(value); } + void SetFontSize(float value) + { Set<"fontSize">(value); } + // TextAlignmentOptions + void SetAlignment(int32_t value) + { Set<"alignment">(value); } + void SetColor(color value) + { Set<"color">(value); } + void SetRaycastTarget(bool value) + { Set<"raycastTarget">(value); } + void SetEnableWordWrapping(bool value) + { Set<"enableWordWrapping">(value); } + void SetText(Il2CppString* value) + { Set<"text">(value); } +}; -bool Set(Il2CppObject* object, const char* name, void* value) -{ - void* args[] = {value}; - return object && TryInvoke(IL2CppClassHelper(object->klass).GetMethodInfo(name, 1), object, args); -} +struct FactionInformation : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.HUD", "FactionInformation">; -Il2CppObject* Field(Il2CppObject* object, const char* name) -{ - auto* field = object ? il2cpp_class_get_field_from_name(object->klass, name) : nullptr; - if (!field || il2cpp_type_is_byref(il2cpp_field_get_type(field))) - return nullptr; - auto* cls = il2cpp_class_from_type(il2cpp_field_get_type(field)); - if (!cls || il2cpp_class_is_valuetype(cls)) - return nullptr; - Il2CppObject* result = nullptr; - il2cpp_field_get_value(object, field, &result); - return result; -} + // TextLocalizer components, one per system name label. + il2cpp::List* systemName() + { return Field<"_systemName", il2cpp::List*>(); } +}; -Il2CppObject* WithType(const MethodInfo* method, Il2CppObject* target, Il2CppClass* type) -{ - if (!type) - return nullptr; - void* args[] = {il2cpp_type_get_object(il2cpp_class_get_type(type))}; - Il2CppObject* result = nullptr; - TryInvoke(method, target, args, &result); - return result; -} +struct HudNavigationViewController : Component { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation.UI", "HudNavigationViewController">; + + Component* galacticAnomaliesButtonWidget() + { return Field<"_galacticAnomaliesButtonWidget", Component*>(); } + FactionInformation* factionInformationHelper() + { return Field<"_factionInformationHelper", FactionInformation*>(); } + bool isActiveAndEnabled() + { return Get<"isActiveAndEnabled", bool>(); } + bool CanShowGalacticAnomaly() + { return Call<"CanShowGalacticAnomaly", bool>(); } + // NavigationViewData. It and the server models below are read on their runtime classes. + il2cpp::Object* CanvasContext() + { return Get<"CanvasContext", il2cpp::Object*>(); } +}; -Il2CppClass* UnityObject() -{ - static auto* cls = Class("UnityEngine.CoreModule", "UnityEngine", "Object"); - return cls; -} +struct HudAllianceAndNewsViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.HUD", "HudAllianceAndNewsViewController">; -bool Alive(Il2CppObject* object) -{ - if (!object) - return false; - void* args[] = {object}; - Il2CppObject* result = nullptr; - static auto* method = IL2CppClassHelper(UnityObject()).GetMethodInfo("op_Implicit", 1); - if (!TryInvoke(method, nullptr, args, &result) || !result || !result->klass) - return false; - const auto* type = il2cpp_class_get_type(result->klass); - if (!type || type->byref || type->type != IL2CPP_TYPE_BOOLEAN) - return false; - auto* alive = static_cast(il2cpp_object_unbox(result)); - return alive && *alive; -} + Component* chestPromotion() + { return Field<"_chestPromotion", Component*>(); } +}; -struct Root { - Il2CppGCHandle handle{}; - Il2CppObject* get() const - { return handle ? il2cpp_gchandle_get_target(handle) : nullptr; } - void reset(Il2CppObject* value = nullptr) - { - if (handle) - il2cpp_gchandle_free(handle); - handle = value ? il2cpp_gchandle_new(value, false) : 0; - } +// The data contexts the two HUD canvases are registered with. +struct NavigationViewData : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "NavigationViewData">; +}; +struct HudAllianceAndNewsViewControllerContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.HUD", "HudAllianceAndNewsViewControllerContext">; }; -Root navigation, panel, label, anomalyManager; -std::int64_t renderedSeconds = -1; +struct GalacticAnomaliesManager : MonoSingleton { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.GalacticAnomalies", "GalacticAnomaliesManager">; -void Clear() -{ - if (Alive(panel.get())) { - bool active = false; - Set(panel.get(), "SetActive", &active); - void* args[] = {panel.get()}; - TryInvoke(IL2CppClassHelper(UnityObject()).GetMethodInfo("Destroy", 1), nullptr, args); - } - label.reset(); - panel.reset(); - navigation.reset(); - anomalyManager.reset(); - renderedSeconds = -1; -} + // Digit.PrimeServer.Models.GalacticAnomaly, or null. + il2cpp::Object* GetSystemGalacticAnomalies(int64_t system) + { return Call<"GetSystemGalacticAnomalies", il2cpp::Object*>(system); } +}; -bool InSystemNavigation() -{ - static auto* hub = Class("Assembly-CSharp", "Digit.Client.Core", "Hub"); - static auto* sectionsGetter = IL2CppClassHelper(hub).GetMethodInfo("get_SectionManager", 0); - Il2CppObject* sections = nullptr; - std::int32_t section = 0; - return TryInvoke(sectionsGetter, nullptr, nullptr, §ions) && Value(sections, "get_CurrentSection", section) - && static_cast(section) == SectionID::Navigation_System; -} +bool Alive(Il2CppObject* object) +{ return object && UnityObject::Call<"op_Implicit", bool>(object); } -Il2CppObject* ExistingSingleton(Il2CppClass* singletonClass) +void Destroy(Il2CppObject* object) +{ UnityObject::Call<"Destroy">(object); } + +// The first active T on `root`'s GameObject or below it. Scoped to one hierarchy, unlike FindObjectOfType, which +// scans every loaded object. +template T* FindInChildren(il2cpp::Object* root) { - // get_Instance may search the scene or create a replacement. Read only the - // native owner's existing singleton, and retry when it has not initialized. - auto* parent = singletonClass ? il2cpp_class_get_parent(singletonClass) : nullptr; - if (!parent || std::strcmp(il2cpp_class_get_name(parent), "MonoSingleton`1")) - return nullptr; - auto* instance = il2cpp_class_get_field_from_name(parent, "s_instance"); - if (!instance || !(il2cpp_field_get_flags(instance) & FIELD_ATTRIBUTE_STATIC) || !instance->type - || il2cpp_type_is_byref(instance->type)) - return nullptr; - const auto kind = il2cpp_type_get_type(instance->type); - if (kind != IL2CPP_TYPE_CLASS && kind != IL2CPP_TYPE_GENERICINST && kind != IL2CPP_TYPE_VAR) - return nullptr; - Il2CppObject* value = nullptr; - il2cpp_field_static_get_value(instance, &value); - return value && il2cpp_class_is_assignable_from(singletonClass, value->klass) && Alive(value) ? value : nullptr; + auto* type = T::Class::TypeObject(); + return root && type ? root->Call<"GetComponentInChildren", T*>(type, false) : nullptr; } -Il2CppObject* RegisteredController(Il2CppClass* controllerClass, Il2CppClass* contextClass) +// The first live T on a visible registered canvas whose context is a `Context`. Only the canvas that owns that +// context is searched: the top interaction canvas and its root can belong to unrelated overlays. +template T* RegisteredController() { - if (!controllerClass || !contextClass) - return nullptr; - static auto* screenClass = Class("Assembly-CSharp", "Digit.Client.UI", "ScreenManager"); - auto* screens = Field(ExistingSingleton(screenClass), "_screens"); - int count = 0; - if (!Value(screens, "get_Count", count) || count < 0 || count > 256) + auto* screenManager = ScreenManager::Existing(); + auto* screens = Alive(screenManager) ? screenManager->screens() : nullptr; + int32_t count = screens ? screens->Count() : 0; + if (count < 0 || count > 256) return nullptr; - auto* item = IL2CppClassHelper(screens->klass).GetMethodInfo("get_Item", 1); - for (int index = 0; index < count; ++index) { - void* args[] = {&index}; - Il2CppObject* canvas = nullptr; - if (!TryInvoke(item, screens, args, &canvas) || !Alive(canvas)) + for (int32_t index = 0; index < count; ++index) { + auto* canvas = (*screens)[index]; + auto* context = Alive(canvas) ? canvas->Context() : nullptr; + if (!context || !context->Is() || !canvas->Visible()) continue; - auto* context = Get(canvas, "get_Context"); - bool visible = false; - if (!context || !il2cpp_class_is_assignable_from(contextClass, context->klass) - || !Value(canvas, "get_Visible", visible) || !visible) - continue; - // Search only the registered canvas that owns this context. The top - // interaction canvas and its root can belong to unrelated overlays. - // Component exposes the non-generic (Type, bool) overload. The one-argument - // overload is generic and cannot be invoked with a runtime Type object. - bool includeInactive = false; - void* controllerArgs[] = {il2cpp_type_get_object(il2cpp_class_get_type(controllerClass)), &includeInactive}; - Il2CppObject* controller = nullptr; - TryInvoke(TypeMethod(canvas->klass, "GetComponentInChildren", 2), canvas, controllerArgs, &controller); - if (Alive(controller)) + if (auto* controller = FindInChildren(canvas); Alive(controller)) return controller; } return nullptr; } -Il2CppObject* NavigationHud() +HudNavigationViewController* NavigationHud() +{ return RegisteredController(); } + +il2cpp::GcHandle navigation, panel, label, anomalyManager; +int64_t renderedSeconds = -1; + +void Clear() { - static auto* hud = Class("Assembly-CSharp", "Digit.Prime.Navigation.UI", "HudNavigationViewController"); - static auto* context = Class("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationViewData"); - return RegisteredController(hud, context); + if (auto* go = panel.Get(); Alive(go)) { + go->SetActive(false); + Destroy(go); + } + label.Reset(); + panel.Reset(); + navigation.Reset(); + anomalyManager.Reset(); + renderedSeconds = -1; } -Il2CppObject* Component(Il2CppObject* object, Il2CppClass* type) -{ return object ? WithType(TypeMethod(object->klass, "GetComponent"), object, type) : nullptr; } - -bool Rect(Il2CppObject* transform, vec2 anchor, vec2 pivot, vec2 size, vec2 position) +void Rect(RectTransform* transform, Vector2 anchor, Vector2 pivot, Vector2 size, Vector2 position) { - return Set(transform, "set_anchorMin", &anchor) && Set(transform, "set_anchorMax", &anchor) - && Set(transform, "set_pivot", &pivot) && Set(transform, "set_sizeDelta", &size) - && Set(transform, "set_anchoredPosition", &position); + transform->SetAnchorMin(anchor); + transform->SetAnchorMax(anchor); + transform->SetPivot(pivot); + transform->SetSizeDelta(size); + transform->SetAnchoredPosition(position); } -Il2CppObject* NewObject(const char* name, Il2CppObject* parent, Root& root) +// Creates an inactive GameObject under `parent` and roots it in `root`. +RectTransform* NewObject(const char* name, Transform* parent, il2cpp::GcHandle& root) { - static auto* go = Class("UnityEngine.CoreModule", "UnityEngine", "GameObject"); - static auto* rt = Class("UnityEngine.CoreModule", "UnityEngine", "RectTransform"); - if (!go || !rt) - return nullptr; - auto* object = il2cpp_object_new(go); - root.reset(object); - void* args[] = {il2cpp_string_new(name)}; - if (!TryInvoke(IL2CppClassHelper(go).GetMethodInfo(".ctor", 1), object, args)) - return nullptr; - bool active = false; - if (!Set(object, "SetActive", &active)) + auto* object = il2cpp::New(il2cpp_string_new(name)); + root = il2cpp::GcHandle::Strong(object); + if (!object) return nullptr; - auto* transform = WithType(TypeMethod(go, "AddComponent"), object, rt); - bool worldPositionStays = false; - void* parentArgs[] = {parent, &worldPositionStays}; - if (!transform - || !TryInvoke(IL2CppClassHelper(transform->klass).GetMethodInfo("SetParent", 2), transform, parentArgs)) + object->SetActive(false); + auto* transform = object->AddComponent(); + if (!transform) return nullptr; + transform->SetParent(parent, false); return transform; } -Il2CppObject* GraphicSprite(Il2CppObject* image) +Sprite* GraphicSprite(Image* image) { + if (!image) + return nullptr; // ImageSelector applies localized artwork through overrideSprite. - auto* sprite = Get(image, "get_overrideSprite"); - return sprite ? sprite : Get(image, "get_sprite"); + auto* sprite = image->overrideSprite(); + return sprite ? sprite : image->sprite(); } -Il2CppObject* NativeBackgroundSprite(Il2CppObject* nav) +Sprite* NativeBackgroundSprite(HudNavigationViewController* nav) { - auto* button = Field(nav, "_galacticAnomaliesButtonWidget"); - auto* parent = Get(Get(button, "get_transform"), "get_parent"); - void* args[] = {il2cpp_string_new("ShortcutKeybindHint/Background")}; - Il2CppObject* background = nullptr; - if (!parent || !TryInvoke(IL2CppClassHelper(parent->klass).GetMethodInfo("Find", 1), parent, args, &background)) - return nullptr; - static auto* image = Class("UnityEngine.UI", "UnityEngine.UI", "Image"); - return GraphicSprite(Component(background, image)); + auto* button = nav->galacticAnomaliesButtonWidget(); + auto* transform = button ? button->transform() : nullptr; + auto* parent = transform ? transform->parent() : nullptr; + auto* background = parent ? parent->Find(il2cpp_string_new("ShortcutKeybindHint/Background")) : nullptr; + auto* object = background ? background->gameObject() : nullptr; + return object ? GraphicSprite(object->GetComponent()) : nullptr; } -void Slice(Il2CppObject* image, Il2CppObject* sprite) +void Slice(Image* image, Sprite* sprite) { if (!image || !sprite) return; - int sliced = 1; - float spritePixels = 100; - if (!Value(sprite, "get_pixelsPerUnit", spritePixels) || spritePixels <= 0) + float spritePixels = sprite->pixelsPerUnit(); + if (spritePixels <= 0) return; - float multiplier = 100 / spritePixels; - Set(image, "set_type", &sliced); - Set(image, "set_pixelsPerUnitMultiplier", &multiplier); + image->SetType(Image::Type::Sliced); + image->SetPixelsPerUnitMultiplier(100 / spritePixels); } -Il2CppObject* Decoration(const char* name, Il2CppObject* parent, vec2 size, vec2 position, color tint, - Il2CppObject* sprite = nullptr) +Image* Decoration(const char* name, Transform* parent, Vector2 size, Vector2 position, color tint, + Sprite* sprite = nullptr) { - Root object; - auto* transform = NewObject(name, parent, object); - static auto* imageClass = Class("UnityEngine.UI", "UnityEngine.UI", "Image"); - auto* image = - object.get() ? WithType(TypeMethod(object.get()->klass, "AddComponent"), object.get(), imageClass) : nullptr; - bool no = false, yes = true; - bool ok = transform && image && Rect(transform, {0.5f, 0.5f}, {0.5f, 0.5f}, size, position) - && Set(image, "set_color", &tint) && Set(image, "set_raycastTarget", &no); - if (sprite) - ok = ok && Set(image, "set_sprite", sprite); - ok = ok && Set(object.get(), "SetActive", &yes); - if (!ok && Alive(object.get())) { - void* args[] = {object.get()}; - TryInvoke(IL2CppClassHelper(UnityObject()).GetMethodInfo("Destroy", 1), nullptr, args); + il2cpp::GcHandle object; + auto* transform = NewObject(name, parent, object); + auto* go = object.Get(); + auto* image = go ? go->AddComponent() : nullptr; + const bool ok = transform && image; + if (ok) { + Rect(transform, {0.5f, 0.5f}, {0.5f, 0.5f}, size, position); + image->SetColor(tint); + image->SetRaycastTarget(false); + if (sprite) + image->SetSprite(sprite); + go->SetActive(true); + } else if (Alive(go)) { + Destroy(go); } - object.reset(); return ok ? image : nullptr; } -bool Create(Il2CppObject* nav) +bool Create(HudNavigationViewController* nav) { // Keep the native system-name font, but place the timer under the upper-right // Help All / Engage / Claim drawer. Parenting to the drawer follows its scale // and visibility without changing any native button positions. - auto* names = Field(Field(nav, "_factionInformationHelper"), "_systemName"); - int count = 0; - if (!Value(names, "get_Count", count) || count < 1) + auto* helper = nav->factionInformationHelper(); + auto* names = helper ? helper->systemName() : nullptr; + if (!names || names->Count() < 1) return false; - int index = 0; - void* args[] = {&index}; - Il2CppObject* name = nullptr; - if (!TryInvoke(IL2CppClassHelper(names->klass).GetMethodInfo("get_Item", 1), names, args, &name) || !Alive(name)) + auto* name = (*names)[0]; + if (!Alive(name)) return false; - static auto* drawerClass = Class("Assembly-CSharp", "Digit.Prime.HUD", "HudAllianceAndNewsViewController"); - static auto* drawerContext = Class("Assembly-CSharp", "Digit.Prime.HUD", "HudAllianceAndNewsViewControllerContext"); - auto* drawer = RegisteredController(drawerClass, drawerContext); - auto* chest = Get(Field(drawer, "_chestPromotion"), "get_transform"); - auto* parent = Get(chest, "get_parent"); - static auto* tmp = Class("Unity.TextMeshPro", "TMPro", "TextMeshProUGUI"); - auto* source = Component(name, tmp); - auto* font = Get(source, "get_font"); + auto* drawer = RegisteredController(); + auto* promotion = drawer ? drawer->chestPromotion() : nullptr; + auto* chest = promotion ? promotion->transform() : nullptr; + auto* parent = chest ? chest->parent() : nullptr; + auto* nameObject = name->gameObject(); + auto* source = nameObject ? nameObject->GetComponent() : nullptr; + auto* font = source ? source->font() : nullptr; if (!parent || !font) return false; auto* transform = NewObject("CommunityMod_AnomalyTimer", parent, panel); - if (!transform || !Rect(transform, {0.5f, 0}, {0.5f, 1}, {198, 38}, {0, -40})) + auto* panelGo = panel.Get(); + if (!transform || !panelGo) return false; - static auto* image = Class("UnityEngine.UI", "UnityEngine.UI", "Image"); - auto* background = WithType(TypeMethod(panel.get()->klass, "AddComponent"), panel.get(), image); - color border{0.64f, 0.34f, 0.46f, 0.85f}; - bool no = false; - if (!background || !Set(background, "set_color", &border) || !Set(background, "set_raycastTarget", &no)) + Rect(transform, {0.5f, 0}, {0.5f, 1}, {198, 38}, {0, -40}); + auto* background = panelGo->AddComponent(); + if (!background) return false; + background->SetColor({0.64f, 0.34f, 0.46f, 0.85f}); + background->SetRaycastTarget(false); // Use the existing native key-hint silhouette for both layers, with a narrow // inset creating the border. A missing sprite still yields a visible frame. auto* rounded = NativeBackgroundSprite(nav); if (rounded) { - Set(background, "set_sprite", rounded); + background->SetSprite(rounded); Slice(background, rounded); } auto* fill = Decoration("AnomalyFill", transform, {194, 34}, {0, 0}, {0.06f, 0.10f, 0.14f, 0.96f}, rounded); @@ -378,28 +270,48 @@ bool Create(Il2CppObject* nav) return false; Slice(fill, rounded); - Root textObject; - auto* textTransform = NewObject("Countdown", transform, textObject); - auto* text = - WithType(TypeMethod(textObject.get() ? textObject.get()->klass : nullptr, "AddComponent"), textObject.get(), tmp); - label.reset(text); - bool ok = textTransform && text && Rect(textTransform, {0.5f, 0.5f}, {0.5f, 0.5f}, {190, 28}, {0, 0}); - float fontSize = 22; - int alignment = 0x202; // TMP: Center (514). - color color{0.95f, 0.83f, 0.89f, 1}; - ok = ok && Set(text, "set_font", font) && Set(text, "set_fontSize", &fontSize) - && Set(text, "set_alignment", &alignment) && Set(text, "set_color", &color) - && Set(text, "set_raycastTarget", &no) && Set(text, "set_enableWordWrapping", &no); - bool yes = true; - ok = ok && Set(textObject.get(), "SetActive", &yes); - if (!ok && Alive(textObject.get())) { - void* destroyArgs[] = {textObject.get()}; - TryInvoke(IL2CppClassHelper(UnityObject()).GetMethodInfo("Destroy", 1), nullptr, destroyArgs); + il2cpp::GcHandle textObject; + auto* textTransform = NewObject("Countdown", transform, textObject); + auto* textGo = textObject.Get(); + auto* text = textGo ? textGo->AddComponent() : nullptr; + label = il2cpp::GcHandle::Strong(text); + const bool ok = textTransform && text; + if (ok) { + Rect(textTransform, {0.5f, 0.5f}, {0.5f, 0.5f}, {190, 28}, {0, 0}); + text->SetFont(font); + text->SetFontSize(22); + text->SetAlignment(0x202); // TMP: Center (514). + text->SetColor({0.95f, 0.83f, 0.89f, 1}); + text->SetRaycastTarget(false); + text->SetEnableWordWrapping(false); + textGo->SetActive(true); + } else if (Alive(textGo)) { + Destroy(textGo); } - textObject.reset(); // The parent hierarchy now owns this child. + textObject.Reset(); // The parent hierarchy now owns this child. return ok; } +// Remaining ticks of the active anomaly in the viewed system, or 0. +int64_t RemainingTicks(HudNavigationViewController* nav) +{ + auto* context = nav->CanvasContext(); + auto* address = context ? context->Get<"ViewingAddress", il2cpp::Object*>() : nullptr; + int64_t system = address ? address->Get<"System", int64_t>() : -1; + if (system <= 0) + return 0; + // Cache the Unity object, not its timer data. Unity's liveness check detects + // destruction even while the managed wrapper is retained by our GC handle. + if (!Alive(anomalyManager.Get())) + anomalyManager = il2cpp::GcHandle::Strong(GalacticAnomaliesManager::Existing()); + auto* manager = anomalyManager.Get(); + auto* anomaly = Alive(manager) ? manager->GetSystemGalacticAnomalies(system) : nullptr; + if (!anomaly || !anomaly->Get<"IsActive", bool>()) + return 0; + auto* timer = anomaly->Get<"EndTimeTimerDataContext", il2cpp::Object*>(); + return timer ? timer->Get<"RemainingTime", TimeSpan>().ticks : 0; +} + void Update() { static auto next = std::chrono::steady_clock::time_point{}; @@ -412,49 +324,34 @@ void Update() Clear(); return; } - if (!InSystemNavigation()) { + // The anomaly button only exists in system view. + auto* sections = Hub::get_SectionManager(); + if (!sections || sections->CurrentSection() != SectionID::Navigation_System) { Clear(); return; } + // Rediscovery walks every registered canvas, so retry at most once a second. A hidden HUD is kept until a + // different live one turns up. static auto nextDiscovery = std::chrono::steady_clock::time_point{}; - bool active = false; - if ((!Alive(navigation.get()) || !Value(navigation.get(), "get_isActiveAndEnabled", active) || !active) - && now >= nextDiscovery) { - nextDiscovery = now + std::chrono::seconds(1); + auto* cached = navigation.Get(); + if ((!Alive(cached) || !cached->isActiveAndEnabled()) && now >= nextDiscovery) { + nextDiscovery = now + std::chrono::seconds(1); auto* replacement = NavigationHud(); - if ((replacement && replacement != navigation.get()) || !Alive(navigation.get())) { + if ((replacement && replacement != cached) || !Alive(cached)) { Clear(); - navigation.reset(replacement); + navigation = il2cpp::GcHandle::Strong(replacement); } } - auto* nav = navigation.get(); - bool show = false; - std::int64_t ticks = 0; - if (Alive(nav) && Value(nav, "get_isActiveAndEnabled", show) && show && Value(nav, "CanShowGalacticAnomaly", show) - && show) { - auto* address = Get(Get(nav, "get_CanvasContext"), "get_ViewingAddress"); - std::int64_t system = -1; - static auto* managerClass = Class("Assembly-CSharp", "Digit.Prime.GalacticAnomalies", "GalacticAnomaliesManager"); - // Cache the Unity object, not its timer data. Unity's liveness check detects - // destruction even while the managed wrapper is retained by our GC handle. - if (!Alive(anomalyManager.get())) - anomalyManager.reset(ExistingSingleton(managerClass)); - auto* manager = anomalyManager.get(); - Il2CppObject* anomaly = nullptr; - void* args[] = {&system}; - show = manager && Value(address, "get_System", system) && system > 0 - && TryInvoke(IL2CppClassHelper(managerClass).GetMethodInfo("GetSystemGalacticAnomalies", 1), manager, args, - &anomaly) - && Value(anomaly, "get_IsActive", show) && show - && Value(Get(anomaly, "get_EndTimeTimerDataContext"), "get_RemainingTime", ticks) && ticks > 0; - } else { - show = false; - } - if (show && (!Alive(panel.get()) || !Alive(label.get()))) { + auto* nav = navigation.Get(); + int64_t ticks = 0; + if (Alive(nav) && nav->isActiveAndEnabled() && nav->CanShowGalacticAnomaly()) + ticks = RemainingTicks(nav); + bool show = ticks > 0; + if (show && (!Alive(panel.Get()) || !Alive(label.Get()))) { // Release old UI without releasing the current navigation reference. auto* current = nav; Clear(); - navigation.reset(current); + navigation = il2cpp::GcHandle::Strong(current); if (!Create(current)) { static bool warned = false; if (!warned) { @@ -466,19 +363,20 @@ void Update() return; } } - if (!Alive(panel.get())) + auto* panelGo = panel.Get(); + if (!Alive(panelGo)) return; if (show) { + // Only touch the label when the displayed whole second changes. auto seconds = ticks / 10000000 + (ticks % 10000000 != 0); if (seconds != renderedSeconds) { auto text = seconds >= 3600 ? fmt::format("{}h {:02}m {:02}s", seconds / 3600, seconds / 60 % 60, seconds % 60) : fmt::format("{:02}m {:02}s", seconds / 60, seconds % 60); - show = Set(label.get(), "set_text", il2cpp_string_new(text.c_str())); - if (show) - renderedSeconds = seconds; + label.Get()->SetText(il2cpp_string_new(text.c_str())); + renderedSeconds = seconds; } } - Set(panel.get(), "SetActive", &show); + panelGo->SetActive(show); } } // namespace diff --git a/mods/src/patches/parts/haven_history.cc b/mods/src/patches/parts/haven_history.cc index 2500d8eb1..5952ee63c 100644 --- a/mods/src/patches/parts/haven_history.cc +++ b/mods/src/patches/parts/haven_history.cc @@ -77,7 +77,7 @@ void InstallHavenHistoryHooks() auto context = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.PlanetaryBase", "PlanetaryHelpHistoryContext"); auto applyRecords = context.GetMethod("ApplyRecords", 1); if (!applyRecords) { - ErrorMsg::MissingMethod("PlanetaryHelpHistoryContext", "ApplyRecords"); + spdlog::error("Unable to find method PlanetaryHelpHistoryContext.ApplyRecords"); return; } diff --git a/mods/src/patches/parts/hotkeys.cc b/mods/src/patches/parts/hotkeys.cc index 1d687f17d..208490b02 100644 --- a/mods/src/patches/parts/hotkeys.cc +++ b/mods/src/patches/parts/hotkeys.cc @@ -1,8 +1,11 @@ #include "config.h" +#include "patches/object_tracker.h" #include "patches/ship_shortcut_hint.h" +#include "prime/ActivatedAbilityManager.h" +#include #include -#include + // Object Viewers #include "prime/AllianceStarbaseObjectViewerWidget.h" @@ -42,6 +45,7 @@ #include "prime/ShipManagementViewController.h" #include "prime/ShipManagementScreenStateViewController.h" #include "prime/ShortcutsManager.h" +#include "prime/TextLocalizer.h" #include "patches/parts/action_queue.h" #include "patches/key.h" @@ -51,8 +55,7 @@ #include "patches/parts/focus_search.h" #include "str_utils.h" -#include -#include +#include #include "patches/screen_update_hook.h" @@ -72,74 +75,73 @@ #include #endif -static bool reset_focus_next_frame = false; -static int show_info_pending = 0; -static int show_officer_presets_pending = 0; -static int show_officer_swap_pending = 0; -static int show_ship_swap_pending = 0; +namespace +{ +// Classes only the shortcut hint badges touch. +struct InputAction : il2cpp::Object { + using Class = il2cpp::Class<"Unity.InputSystem", "UnityEngine.InputSystem", "InputAction">; + + Il2CppString* name() + { return Get<"name", Il2CppString*>(); } +}; + +struct InputActionReference : il2cpp::Object { + using Class = il2cpp::Class<"Unity.InputSystem", "UnityEngine.InputSystem", "InputActionReference">; + + InputAction* action() + { return Get<"action", InputAction*>(); } +}; + +struct ShortcutKeybindHint : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.GameInput", "ShortcutKeybindHint">; + + InputActionReference* inputAction() + { return Field<"_inputAction", InputActionReference*>(); } + TextLocalizer* keyTextLocalizer() + { return Field<"_keyTextLocalizer", TextLocalizer*>(); } +}; +} // namespace + +static bool reset_focus_next_frame = false; +static int show_info_pending = 0; +static int show_officer_presets_pending = 0; +static int show_officer_swap_pending = 0; +static int show_ship_swap_pending = 0; static bool officer_presets_assignment_opened = false; -using GetShowKeybindingsFn = bool(void*); -using SetShowKeybindingsFn = void(void*, bool); -using CanUseShortcutsFn = bool(); -using ClearTextOverrideFn = void(void*); -using UpdateShortcutHintTextFn = void(void*); -using GetInputActionFn = void*(void*); -using GetInputActionNameFn = Il2CppString*(void*); -using OverrideLocalizedTextFn = void(void*, Il2CppString*); -using UpdateShortcutHintVisibilityFn = void(void*, bool); - -static GetShowKeybindingsFn* get_show_keybindings = nullptr; -static SetShowKeybindingsFn* set_show_keybindings = nullptr; -static CanUseShortcutsFn* can_use_shortcuts = nullptr; -static ClearTextOverrideFn* clear_text_override = nullptr; -static GetInputActionFn* get_input_action = nullptr; -static GetInputActionNameFn* get_input_action_name = nullptr; -static OverrideLocalizedTextFn* override_localized_text = nullptr; +using UpdateShortcutHintTextFn = void(ShortcutKeybindHint*); + static UpdateShortcutHintTextFn* original_shortcut_hint_update_text = nullptr; -static ptrdiff_t shortcut_hint_input_action_offset = 0; -static ptrdiff_t shortcut_hint_text_localizer_offset = 0; -static bool shortcut_hint_fields_ready = false; -static bool initialize_actions_hook_ready = false; -static bool shortcut_hints_ready = false; +static bool initialize_actions_hook_ready = false; +static bool shortcut_hints_ready = false; bool SetNativeShortcutHintsVisible(bool visible) { - if (!get_show_keybindings || !set_show_keybindings) { - spdlog::warn("[Hotkeys] native shortcut hints are unavailable"); - return false; - } - auto* shortcuts_manager = ShortcutsManager::Instance(); if (!shortcuts_manager) { spdlog::warn("[Hotkeys] native shortcut hints are unavailable"); return false; } - if (get_show_keybindings(shortcuts_manager) != visible) - set_show_keybindings(shortcuts_manager, visible); + if (shortcuts_manager->ShowKeybindings() != visible) + shortcuts_manager->SetShowKeybindings(visible); return true; } bool ToggleNativeShortcutHints() { - if (!shortcut_hints_ready || !get_show_keybindings || !set_show_keybindings || !can_use_shortcuts) { - spdlog::warn("[Hotkeys] native shortcut hints are unavailable"); - return false; - } - - auto* shortcuts_manager = ShortcutsManager::Instance(); + auto* shortcuts_manager = shortcut_hints_ready ? ShortcutsManager::Instance() : nullptr; if (!shortcuts_manager) { spdlog::warn("[Hotkeys] native shortcut hints are unavailable"); return false; } // Match the native OnShowKeybindingsAction gate while avoiding a fabricated InputAction.CallbackContext. - if (!can_use_shortcuts()) + if (!ShortcutsManager::CanUseShortcuts()) return false; - set_show_keybindings(shortcuts_manager, !get_show_keybindings(shortcuts_manager)); + shortcuts_manager->SetShowKeybindings(!shortcuts_manager->ShowKeybindings()); return true; } @@ -214,34 +216,20 @@ static_assert(NativeShortcutForAction("interior_view")->primary == GameFunction: static_assert(NativeShortcutForAction("side_chat")->fallback == GameFunction::ShowChatSide2); static_assert(NativeShortcutForAction("unknown") == nullptr); -bool IsInstanceClassField(const FieldInfo* field, std::string_view expected_namespace, std::string_view expected_name) -{ - if (!field || !field->type || field->type->byref || field->type->type != IL2CPP_TYPE_CLASS - || (il2cpp_field_get_flags(const_cast(field)) & FIELD_ATTRIBUTE_STATIC) != 0 - || field->offset < static_cast(sizeof(Il2CppObject))) { - return false; - } - - auto* field_class = il2cpp_class_from_type(field->type); - return field_class && field_class->namespaze && field_class->name && field_class->namespaze == expected_namespace - && field_class->name == expected_name; -} - -void ShortcutKeybindHint_UpdateText_Hook(auto original, void* _this) +void ShortcutKeybindHint_UpdateText_Hook(auto original, ShortcutKeybindHint* _this) { if (!_this || !shortcut_hints_ready) { return original(_this); } - auto* text_localizer = - *reinterpret_cast(reinterpret_cast(_this) + shortcut_hint_text_localizer_offset); + auto* text_localizer = _this->keyTextLocalizer(); if (!text_localizer) { return original(_this); } // UpdateText does not clear an existing localization override. Clear ours first so native, disabled, and unknown // actions cannot retain a stale mod badge. - clear_text_override(text_localizer); + text_localizer->ClearTextOverride(); original(_this); const auto& config = Config::Get(); @@ -249,14 +237,13 @@ void ShortcutKeybindHint_UpdateText_Hook(auto original, void* _this) return; } - auto* input_action_reference = - *reinterpret_cast(reinterpret_cast(_this) + shortcut_hint_input_action_offset); + auto* input_action_reference = _this->inputAction(); if (!input_action_reference) { return; } - auto* action = get_input_action(input_action_reference); - auto* name = action ? get_input_action_name(action) : nullptr; + auto* action = input_action_reference->action(); + auto* name = action ? action->name() : nullptr; if (!name) { return; } @@ -264,7 +251,7 @@ void ShortcutKeybindHint_UpdateText_Hook(auto original, void* _this) const auto action_name = to_string(name); const auto* mapping = NativeShortcutForAction(action_name); if (!mapping) { - override_localized_text(text_localizer, il2cpp_string_new("-")); + text_localizer->OverrideLocalizedText(il2cpp_string_new("-")); return; } @@ -274,9 +261,9 @@ void ShortcutKeybindHint_UpdateText_Hook(auto original, void* _this) auto* bar = ObjectFinder::Get(); auto* context = bar ? bar->CanvasContext() : nullptr; auto* fleets = context ? context->FleetData() : nullptr; - hint_action = ShipShortcutHintAction(hint_action, fleets ? fleets->max_length : 0, [fleets](std::size_t position) { - auto* fleet = reinterpret_cast(fleets->vector)[position]; - return fleet ? fleet->Index : -1; + hint_action = ShipShortcutHintAction(hint_action, fleets ? fleets->size() : 0, [fleets](std::size_t position) { + auto* fleet = (*fleets)[position]; + return fleet ? fleet->Index() : -1; }); } auto shortcut = hint_action == GameFunction::Max ? std::string{} : MapKey::GetShortcutHint(hint_action); @@ -286,10 +273,10 @@ void ShortcutKeybindHint_UpdateText_Hook(auto original, void* _this) if (shortcut.empty()) { shortcut = "-"; } - override_localized_text(text_localizer, il2cpp_string_new(shortcut.c_str())); + text_localizer->OverrideLocalizedText(il2cpp_string_new(shortcut.c_str())); } -void ShortcutKeybindHint_UpdateVisibility_Hook(auto original, void* _this, bool visible) +void ShortcutKeybindHint_UpdateVisibility_Hook(auto original, ShortcutKeybindHint* _this, bool visible) { original(_this, visible); if (visible && shortcut_hints_ready && original_shortcut_hint_update_text) { @@ -297,12 +284,8 @@ void ShortcutKeybindHint_UpdateVisibility_Hook(auto original, void* _this, bool } } -static const MethodInfo* on_events_action = nullptr; -static const MethodInfo* on_galaxy_action = nullptr; -static const MethodInfo* show_shipconstruction_action = nullptr; -static const MethodInfo* show_shields_action = nullptr; -static const MethodInfo* show_battlelogs_action = nullptr; - +// UnityEngine.InputSystem.InputAction.CallbackContext, passed empty. Generated code takes it by value with the +// platform's C++ ABI, so this layout must match the managed struct. struct InputActionCallbackContext { void* state; int32_t action_index; @@ -310,25 +293,17 @@ struct InputActionCallbackContext { static_assert(sizeof(InputActionCallbackContext) == 16); -bool InvokeNativeShortcut(const MethodInfo* method, const char* action_name) +// Calls one of ShortcutsManager's On*Action(InputAction.CallbackContext) handlers. A missing handler is reported +// once by the binding layer and does nothing. +template bool InvokeNativeShortcut(const char* action_name) { auto* shortcuts_manager = ShortcutsManager::Instance(); - if (!shortcuts_manager || !method) { + if (!shortcuts_manager) { spdlog::warn("[Hotkeys] native {} shortcut is unavailable", action_name); return false; } - // These callbacks do not inspect CallbackContext, but runtime_invoke still requires storage for the value-type - // argument. Using the managed invoker also avoids platform-specific by-value ABI assumptions. - InputActionCallbackContext context{}; - void* args[]{&context}; - Il2CppException* exception = nullptr; - il2cpp_runtime_invoke(method, shortcuts_manager, args, &exception); - if (exception) { - spdlog::warn("[Hotkeys] native {} shortcut raised exception={}", action_name, static_cast(exception)); - return false; - } - + shortcuts_manager->Call(InputActionCallbackContext{}); return true; } @@ -418,7 +393,7 @@ bool IsRealShipIndex(SelectableList* list, int32_t index) { auto* item = list->DataItem(index); if (!item) return false; - return reinterpret_cast(item)->HasShip; + return static_cast(item)->HasShip(); } bool MoveShipSelectionInDock(bool goLeft) @@ -432,25 +407,25 @@ bool MoveShipSelectionInDock(bool goLeft) if (!widget) continue; auto canvas = GetCanvasControllerFromComponent(widget); - if (!canvas || !canvas->Visible() || !widget->isActiveAndEnabled) { + if (!canvas || !canvas->Visible() || !widget->isActiveAndEnabled()) { spdlog::trace("MoveShipSelectionInDock({}) - widget={} not visible/active", (int)goLeft, (void*)widget); continue; } - auto* list = widget->_selectableList; + auto* list = widget->selectableList(); if (!list) { spdlog::trace("MoveShipSelectionInDock({}) - widget={} has no _selectableList", (int)goLeft, (void*)widget); continue; } - const auto count = list->Count; + const auto count = list->Count(); if (count <= 0) { spdlog::trace("MoveShipSelectionInDock({}) - list={} count={}", (int)goLeft, (void*)list, count); continue; } const auto step = goLeft ? -1 : 1; - auto newIndex = list->SelectedIndex + step; + auto newIndex = list->SelectedIndex() + step; while (newIndex >= 0 && newIndex < count && !IsRealShipIndex(list, newIndex)) { newIndex += step; } @@ -460,7 +435,8 @@ bool MoveShipSelectionInDock(bool goLeft) } spdlog::debug("MoveShipSelectionInDock({}) - selecting index {} of {}", (int)goLeft, newIndex, count); - acted = list->RequestSelect(newIndex) || acted; + list->RequestSelect(newIndex); + acted = true; } return acted; @@ -476,12 +452,12 @@ void UpdateManageShipDockSelection() next_update = now + std::chrono::milliseconds(100); auto* sections = Hub::get_SectionManager(); if (!Config::Get().dock_syncmanagement || !sections - || sections->CurrentSection != SectionID::ShipManagement_Details) { + || sections->CurrentSection() != SectionID::ShipManagement_Details) { last_fleet_id = 0; return; } for (auto* controller : ObjectFinder::GetAll()) { - if (!controller || !controller->isActiveAndEnabled) + if (!controller || !controller->isActiveAndEnabled()) continue; auto* canvas = GetCanvasControllerFromComponent(controller); if (!canvas || !canvas->Visible()) @@ -489,16 +465,16 @@ void UpdateManageShipDockSelection() auto* context = controller->Context(); auto* fleet = context ? context->CurrentFleet() : nullptr; auto* manager = FleetsManager::Instance(); - if (!fleet || !fleet->HasShip || !manager || !manager->HasFleetService()) + if (!fleet || !fleet->HasShip() || !manager || !manager->HasFleetService()) continue; - const auto index = fleet->Index; + const auto index = fleet->Index(); if (index < 0) continue; // Exclude unassigned and synthetic fleets; management list order is not dock order. auto* dock_fleet = manager->GetFleetPlayerData(index); - if (!dock_fleet || dock_fleet->Id != fleet->Id) + if (!dock_fleet || dock_fleet->Id() != fleet->Id()) continue; - if (last_fleet_id == fleet->Id) + if (last_fleet_id == fleet->Id()) return; auto* bar = ObjectFinder::Get(); if (!bar) @@ -508,9 +484,9 @@ void UpdateManageShipDockSelection() bar->RequestSelect(index); if (!bar->IsIndexSelected(index)) return; // Retry next update if native selection was blocked. - spdlog::debug("[ManageShips] selected dock {} for fleet {}", index + 1, fleet->Id); + spdlog::debug("[ManageShips] selected dock {} for fleet {}", index + 1, fleet->Id()); } - last_fleet_id = fleet->Id; + last_fleet_id = fleet->Id(); return; } last_fleet_id = 0; @@ -524,58 +500,29 @@ void UpdateExpiredAbilityButtons() return; next_update = now + std::chrono::milliseconds(250); auto* bar = ObjectFinder::Get(); - auto* controller = bar ? bar->_fleetPanelController : nullptr; - auto* fleet = controller ? controller->fleet : nullptr; - if (!fleet || (static_cast(fleet->CurrentState) & static_cast(FleetState::CanActivateAbility)) == 0) - return; - static auto fleet_class = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Ships", "FleetLocalViewController"); - static auto panel_field = fleet_class.GetField("_shipActionPanelLocalViewController"); - static auto panel_class = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Ships", "ShipActionPanelLocalViewController"); - static auto ids_field = panel_class.GetField("_abilityIDs"); - static auto buttons_field = panel_class.GetField("_buttons"); - static auto cooldown = panel_class.GetMethodInfo("GetRemainingCooldown", 1); - static auto has_effect = panel_class.GetMethod("HasShipAnActiveAbilityWithActiveEffect", 0); - static auto timer_class = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "TimerDataContext"); - static auto remaining_time = timer_class.GetMethodInfo("get_RemainingTime", 0); - static auto ability_class = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ActivatedAbilities", "ActivatedAbilityManager"); - static auto instance = ability_class.GetParent("MonoSingleton`1").GetProperty("Instance"); - static auto can_use = method_contract::Resolve(ability_class.get_cls(), "CanUseAbility", false, "System.Boolean", - {"Digit.PrimeServer.Models.FleetPlayerData", "System.Int64"}); - if (!panel_field.get_info() || !ids_field.get_info() || !buttons_field.get_info() || !cooldown - || !remaining_time || !has_effect || !can_use) + auto* controller = bar ? bar->fleetPanelController() : nullptr; + auto* fleet = controller ? controller->fleet() : nullptr; + if (!fleet || (static_cast(fleet->CurrentState()) & static_cast(FleetState::CanActivateAbility)) == 0) return; - auto* manager = instance.GetRaw(nullptr); - if (!manager) - return; - auto* panel = *reinterpret_cast(reinterpret_cast(controller) + panel_field.offset()); + auto* manager = ActivatedAbilityManager::Instance(); + auto* panel = controller->shipActionPanelLocalViewController(); auto* canvas = panel ? GetCanvasControllerFromComponent(panel) : nullptr; - if (!canvas || !canvas->Visible() || has_effect(panel)) + if (!manager || !canvas || !canvas->Visible() || panel->HasShipAnActiveAbilityWithActiveEffect()) return; - auto* ids = *reinterpret_cast(reinterpret_cast(panel) + ids_field.offset()); - auto* buttons = *reinterpret_cast(reinterpret_cast(panel) + buttons_field.offset()); + auto* ids = panel->abilityIDs(); + auto* buttons = panel->buttons(); if (!ids || !buttons) return; - for (uintptr_t index = 0; index < ids->max_length && index < buttons->max_length; ++index) { - auto* button = reinterpret_cast(buttons->vector)[index]; - if (!button || !button->isActiveAndEnabled || !button->Context || button->Context->Interactable) - continue; - auto ability_id = reinterpret_cast(ids->vector)[index]; - void* args[] = {&ability_id}; - Il2CppException* exception = nullptr; - auto* timer = il2cpp_runtime_invoke(cooldown, panel, args, &exception); - if (exception || !timer) + for (size_t index = 0; index < ids->size() && index < buttons->size(); ++index) { + auto* button = (*buttons)[index]; + auto* context = button ? button->Context() : nullptr; + if (!button || !button->isActiveAndEnabled() || !context || context->Interactable()) continue; - auto* remaining = il2cpp_runtime_invoke(remaining_time, timer, nullptr, &exception); - // TimeSpan is boxed Int64 in 100 ns ticks. Repair at -1 second or below. - if (exception || !remaining || *static_cast(il2cpp_object_unbox(remaining)) > -10000000) - continue; - void* ready_args[] = {fleet, &ability_id}; - auto* ready = il2cpp_runtime_invoke(can_use, manager, ready_args, &exception); - if (!exception && ready && *static_cast(il2cpp_object_unbox(ready))) - button->Context->Interactable = true; + const auto ability_id = (*ids)[index]; + auto* timer = panel->GetRemainingCooldown(ability_id); + // TimeSpan is an eight-byte value in 100 ns ticks. Repair at -1 second or below. + if (timer && timer->RemainingTime().ticks <= -10000000 && manager->CanUseAbility(fleet, ability_id)) + context->SetInteractable(true); } } @@ -586,13 +533,13 @@ bool MoveDockInManagementView(bool goLeft) } auto* section_manager = Hub::get_SectionManager(); - if (!section_manager || section_manager->CurrentSection != SectionID::ShipManagement_Details) { + if (!section_manager || section_manager->CurrentSection() != SectionID::ShipManagement_Details) { return false; } bool acted = false; for (auto controller : ObjectFinder::GetAll()) { - if (!controller || !controller->isActiveAndEnabled) { + if (!controller || !controller->isActiveAndEnabled()) { continue; } @@ -640,11 +587,11 @@ bool PressShipSwapButton() } auto canvas = GetCanvasControllerFromComponent(controller); - if (!canvas || !canvas->Visible() || !controller->isActiveAndEnabled) { + if (!canvas || !canvas->Visible() || !controller->isActiveAndEnabled()) { continue; } - auto* button = controller->_swapShipButton; + auto* button = controller->swapShipButton(); if (button) { spdlog::info("[ShipSwap] pressing swap button controller={} button={}", static_cast(controller), static_cast(button)); @@ -664,11 +611,11 @@ bool PressOfficerSwapButton() } auto canvas = GetCanvasControllerFromComponent(controller); - if (!canvas || !canvas->Visible() || !controller->isActiveAndEnabled) { + if (!canvas || !canvas->Visible() || !controller->isActiveAndEnabled()) { continue; } - auto* button = controller->_officerIconButton; + auto* button = controller->officerIconButton(); if (button) { spdlog::info("[OfficerSwap] pressing officer button controller={} button={}", static_cast(controller), static_cast(button)); @@ -688,13 +635,13 @@ bool PressOfficerPresetsButton() } auto canvas = GetCanvasControllerFromComponent(controller); - if (!canvas || !canvas->Visible() || !controller->isActiveAndEnabled) { + if (!canvas || !canvas->Visible() || !controller->isActiveAndEnabled()) { continue; } - auto* button = controller->_officerPresetsButton; - auto* listener = button ? button->SemaphoreListener : nullptr; - auto* native_button = listener ? listener->TheButton : nullptr; + auto* button = controller->officerPresetsButton(); + auto* listener = button ? button->SemaphoreListener() : nullptr; + auto* native_button = listener ? listener->TheButton() : nullptr; if (native_button) { spdlog::info("[OfficerPresets] pressing presets button controller={} button={}", static_cast(controller), static_cast(button)); @@ -793,8 +740,6 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) return; } - static auto GetDeltaTime = il2cpp_resolve_icall_typed("UnityEngine.Time::get_deltaTime()"); - const auto is_in_chat = Hub::IsInChat(); const auto config = &Config::Get(); @@ -837,25 +782,36 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) if (ship_select_request != -1 && !Key::IsInputFocused()) { + // TODO: Shift+ship towing with the Discovery never ran on earlier clients (its PlanCourse and + // SetTowRequest bindings had drifted) and has not been tested in game since they were fixed. It sends a + // real tow request, so keep it off until someone verifies it. Shift+ship falls through as a no-op meanwhile. + constexpr bool kEnableDiscoveryTow = false; if (Key::HasShift()) { - FleetPlayerData* foundDisco = nullptr; - for (int discoIdx = 0; discoIdx < 10; ++discoIdx) { - auto fleetPlayerData = FleetsManager::Instance()->GetFleetPlayerData(discoIdx); - if (fleetPlayerData && fleetPlayerData->Hull && fleetPlayerData->Hull->Id == 1307832955) { - foundDisco = fleetPlayerData; - break; + if constexpr (kEnableDiscoveryTow) { + FleetPlayerData* foundDisco = nullptr; + for (int discoIdx = 0; discoIdx < 10; ++discoIdx) { + auto fleetPlayerData = FleetsManager::Instance()->GetFleetPlayerData(discoIdx); + if (fleetPlayerData && fleetPlayerData->Hull() && fleetPlayerData->Hull()->Id() == 1307832955) { + foundDisco = fleetPlayerData; + break; + } } - } - if (foundDisco) { - auto towedFleetId = FleetsManager::Instance()->GetFleetPlayerData(ship_select_request)->Id; - auto plannedCourse = - DeploymentManger::Instance()->PlanCourse(FleetsManager::Instance()->GetFleetPlayerData(ship_select_request), - foundDisco->Address, Vector3::zero(), nullptr, nullptr, nullptr); - while (plannedCourse->MoveNext()) { - ; + if (foundDisco) { + auto* towed = FleetsManager::Instance()->GetFleetPlayerData(ship_select_request); + auto* deployment = DeploymentManger::Instance(); + auto* plannedCourse = + towed && deployment ? deployment->PlanCourse(towed, foundDisco->Address(), Vector3::zero(), nullptr, + nullptr, nullptr) + : nullptr; + if (plannedCourse) { + // Drain the planning coroutine synchronously. The bound stops a coroutine that keeps yielding + // (it waits on something only the game loop advances) from hanging the frame. + for (int step = 0; step < 1000 && plannedCourse->MoveNext(); ++step) { + } + deployment->SetTowRequest(towed->Id(), foundDisco->Id()); + } } - DeploymentManger::Instance()->SetTowRequest(towedFleetId, foundDisco->Id); } } else { auto fleet_bar = ObjectFinder::Get(); @@ -868,13 +824,13 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) if (can_locate && ship_select_request == last_ship_select_request && fleet_bar->IsIndexSelected(ship_select_request) && select_diff < std::chrono::milliseconds((int)Config::Get().select_timer)) { - auto fleet_controller = fleet_bar->_fleetPanelController; - auto fleet = fleet_controller ? fleet_controller->fleet : nullptr; + auto fleet_controller = fleet_bar->fleetPanelController(); + auto fleet = fleet_controller ? fleet_controller->fleet() : nullptr; if (!fleet) { return; } - if (NavigationSectionManager::Instance() && NavigationSectionManager::Instance()->SNavigationManager) { - NavigationSectionManager::Instance()->SNavigationManager->HideInteraction(); + if (NavigationSectionManager::Instance() && NavigationSectionManager::Instance()->SNavigationManager()) { + NavigationSectionManager::Instance()->SNavigationManager()->HideInteraction(); } FleetsManager::Instance()->RequestViewFleet(fleet, true); } else { @@ -900,11 +856,11 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) if (MapKey::IsDown(GameFunction::SelectCurrent)) { auto fleet_bar = ObjectFinder::Get(); if (fleet_bar) { - auto fleet_controller = fleet_bar->_fleetPanelController; - auto fleet = fleet_controller ? fleet_controller->fleet : nullptr; + auto fleet_controller = fleet_bar->fleetPanelController(); + auto fleet = fleet_controller ? fleet_controller->fleet() : nullptr; if (fleet) { - if (NavigationSectionManager::Instance() && NavigationSectionManager::Instance()->SNavigationManager) { - NavigationSectionManager::Instance()->SNavigationManager->HideInteraction(); + if (NavigationSectionManager::Instance() && NavigationSectionManager::Instance()->SNavigationManager()) { + NavigationSectionManager::Instance()->SNavigationManager()->HideInteraction(); } FleetsManager::Instance()->RequestViewFleet(fleet, true); return; @@ -922,10 +878,10 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) if ((MapKey::IsDown(GameFunction::ShowChat) || MapKey::IsDown(GameFunction::ShowChatSide1) || MapKey::IsDown(GameFunction::ShowChatSide2))) { if (auto chat_manager = ChatManager::Instance(); chat_manager) { - if (chat_manager->IsSideChatOpen) { + if (chat_manager->IsSideChatOpen()) { if (auto view_controller = ObjectFinder::Get(); view_controller) { - if (auto message_list = view_controller->_messageList; message_list) { - if (auto message_field = message_list->_inputField; message_field) { + if (auto message_list = view_controller->messageList(); message_list) { + if (auto message_field = message_list->inputField(); message_field) { message_field->ActivateInputField(); } } @@ -984,7 +940,7 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) } else if (MapKey::IsDown(GameFunction::ShoWStationExterior)) { return GotoSection(SectionID::Starbase_Exterior); } else if (MapKey::IsDown(GameFunction::NativeShortcutGalaxy)) { - InvokeNativeShortcut(on_galaxy_action, "Galaxy"); + InvokeNativeShortcut<"OnGalaxyAction">("Galaxy"); return; } else if (MapKey::IsDown(GameFunction::ShowGalaxy)) { return ChangeNavigationSection(SectionID::Navigation_Galaxy); @@ -997,19 +953,19 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) } else if (MapKey::IsDown(GameFunction::ShowArtifacts)) { return GotoSection(SectionID::ArtifactHall_Inventory); } else if (MapKey::IsDown(GameFunction::ShowShipConstruction)) { - InvokeNativeShortcut(show_shipconstruction_action, "Ship Construction"); + InvokeNativeShortcut<"OnShipsAction">("Ship Construction"); return; } else if (MapKey::IsDown(GameFunction::ShowOfficerSwap)) { auto* section_manager = Hub::get_SectionManager(); spdlog::info("[OfficerSwap] shortcut pressed current_section={}", - section_manager ? static_cast(section_manager->CurrentSection) : 0); + section_manager ? static_cast(section_manager->CurrentSection()) : 0); if (PressOfficerSwapButton()) { return; } auto* fleet_bar = ObjectFinder::Get(); - auto* fleet_controller = fleet_bar ? fleet_bar->_fleetPanelController : nullptr; - auto* fleet = fleet_controller ? fleet_controller->fleet : nullptr; + auto* fleet_controller = fleet_bar ? fleet_bar->fleetPanelController() : nullptr; + auto* fleet = fleet_controller ? fleet_controller->fleet() : nullptr; if (fleet) { spdlog::info("[OfficerSwap] requesting Manage Ships before officer assignment fleet={}", static_cast(fleet)); @@ -1022,14 +978,14 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) } else if (MapKey::IsDown(GameFunction::ShowShipSwap)) { auto* section_manager = Hub::get_SectionManager(); spdlog::info("[ShipSwap] shortcut pressed current_section={}", - section_manager ? static_cast(section_manager->CurrentSection) : 0); + section_manager ? static_cast(section_manager->CurrentSection()) : 0); if (PressShipSwapButton()) { return; } auto* fleet_bar = ObjectFinder::Get(); - auto* fleet_controller = fleet_bar ? fleet_bar->_fleetPanelController : nullptr; - auto* fleet = fleet_controller ? fleet_controller->fleet : nullptr; + auto* fleet_controller = fleet_bar ? fleet_bar->fleetPanelController() : nullptr; + auto* fleet = fleet_controller ? fleet_controller->fleet() : nullptr; if (fleet) { spdlog::info("[ShipSwap] requesting Manage Ships before swap fleet={}", static_cast(fleet)); fleet_controller->RequestAction(fleet, ActionType::Manage, 0, ActionBehaviour::Default); @@ -1039,10 +995,10 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) } return; } else if (MapKey::IsDown(GameFunction::ShowShields)) { - InvokeNativeShortcut(show_shields_action, "Peace Shields"); + InvokeNativeShortcut<"OnPeaceShieldAction">("Peace Shields"); return; } else if (MapKey::IsDown(GameFunction::ShowBattlelogs)) { - InvokeNativeShortcut(show_battlelogs_action, "Battle Reports"); + InvokeNativeShortcut<"OnInboxAction">("Battle Reports"); return; } else if (MapKey::IsDown(GameFunction::ShowInventory)) { return GotoSection(SectionID::InventoryList); @@ -1062,8 +1018,8 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) officer_presets_assignment_opened = PressOfficerSwapButton(); if (!officer_presets_assignment_opened) { auto* fleet_bar = ObjectFinder::Get(); - auto* fleet_controller = fleet_bar ? fleet_bar->_fleetPanelController : nullptr; - auto* fleet = fleet_controller ? fleet_controller->fleet : nullptr; + auto* fleet_controller = fleet_bar ? fleet_bar->fleetPanelController() : nullptr; + auto* fleet = fleet_controller ? fleet_controller->fleet() : nullptr; if (fleet) { spdlog::info("[OfficerPresets] requesting Manage Ships before officer presets fleet={}", static_cast(fleet)); @@ -1082,7 +1038,7 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) } else if (MapKey::IsDown(GameFunction::ShowAwayTeam)) { return GotoSection(SectionID::Missions_AwayTeamsList); } else if (MapKey::IsDown(GameFunction::NativeShortcutEvents)) { - InvokeNativeShortcut(on_events_action, "Events"); + InvokeNativeShortcut<"OnEventsAction">("Events"); return; } else if (MapKey::IsDown(GameFunction::ShowEvents)) { return GotoSection(SectionID::Tournament_Group_Selection); @@ -1154,8 +1110,8 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) // spdlog::log("Setting log level to TRACE"); } else if (MapKey::IsDown(GameFunction::ShowShips)) { auto fleet_bar = ObjectFinder::Get(); - auto fleet_controller = fleet_bar ? fleet_bar->_fleetPanelController : nullptr; - auto fleet = fleet_controller ? fleet_controller->fleet : nullptr; + auto fleet_controller = fleet_bar ? fleet_bar->fleetPanelController() : nullptr; + auto fleet = fleet_controller ? fleet_controller->fleet() : nullptr; if (fleet) { fleet_controller->RequestAction(fleet, ActionType::Manage, 0, ActionBehaviour::Default); } @@ -1213,15 +1169,15 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) auto all_pre_scan_widgets = ObjectFinder::GetAll(); for (auto& pre_scan_widget : all_pre_scan_widgets) { - auto visibility_controller = pre_scan_widget ? pre_scan_widget->_visibilityController : nullptr; - auto rewardsWidget = pre_scan_widget ? pre_scan_widget->_rewardsButtonWidget : nullptr; - auto rewards_controller = rewardsWidget ? rewardsWidget->_rewardsController : nullptr; + auto visibility_controller = pre_scan_widget ? pre_scan_widget->visibilityController() : nullptr; + auto rewardsWidget = pre_scan_widget ? pre_scan_widget->rewardsButtonWidget() : nullptr; + auto rewards_controller = rewardsWidget ? rewardsWidget->rewardsController() : nullptr; if (visibility_controller - && (visibility_controller->_state == VisibilityState::Visible - || visibility_controller->_state == VisibilityState::Show) + && (visibility_controller->state() == VisibilityState::Visible + || visibility_controller->state() == VisibilityState::Show) && rewards_controller) { - if (rewards_controller->_state != VisibilityState::Visible - && rewards_controller->_state != VisibilityState::Show) { + if (rewards_controller->state() != VisibilityState::Visible + && rewards_controller->state() != VisibilityState::Show) { show_info_pending = 5; } else { rewards_controller->Hide(); @@ -1235,15 +1191,15 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) auto all_pre_scan_widgets = ObjectFinder::GetAll(); for (auto& pre_scan_widget : all_pre_scan_widgets) { - auto visibility_controller = pre_scan_widget ? pre_scan_widget->_visibilityController : nullptr; - auto rewardsWidget = pre_scan_widget ? pre_scan_widget->_rewardsButtonWidget : nullptr; - auto rewards_controller = rewardsWidget ? rewardsWidget->_rewardsController : nullptr; + auto visibility_controller = pre_scan_widget ? pre_scan_widget->visibilityController() : nullptr; + auto rewardsWidget = pre_scan_widget ? pre_scan_widget->rewardsButtonWidget() : nullptr; + auto rewards_controller = rewardsWidget ? rewardsWidget->rewardsController() : nullptr; const auto pre_scan_visible = visibility_controller - && (visibility_controller->_state == VisibilityState::Visible - || visibility_controller->_state == VisibilityState::Show); + && (visibility_controller->state() == VisibilityState::Visible + || visibility_controller->state() == VisibilityState::Show); if (pre_scan_visible && rewards_controller) { - const auto rewards_widget_visible = rewards_controller->_state == VisibilityState::Visible - || rewards_controller->_state == VisibilityState::Show; + const auto rewards_widget_visible = rewards_controller->state() == VisibilityState::Visible + || rewards_controller->state() == VisibilityState::Show; if (!rewards_widget_visible) { rewards_controller->Show(true); } @@ -1282,15 +1238,15 @@ void ScreenManager_Update_Hook(auto original, ScreenManager* _this) template inline bool CanHideViewer(T* widget, bool require_parent = false) { - auto* visibility = widget ? widget->_visibilityController : nullptr; - if (!visibility || (visibility->_state != VisibilityState::Visible && visibility->_state != VisibilityState::Show)) { + auto* visibility = widget ? widget->visibilityController() : nullptr; + if (!visibility || (visibility->state() != VisibilityState::Visible && visibility->state() != VisibilityState::Show)) { return false; } if (!require_parent) { return true; } - auto* parent = widget->Parent; - return parent && parent->IsShowing; + auto* parent = widget->Parent(); + return parent && parent->IsShowing(); } template inline bool CanHideViewersOfType() @@ -1343,7 +1299,7 @@ void GotoSection(SectionID sectionID, void* section_data) void ChangeNavigationSection(SectionID sectionID) { - const auto section_data = Hub::get_SectionManager()->_sectionStorage->GetState(sectionID); + const auto section_data = Hub::get_SectionManager()->sectionStorage()->GetState(sectionID); if (section_data) { GotoSection(sectionID, section_data); @@ -1363,15 +1319,15 @@ inline bool DidExecuteFleetAction(std::string_view actionText, ActionType action return false; } - auto fleet_controller = fleet_bar->_fleetPanelController; - auto fleet = fleet_controller ? fleet_controller->fleet : nullptr; + auto fleet_controller = fleet_bar->fleetPanelController(); + auto fleet = fleet_controller ? fleet_controller->fleet() : nullptr; if (!fleet) { return false; } - auto fleet_state = fleet->CurrentState; + auto fleet_state = fleet->CurrentState(); - auto fleet_id = fleet->Id; - auto prev_state = fleet->PreviousState; + auto fleet_id = fleet->Id(); + auto prev_state = fleet->PreviousState(); auto canAction = true; // actionRequired->CheckIsMet(); FleetState canState = FleetState::Unknown; auto didAction = false; @@ -1380,22 +1336,22 @@ inline bool DidExecuteFleetAction(std::string_view actionText, ActionType action canState = fleet_state; } - spdlog::trace(FleetAction_Format, actionText, (int)actionType, (int)fleet_id, (int)fleet_state, (int)prev_state, + spdlog::trace(FleetAction_Format, actionText, actionType.flagValue, (int)fleet_id, (int)fleet_state, (int)prev_state, canAction, (int)canState, "[start]"); if (canState != FleetState::Unknown && canAction) { - if (NavigationSectionManager::Instance() && NavigationSectionManager::Instance()->SNavigationManager) { - NavigationSectionManager::Instance()->SNavigationManager->HideInteraction(); + if (NavigationSectionManager::Instance() && NavigationSectionManager::Instance()->SNavigationManager()) { + NavigationSectionManager::Instance()->SNavigationManager()->HideInteraction(); } didAction = fleet_controller->RequestAction(fleet, actionType, 0, ActionBehaviour::Default); } - if (helpState != FleetState::Unknown && (didAction || helpState == fleet->CurrentState)) { + if (helpState != FleetState::Unknown && (didAction || helpState == fleet->CurrentState())) { didAction = didAction || fleet_controller->RequestAction(fleet, actionType, 0, ActionBehaviour::AskHelp); } - spdlog::trace(FleetAction_Format, actionText, (int)actionType, (int)fleet_id, (int)fleet_state, (int)prev_state, + spdlog::trace(FleetAction_Format, actionText, actionType.flagValue, (int)fleet_id, (int)fleet_state, (int)prev_state, canAction, (int)canState, didAction); return didAction; @@ -1406,7 +1362,7 @@ bool DidExecuteRecall(FleetBarViewController* fleet_bar) static constexpr FleetState states[] = {FleetState::IdleInSpace, FleetState::Impulsing, FleetState::Mining, FleetState::Capturing}; - auto fleet_controller = fleet_bar->_fleetPanelController; + auto fleet_controller = fleet_bar->fleetPanelController(); return DidExecuteFleetAction("Recall", ActionType::Recall, fleet_bar, states); } @@ -1427,9 +1383,8 @@ void ExecuteSpaceAction(FleetBarViewController* fleet_bar) return; } // Query active viewers without adding constructor detours for this popup or its subclasses. - if (auto viewers = WaveDefenseObjectViewerWidget::FindActiveViewers()) { - for (uintptr_t index = 0; index < viewers->max_length; ++index) { - auto widget = reinterpret_cast(viewers->vector)[index]; + if (auto* viewers = WaveDefenseObjectViewerWidget::FindActiveViewers()) { + for (auto* widget : *viewers) { if (CanHideViewer(widget, true)) { // Use the displayed native action, including its eligibility and confirmation checks. widget->PressPrimaryButton(); @@ -1443,8 +1398,8 @@ void ExecuteSpaceAction(FleetBarViewController* fleet_bar) return; } - auto fleet_controller = fleet_bar->_fleetPanelController; - auto fleet = fleet_controller ? fleet_controller->fleet : nullptr; + auto fleet_controller = fleet_bar->fleetPanelController(); + auto fleet = fleet_controller ? fleet_controller->fleet() : nullptr; if (!fleet) { return; } @@ -1467,7 +1422,7 @@ void ExecuteSpaceAction(FleetBarViewController* fleet_bar) if (has_queue_clear) { action_queue->ClearQueue(fleet); } else if (has_recall_cancel - && (fleet->CurrentState == FleetState::WarpCharging || fleet->CurrentState == FleetState::Warping)) { + && (fleet->CurrentState() == FleetState::WarpCharging || fleet->CurrentState() == FleetState::Warping)) { fleet_controller->CancelButtonClicked(); } else { auto all_pre_scan_widgets = ObjectFinder::GetAll(); @@ -1476,23 +1431,23 @@ void ExecuteSpaceAction(FleetBarViewController* fleet_bar) if (auto mine_object_viewer_widget = ObjectFinder::Get(); CanHideViewer(mine_object_viewer_widget, require_visible_target)) { - if (has_secondary && pre_scan_widget->_scanEngageButtonsWidget) { - return pre_scan_widget->_scanEngageButtonsWidget->OnScanButtonClicked(); + if (has_secondary && pre_scan_widget->scanEngageButtonsWidget()) { + return pre_scan_widget->scanEngageButtonsWidget()->OnScanButtonClicked(); } else if (has_primary) { return mine_object_viewer_widget->MineClicked(); } } - if (has_queue && action_queue->IsQueueUnlocked() && pre_scan_widget->_addToQueueButtonWidget - && pre_scan_widget->_scanEngageButtonsWidget) { - auto context = pre_scan_widget->_scanEngageButtonsWidget->Context; + if (has_queue && action_queue->IsQueueUnlocked() && pre_scan_widget->addToQueueButtonWidget() + && pre_scan_widget->scanEngageButtonsWidget()) { + auto context = pre_scan_widget->scanEngageButtonsWidget()->Context(); auto type = GetHullTypeFromBattleTarget(context); if (type != HullType::ArmadaTarget && (type != HullType::Any || force_space_action_next_frame)) { - if (pre_scan_widget->_addToQueueButtonWidget->isActiveAndEnabled) { - auto listener = pre_scan_widget->_addToQueueButtonWidget->SemaphoreListener; + if (pre_scan_widget->addToQueueButtonWidget()->isActiveAndEnabled()) { + auto listener = pre_scan_widget->addToQueueButtonWidget()->SemaphoreListener(); if (listener && !action_queue->IsQueueFull(fleet)) { - auto button = listener->TheButton; + auto button = listener->TheButton(); if (button) { button->Press(); DidHideViewers(); @@ -1508,13 +1463,13 @@ void ExecuteSpaceAction(FleetBarViewController* fleet_bar) } } - if (has_secondary && pre_scan_widget->_scanEngageButtonsWidget) { - return pre_scan_widget->_scanEngageButtonsWidget->OnScanButtonClicked(); + if (has_secondary && pre_scan_widget->scanEngageButtonsWidget()) { + return pre_scan_widget->scanEngageButtonsWidget()->OnScanButtonClicked(); } - if (has_primary && pre_scan_widget->_scanEngageButtonsWidget - && pre_scan_widget->_scanEngageButtonsWidget->enabled) { - auto context = pre_scan_widget->_scanEngageButtonsWidget->Context; + if (has_primary && pre_scan_widget->scanEngageButtonsWidget() + && pre_scan_widget->scanEngageButtonsWidget()->enabled()) { + auto context = pre_scan_widget->scanEngageButtonsWidget()->Context(); auto type = GetHullTypeFromBattleTarget(context); // Try once more in X frames if we get ANY @@ -1523,8 +1478,8 @@ void ExecuteSpaceAction(FleetBarViewController* fleet_bar) auto armada_state = VisibilityState::Unknown; if (armada_widget) { - if (armada_widget->_visibilityController) { - armada_state = armada_widget->_visibilityController->State; + if (armada_widget->visibilityController()) { + armada_state = armada_widget->visibilityController()->State(); } else { spdlog::warn("ArmadaWidget has no visibility controller, using default Visible state"); armada_state = VisibilityState::Visible; @@ -1543,19 +1498,20 @@ void ExecuteSpaceAction(FleetBarViewController* fleet_bar) // in-case of failed to navgitate error? if (canActionPrimary) { if (type == HullType::ArmadaTarget) { - if (pre_scan_widget->_armadaAttackButton && pre_scan_widget->_armadaAttackButton->isActiveAndEnabled) { - auto listener = pre_scan_widget->_armadaAttackButton->SemaphoreListener; + auto* attack_button = pre_scan_widget->armadaAttackButton(); + if (attack_button && attack_button->isActiveAndEnabled()) { + auto listener = attack_button->SemaphoreListener(); if (listener) { - auto button = listener->TheButton; + auto button = listener->TheButton(); if (button) { button->Press(); } } return; } - pre_scan_widget->_scanEngageButtonsWidget->OnArmadaButtonClicked(); + pre_scan_widget->scanEngageButtonsWidget()->OnArmadaButtonClicked(); } else { - pre_scan_widget->_scanEngageButtonsWidget->OnEngageButtonClicked(); + pre_scan_widget->scanEngageButtonsWidget()->OnEngageButtonClicked(); } return; } else if (type == HullType::Any) { @@ -1568,15 +1524,15 @@ void ExecuteSpaceAction(FleetBarViewController* fleet_bar) if (auto mine_object_viewer_widget = ObjectFinder::Get(); CanHideViewer(mine_object_viewer_widget, require_visible_target)) { - if (has_secondary && mine_object_viewer_widget->_scanEngageButtonsWidget) { - if (mine_object_viewer_widget->_scanEngageButtonsWidget->Context) { - return mine_object_viewer_widget->_scanEngageButtonsWidget->OnScanButtonClicked(); + if (has_secondary && mine_object_viewer_widget->scanEngageButtonsWidget()) { + if (mine_object_viewer_widget->scanEngageButtonsWidget()->Context()) { + return mine_object_viewer_widget->scanEngageButtonsWidget()->OnScanButtonClicked(); } } else if (has_primary) { return mine_object_viewer_widget->MineClicked(); } } else if (auto star_node_object_viewer_widget = ObjectFinder::Get(); - star_node_object_viewer_widget && star_node_object_viewer_widget->Context + star_node_object_viewer_widget && star_node_object_viewer_widget->Context() && (!require_visible_target || CanHideViewer(star_node_object_viewer_widget, true))) { if (has_secondary) { star_node_object_viewer_widget->OnViewButtonActivation(); @@ -1591,8 +1547,8 @@ void ExecuteSpaceAction(FleetBarViewController* fleet_bar) auto armada_state = VisibilityState::Unknown; if (armada_widget) { - if (armada_widget->_visibilityController) { - armada_state = armada_widget->_visibilityController->State; + if (armada_widget->visibilityController()) { + armada_state = armada_widget->visibilityController()->State(); } else { spdlog::warn("ArmadaWidget has no visibility controller, using default Visible state"); armada_state = VisibilityState::Visible; @@ -1601,8 +1557,8 @@ void ExecuteSpaceAction(FleetBarViewController* fleet_bar) spdlog::info("have armada? {}, State {}", (armada_widget ? "Yes" : "No"), (int)armada_state); if (armada_widget && (armada_state == VisibilityState::Visible || armada_state == VisibilityState::Show)) { - auto button = armada_widget->__get__joinContext(); - if (button && button->Interactable) { + auto button = armada_widget->joinContext(); + if (button && button->Interactable()) { armada_widget->ValidateThenJoinArmada(); return; } @@ -1631,15 +1587,15 @@ HullType GetHullTypeFromBattleTarget(BattleTargetData* context) if (!context) { return HullType::Any; } - auto deployed_data = context->TargetFleetDeployedData; + auto deployed_data = context->TargetFleetDeployedData(); if (!deployed_data) { return HullType::Any; } - auto hull_spec = deployed_data->Hull; + auto hull_spec = deployed_data->Hull(); if (!hull_spec) { return HullType::Any; } - return hull_spec->Type; + return hull_spec->Type(); } void ChatMessageListLocalViewController_AboutToShow_Hook(ChatMessageListLocalViewController* _this); @@ -1648,12 +1604,12 @@ decltype(ChatMessageListLocalViewController_AboutToShow_Hook)* oChatMessageListL void ChatMessageListLocalViewController_AboutToShow_Hook(ChatMessageListLocalViewController* _this) { oChatMessageListLocalViewController_AboutToShow(_this); - if (_this->_inputField) { - _this->_inputField->SendOnFocus(); + if (_this->inputField()) { + _this->inputField()->SendOnFocus(); } } -void InitializeActions_Hook(auto original, void* _this) +void InitializeActions_Hook(auto original, ShortcutsManager* _this) { if (Config::Get().use_scopely_hotkeys) { return original(_this); @@ -1666,20 +1622,20 @@ bool CheckShowCargo(RewardsButtonWidget* widget) return false; } - if (!widget->Context) { + if (!widget->Context()) { return false; } - const auto target_fleet_deployed = widget->Context->TargetFleetDeployedData; + const auto target_fleet_deployed = widget->Context()->TargetFleetDeployedData(); if (!target_fleet_deployed) { return Config::Get().show_station_cargo; } - auto fleet_type = target_fleet_deployed->FleetType; + auto fleet_type = target_fleet_deployed->FleetType(); if (fleet_type == DeployedFleetType::Player) { return Config::Get().show_player_cargo; } else if (fleet_type == DeployedFleetType::Marauder) { - if (auto hull = target_fleet_deployed->Hull; hull && hull->Type == HullType::ArmadaTarget) { + if (auto hull = target_fleet_deployed->Hull(); hull && hull->Type() == HullType::ArmadaTarget) { return Config::Get().show_armada_cargo; } else { return Config::Get().show_hostile_cargo; @@ -1695,13 +1651,13 @@ void OnDidBindContext_Hook(auto original, RewardsButtonWidget* _this) return original(_this); } - auto rewards_controller = _this->_rewardsController; - auto pre_state = rewards_controller ? rewards_controller->_state : VisibilityState::Unknown; + auto rewards_controller = _this->rewardsController(); + auto pre_state = rewards_controller ? rewards_controller->state() : VisibilityState::Unknown; pre_state = pre_state; original(_this); - rewards_controller = _this->_rewardsController; - auto post_state = rewards_controller ? rewards_controller->_state : VisibilityState::Unknown; + rewards_controller = _this->rewardsController(); + auto post_state = rewards_controller ? rewards_controller->state() : VisibilityState::Unknown; post_state = post_state; if (rewards_controller && CheckShowCargo(_this)) { rewards_controller->Show(true); @@ -1716,8 +1672,8 @@ void ShowWithFleet_Hook(auto original, PreScanTargetWidget* _this, void* a1) return; } - auto rewards_button_widget = _this->_rewardsButtonWidget; - auto rewards_controller = rewards_button_widget ? rewards_button_widget->_rewardsController : nullptr; + auto rewards_button_widget = _this->rewardsButtonWidget(); + auto rewards_controller = rewards_button_widget ? rewards_button_widget->rewardsController() : nullptr; if (rewards_controller && CheckShowCargo(rewards_button_widget)) { rewards_controller->Show(true); show_info_pending = 1; @@ -1731,18 +1687,12 @@ bool install_screen_manager_update_hook() return true; } - auto helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "ScreenManager"); - if (!helper.isValidHelper()) { - ErrorMsg::MissingHelper("UI", "ScreenManager"); - } else if (auto update = helper.GetMethod("Update"); update) { - if (!SPUD_STATIC_DETOUR(update, ScreenManager_Update_Hook)) + if (auto m = ScreenManager::Class::Method("Update")) { + if (!SPUD_STATIC_DETOUR(m.Pointer(), ScreenManager_Update_Hook)) return false; installed = true; - return true; - } else { - ErrorMsg::MissingMethod("ScreenManager", "Update"); } - return false; + return installed; } void InstallShortcutHintHooks() @@ -1752,139 +1702,25 @@ void InstallShortcutHintHooks() return; } - auto shortcuts_manager_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.GameInput", "ShortcutsManager"); - if (!shortcuts_manager_helper.isValidHelper()) { - ErrorMsg::MissingHelper("GameInput", "ShortcutsManager"); - } else { - get_show_keybindings = shortcuts_manager_helper.GetMethod("get_ShowKeybindings", 0); - set_show_keybindings = shortcuts_manager_helper.GetMethod("set_ShowKeybindings", 1); - can_use_shortcuts = shortcuts_manager_helper.GetMethod("get_CanUseShortcuts", 0); - if (!get_show_keybindings) - ErrorMsg::MissingMethod("ShortcutsManager", "get_ShowKeybindings"); - if (!set_show_keybindings) - ErrorMsg::MissingMethod("ShortcutsManager", "set_ShowKeybindings"); - if (!can_use_shortcuts) - ErrorMsg::MissingMethod("ShortcutsManager", "get_CanUseShortcuts"); - } - - auto text_localizer_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "TextLocalizer"); - if (!text_localizer_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Client.UI", "TextLocalizer"); - } else { - clear_text_override = text_localizer_helper.GetMethod("ClearTextOverride", 0); - override_localized_text = - text_localizer_helper.GetMethod("OverrideLocalizedText", 1); - if (!clear_text_override) - ErrorMsg::MissingMethod("TextLocalizer", "ClearTextOverride"); - if (!override_localized_text) - ErrorMsg::MissingMethod("TextLocalizer", "OverrideLocalizedText"); - } - - UpdateShortcutHintTextFn* shortcut_hint_update_text = nullptr; - UpdateShortcutHintVisibilityFn* shortcut_hint_update_visibility = nullptr; - auto shortcut_hint_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.GameInput", "ShortcutKeybindHint"); - if (!shortcut_hint_helper.isValidHelper()) { - ErrorMsg::MissingHelper("GameInput", "ShortcutKeybindHint"); - } else { - auto* input_action_field = il2cpp_class_get_field_from_name(shortcut_hint_helper.get_cls(), "_inputAction"); - auto* text_localizer_field = il2cpp_class_get_field_from_name(shortcut_hint_helper.get_cls(), "_keyTextLocalizer"); - const bool input_action_field_valid = - IsInstanceClassField(input_action_field, "UnityEngine.InputSystem", "InputActionReference"); - const bool text_localizer_field_valid = - IsInstanceClassField(text_localizer_field, "Digit.Client.UI", "TextLocalizer"); - if (!input_action_field_valid) { - spdlog::error("Unable to resolve field 'ShortcutKeybindHint->_inputAction' as InputActionReference"); - } - if (!text_localizer_field_valid) { - spdlog::error("Unable to resolve field 'ShortcutKeybindHint->_keyTextLocalizer' as TextLocalizer"); - } - if (input_action_field_valid && text_localizer_field_valid) { - shortcut_hint_input_action_offset = input_action_field->offset; - shortcut_hint_text_localizer_offset = text_localizer_field->offset; - shortcut_hint_fields_ready = true; - } - - shortcut_hint_update_text = shortcut_hint_helper.GetMethod("UpdateText", 0); - shortcut_hint_update_visibility = - shortcut_hint_helper.GetMethod("UpdateVisibility", 1); - if (!shortcut_hint_update_text) { - ErrorMsg::MissingMethod("ShortcutKeybindHint", "UpdateText"); - } - if (!shortcut_hint_update_visibility) - ErrorMsg::MissingMethod("ShortcutKeybindHint", "UpdateVisibility"); - } - - auto input_action_reference_helper = - il2cpp_get_class_helper("Unity.InputSystem", "UnityEngine.InputSystem", "InputActionReference"); - auto input_action_helper = - il2cpp_get_class_helper("Unity.InputSystem", "UnityEngine.InputSystem", "InputAction"); - if (!input_action_reference_helper.isValidHelper()) { - ErrorMsg::MissingHelper("UnityEngine.InputSystem", "InputActionReference"); - } else { - get_input_action = input_action_reference_helper.GetMethod("get_action", 0); - if (!get_input_action) - ErrorMsg::MissingMethod("InputActionReference", "get_action"); - } - if (!input_action_helper.isValidHelper()) { - ErrorMsg::MissingHelper("UnityEngine.InputSystem", "InputAction"); - } else { - get_input_action_name = input_action_helper.GetMethod("get_name", 0); - if (!get_input_action_name) - ErrorMsg::MissingMethod("InputAction", "get_name"); + // Only the detour targets gate the feature. The managed members the hint path calls resolve on first use; a + // missing one is reported once and reads as null/false, which leaves the native hint text in place. + auto update_text = ShortcutKeybindHint::Class::Method("UpdateText", 0); + auto update_visibility = ShortcutKeybindHint::Class::Method("UpdateVisibility", 1); + if (!initialize_actions_hook_ready || !update_text || !update_visibility) { + return; } - if (get_show_keybindings && set_show_keybindings && can_use_shortcuts && initialize_actions_hook_ready && - clear_text_override && override_localized_text && shortcut_hint_fields_ready && shortcut_hint_update_text && - shortcut_hint_update_visibility && get_input_action && get_input_action_name) { - MapKey::CacheShortcutHints(); - original_shortcut_hint_update_text = - SPUD_STATIC_DETOUR(shortcut_hint_update_text, ShortcutKeybindHint_UpdateText_Hook); - SPUD_STATIC_DETOUR(shortcut_hint_update_visibility, ShortcutKeybindHint_UpdateVisibility_Hook); - shortcut_hints_ready = true; - } + MapKey::CacheShortcutHints(); + original_shortcut_hint_update_text = SPUD_STATIC_DETOUR(update_text.Pointer(), ShortcutKeybindHint_UpdateText_Hook); + SPUD_STATIC_DETOUR(update_visibility.Pointer(), ShortcutKeybindHint_UpdateVisibility_Hook); + shortcut_hints_ready = true; } void InstallHotkeyHooks() { - auto shortcuts_manager_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.GameInput", "ShortcutsManager"); - if (!shortcuts_manager_helper.isValidHelper()) { - ErrorMsg::MissingHelper("GameInput", "ShortcutsManager"); - } else { - on_events_action = shortcuts_manager_helper.GetMethodInfo("OnEventsAction", 1); - if (on_events_action == nullptr) { - ErrorMsg::MissingMethod("ShortcutsManager", "OnEventsAction"); - } - - on_galaxy_action = shortcuts_manager_helper.GetMethodInfo("OnGalaxyAction", 1); - if (on_galaxy_action == nullptr) { - ErrorMsg::MissingMethod("ShortcutsManager", "OnGalaxyAction"); - } - - show_shipconstruction_action = shortcuts_manager_helper.GetMethodInfo("OnShipsAction", 1); - if (show_shipconstruction_action == nullptr) { - ErrorMsg::MissingMethod("ShortcutsManager", "OnShipsAction"); - } - - show_shields_action = shortcuts_manager_helper.GetMethodInfo("OnPeaceShieldAction", 1); - if (show_shields_action == nullptr) { - ErrorMsg::MissingMethod("ShortcutsManager", "OnPeaceShieldAction"); - } - - show_battlelogs_action = shortcuts_manager_helper.GetMethodInfo("OnInboxAction", 1); - if (show_battlelogs_action == nullptr) { - ErrorMsg::MissingMethod("ShortcutsManager", "OnInboxAction"); - } - - auto ptr_can_user_shortcuts = shortcuts_manager_helper.GetMethod("InitializeActions"); - if (ptr_can_user_shortcuts == nullptr) { - ErrorMsg::MissingMethod("ShortcutsManager", "InitializeActions"); - } else { - SPUD_STATIC_DETOUR(ptr_can_user_shortcuts, InitializeActions_Hook); - initialize_actions_hook_ready = true; - } + if (auto m = ShortcutsManager::Class::Method("InitializeActions")) { + SPUD_STATIC_DETOUR(m.Pointer(), InitializeActions_Hook); + initialize_actions_hook_ready = true; } InstallShortcutHintHooks(); @@ -1894,31 +1730,8 @@ void InstallHotkeyHooks() fleet_watch::InstallRuntimeProbe(); #endif - static auto rewards_button_widget = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Combat", "RewardsButtonWidget"); - if (!rewards_button_widget.isValidHelper()) { - ErrorMsg::MissingHelper("Combat", "RewardsButtonWidget"); - } else { - auto on_did_bind_context_ptr = rewards_button_widget.GetMethod("OnDidBindContext"); - on_did_bind_context_ptr = on_did_bind_context_ptr; - if (on_did_bind_context_ptr == nullptr) { - ErrorMsg::MissingMethod("RewardsButtonWidget", "OnDidBindContext"); - } else { - SPUD_STATIC_DETOUR(on_did_bind_context_ptr, OnDidBindContext_Hook); - } - } - - static auto pre_scan_target_widget = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Combat", "PreScanTargetWidget"); - if (!pre_scan_target_widget.isValidHelper()) { - ErrorMsg::MissingHelper("Combat", "PreScanTargetWidget"); - } else { - auto show_with_fleet_ptr = pre_scan_target_widget.GetMethod("ShowWithFleet"); - show_with_fleet_ptr = show_with_fleet_ptr; - if (show_with_fleet_ptr == nullptr) { - ErrorMsg::MissingMethod("PreScanTargetWidget", "ShowWithFleet"); - } else { - SPUD_STATIC_DETOUR(show_with_fleet_ptr, ShowWithFleet_Hook); - } - } + if (auto m = RewardsButtonWidget::Class::Method("OnDidBindContext")) + SPUD_STATIC_DETOUR(m.Pointer(), OnDidBindContext_Hook); + if (auto m = PreScanTargetWidget::Class::Method("ShowWithFleet")) + SPUD_STATIC_DETOUR(m.Pointer(), ShowWithFleet_Hook); } diff --git a/mods/src/patches/parts/instant_cargo.cc b/mods/src/patches/parts/instant_cargo.cc index 1e6662e4f..491a591c7 100644 --- a/mods/src/patches/parts/instant_cargo.cc +++ b/mods/src/patches/parts/instant_cargo.cc @@ -1,49 +1,34 @@ // Show actual cargo totals through the native text renderer, without its count-up animation. #include "config.h" -#include -#include +#include "prime/TextLocalizer.h" + +#include #include #include #if defined(_WIN32) && defined(_M_X64) -#include namespace { -FieldInfo * textField{}, *contextField{}, *identifierField{}, *snapField{}; -Il2CppClass* cargoDataClass{}; -bool textReady{}; -// Metadata names and storage are checked before reading game-owned fields. -bool IsType(const Il2CppType* type, const char* name) -{ - if (!type || type->byref) - return false; - auto* actual = il2cpp_type_get_name(type); - const bool matches = actual && std::strcmp(actual, name) == 0; - il2cpp_free(actual); - return matches; -} +using CargoProgressData = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "CargoProgressData">; -FieldInfo* Field(Il2CppClass* cls, const char* name, const char* type, size_t bytes) -{ - for (auto* parent = cls; parent; parent = il2cpp_class_get_parent(parent)) { - auto* field = il2cpp_class_get_field_from_name(parent, name); - if (field && !(il2cpp_field_get_flags(field) & FIELD_ATTRIBUTE_STATIC) && field->type && !field->type->byref - && field->offset >= sizeof(Il2CppObject) && field->offset + bytes <= il2cpp_class_instance_size(cls) - && IsType(field->type, type)) - return field; - } - return nullptr; -} -template T Read(void* self, FieldInfo* field) -{ - T value{}; - if (self && field) - il2cpp_field_get_value(static_cast(self), field, &value); - return value; -} -bool IsCargoText(void* self) +// Fields below are declared on LegacyProgressBar and Widget`1; lookups walk the parents. +struct LegacyComparableProgressBar : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Stats", "LegacyComparableProgressBar">; + + TextLocalizer* currentValue_X_of_Y() + { return Field<"_currentValue_X_of_Y", TextLocalizer*>(); } + // IProgressData + il2cpp::Object* context() + { return Field<"m_context", il2cpp::Object*>(); } + bool snapToValue() + { return Field<"_snapToValue", bool>(); } + void SetSnapToValue(bool value) + { SetField<"_snapToValue">(value); } +}; + +bool IsCargoText(LegacyComparableProgressBar* self) { - auto* label = Read(self, textField); - auto* identifier = Read(label, identifierField); + auto* label = self->currentValue_X_of_Y(); + auto* identifier = label ? label->identifier() : nullptr; constexpr char name[] = "shared_x_of_y_x_coloured"; if (!identifier || il2cpp_string_length(identifier) != sizeof(name) - 1) return false; @@ -51,34 +36,31 @@ bool IsCargoText(void* self) for (size_t i = 0; i < sizeof(name) - 1; ++i) if (chars[i] != name[i]) return false; - auto* context = Read(self, contextField); - if (!context || il2cpp_object_get_class(context) != cargoDataClass) { + auto* context = self->context(); + if (!context || context->klass != CargoProgressData::Raw()) { static bool reported{}; if (!reported) { reported = true; spdlog::warn("[InstantCargoText] cargo label has unexpected context {}; native text retained", - context ? il2cpp_class_get_name(il2cpp_object_get_class(context)) : "null"); + context ? il2cpp_class_get_name(context->klass) : "null"); } return false; } return true; } struct SnapScope { - Il2CppObject* object; - bool previous; - explicit SnapScope(void* self) - : object(static_cast(self)) - , previous(Read(self, snapField)) - { - bool snap = true; - il2cpp_field_set_value(object, snapField, &snap); - } + LegacyComparableProgressBar* object; + bool previous; + explicit SnapScope(LegacyComparableProgressBar* self) + : object(self) + , previous(self->snapToValue()) + { object->SetSnapToValue(true); } ~SnapScope() - { il2cpp_field_set_value(object, snapField, &previous); } + { object->SetSnapToValue(previous); } }; -void SetWidgetData_Hook(auto original, void* self) +void SetWidgetData_Hook(auto original, LegacyComparableProgressBar* self) { - if (!textReady || !self || !Config::Get().instant_cargo_counter || !IsCargoText(self)) { + if (!Config::Get().instant_cargo_counter || !self || !IsCargoText(self)) { original(self); return; } @@ -97,25 +79,12 @@ void SetWidgetData_Hook(auto original, void* self) void InstallInstantCargoCounterHooks() { #if defined(_WIN32) && defined(_M_X64) - auto bar = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Stats", "LegacyComparableProgressBar"); - auto text = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "TextLocalizer"); - auto data = il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "CargoProgressData"); - auto* cls = bar.get_cls(); - cargoDataClass = data.get_cls(); - textField = Field(cls, "_currentValue_X_of_Y", "Digit.Client.UI.TextLocalizer", sizeof(void*)); - contextField = Field(cls, "m_context", "Digit.PrimeServer.Models.IProgressData", sizeof(void*)); - identifierField = Field(text.get_cls(), "m_identifier", "System.String", sizeof(void*)); - snapField = Field(cls, "_snapToValue", "System.Boolean", sizeof(bool)); - auto* method = bar.GetMethodInfo("SetWidgetData", 0); - if (!cargoDataClass || !textField || !contextField || !identifierField || !snapField || !method - || method->klass != cls || !method->methodPointer || method->is_generic || method->is_inflated - || (method->flags & METHOD_ATTRIBUTE_STATIC) || method->parameters_count != 0 - || !IsType(method->return_type, "System.Void")) { - spdlog::warn("[InstantCargoText] required metadata unavailable; native text retained"); + auto method = LegacyComparableProgressBar::Class::Method("SetWidgetData", 0); + // The lookup walks parents; only detour the bar's own override, never the shared Widget`1.SetWidgetData. + if (!method || method.Info()->klass != LegacyComparableProgressBar::Class::Raw()) return; - } - textReady = SPUD_STATIC_DETOUR(method->methodPointer, SetWidgetData_Hook) != nullptr; - spdlog::info("[InstantCargoText] native text hook {}", textReady ? "installed" : "unavailable"); + const bool installed = SPUD_STATIC_DETOUR(method.Pointer(), SetWidgetData_Hook) != nullptr; + spdlog::info("[InstantCargoText] native text hook {}", installed ? "installed" : "unavailable"); #else spdlog::info("[InstantCargoText] native text snap is not supported on this platform"); #endif diff --git a/mods/src/patches/parts/instant_warp_confirmation.cc b/mods/src/patches/parts/instant_warp_confirmation.cc index 561eadef2..6a3429f2c 100644 --- a/mods/src/patches/parts/instant_warp_confirmation.cc +++ b/mods/src/patches/parts/instant_warp_confirmation.cc @@ -1,5 +1,4 @@ #include "config.h" -#include "errormsg.h" #include "patches/instant_warp_policy.h" #include "ship_name_match.h" @@ -14,31 +13,27 @@ namespace { -using PopupAction = void(CoursePromptPopupWidget*); -PopupAction* initiate_regular_warp = nullptr; -PopupAction* on_instant_warp_button_click = nullptr; - void CoursePromptPopupViewController_AboutToShow_Hook(auto original, CoursePromptPopupViewController* _this) { original(_this); - const auto widget = _this == nullptr ? nullptr : _this->PopupWidget; - const auto context = widget == nullptr ? nullptr : widget->Context; - if (context == nullptr || !context->HasInstantWarp) { + const auto widget = _this == nullptr ? nullptr : _this->PopupWidget(); + const auto context = widget == nullptr ? nullptr : widget->Context(); + if (context == nullptr || !context->HasInstantWarp()) { return; } FleetPlayerData* fleet = nullptr; if (const auto course = context->GetCourseData(); course != nullptr) - fleet = course->PlayerFleet; + fleet = course->PlayerFleet(); const auto action = ResolveInstantWarpConfirmation(fleet); spdlog::debug("InstantWarpConfirmation: resolved action {}", static_cast(action)); switch (action) { case InstantWarpConfirmation::Warp: - initiate_regular_warp(widget); + widget->InitiateRegularWarp(); break; case InstantWarpConfirmation::Jump: - on_instant_warp_button_click(widget); + widget->OnInstantWarpButtonClick(); break; case InstantWarpConfirmation::None: break; @@ -66,36 +61,13 @@ InstantWarpConfirmation ResolveInstantWarpConfirmation(FleetPlayerData* fleet) void InstallInstantWarpConfirmationHooks() { - auto helper = CoursePromptPopupViewController::get_class_helper(); - if (!helper.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.ObjectViewer", "CoursePromptPopupViewController"); - return; - } - - const auto about_to_show = helper.GetMethod("AboutToShow", 0); - if (about_to_show == nullptr) { - ErrorMsg::MissingMethod("CoursePromptPopupViewController", "AboutToShow"); - return; - } - - auto widget_helper = CoursePromptPopupWidget::get_class_helper(); - if (!widget_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.Navigation", "CoursePromptPopupWidget"); - return; - } - - initiate_regular_warp = widget_helper.GetMethod("InitiateRegularWarp", 0); - if (initiate_regular_warp == nullptr) { - ErrorMsg::MissingMethod("CoursePromptPopupWidget", "InitiateRegularWarp"); - return; - } - - on_instant_warp_button_click = widget_helper.GetMethod("OnInstantWarpButtonClick", 0); - if (on_instant_warp_button_click == nullptr) { - ErrorMsg::MissingMethod("CoursePromptPopupWidget", "OnInstantWarpButtonClick"); + // The hook is useless without both popup actions, so skip it (and the label) when either has drifted. + const auto about_to_show = CoursePromptPopupViewController::Class::Method("AboutToShow", 0); + if (!about_to_show || !CoursePromptPopupWidget::Class::Method("InitiateRegularWarp", 0) + || !CoursePromptPopupWidget::Class::Method("OnInstantWarpButtonClick", 0)) { return; } - if (SPUD_STATIC_DETOUR(about_to_show, CoursePromptPopupViewController_AboutToShow_Hook)) + if (SPUD_STATIC_DETOUR(about_to_show.Pointer(), CoursePromptPopupViewController_AboutToShow_Hook)) InstallWarpActionLabel(); } diff --git a/mods/src/patches/parts/interior_zoom.cc b/mods/src/patches/parts/interior_zoom.cc index d849742dd..e651fbced 100644 --- a/mods/src/patches/parts/interior_zoom.cc +++ b/mods/src/patches/parts/interior_zoom.cc @@ -63,7 +63,7 @@ void OrbitFrameProvider_UpdateConstrains_Hook(auto original, Il2CppObject *provi auto *section_manager = Hub::get_SectionManager(); const bool in_interior = - section_manager != nullptr && section_manager->CurrentSection == SectionID::Starbase_Interior; + section_manager != nullptr && section_manager->CurrentSection() == SectionID::Starbase_Interior; const float scale = InteriorScale(); if (in_interior && scale > 1.0f) { @@ -117,7 +117,7 @@ void InstallInteriorZoomHooks() auto constraint = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.CameraController", "Constraint"); if (!orbit.isValidHelper()) { - ErrorMsg::MissingHelper("CameraController", "OrbitFrameProvider"); + spdlog::warn("Missing class {}.{}", "CameraController", "OrbitFrameProvider"); return; } @@ -153,7 +153,7 @@ void InstallInteriorZoomHooks() auto ptr_update_constrains = orbit.GetMethod("UpdateConstrains"); if (ptr_update_constrains == nullptr) { - ErrorMsg::MissingMethod("OrbitFrameProvider", "UpdateConstrains"); + spdlog::warn("Missing method {}.{}", "OrbitFrameProvider", "UpdateConstrains"); return; } if (SPUD_STATIC_DETOUR(ptr_update_constrains, OrbitFrameProvider_UpdateConstrains_Hook)) { diff --git a/mods/src/patches/parts/loading_screen.cc b/mods/src/patches/parts/loading_screen.cc index d20f31123..e8de485ea 100644 --- a/mods/src/patches/parts/loading_screen.cc +++ b/mods/src/patches/parts/loading_screen.cc @@ -4,8 +4,18 @@ namespace ls = ls_common; -static void* g_loginLogoGO = nullptr; -static void* g_loginCCLogoGO = nullptr; +namespace +{ +struct LoginSequence : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Login", "LoginSequence">; + + Component* mainCanvas() + { return Field<"_mainCanvas", Component*>(); } +}; +} // namespace + +static GameObject* g_loginLogoGO = nullptr; +static GameObject* g_loginCCLogoGO = nullptr; void ResetLoadingScreenState() { @@ -13,101 +23,61 @@ void ResetLoadingScreenState() g_loginCCLogoGO = nullptr; } -static void* FindLoginBGImage(void* transform, int depth, int maxDepth, - void* fn_cc, void* fn_ch, void* fn_gg, - void* fn_nm, void* fn_gc, void* imageType, void** outFirst) +static Image* FindLoginBGImage(Transform* transform, int depth, int maxDepth, Image** outFirst) { if (!transform || depth > maxDepth) return nullptr; - int32_t n = reinterpret_cast(fn_cc)(transform); - for (int i = 0; i < n; ++i) { - void* child = reinterpret_cast(fn_ch)(transform, i); + int32_t n = transform->childCount(); + for (int32_t i = 0; i < n; ++i) { + auto* child = transform->GetChild(i); if (!child) continue; - void* cgo = reinterpret_cast(fn_gg)(child); + auto* cgo = child->gameObject(); if (!cgo) continue; - void* img = reinterpret_cast(fn_gc)(cgo, imageType); - if (img) { + if (auto* img = cgo->GetComponent()) { if (!*outFirst) *outFirst = img; - char buf[64] = {}; - ls::ReadIl2CppString(reinterpret_cast(fn_nm)(cgo), buf, sizeof(buf)); - if (strcmp(buf, "Background") == 0 || strcmp(buf, "BG") == 0 || strcmp(buf, "bg_image") == 0 - || strcmp(buf, "LoadingBackground") == 0 || strcmp(buf, "LoginBG") == 0 || strcmp(buf, "SplashBG") == 0 - || strcmp(buf, "Image") == 0) + const auto name = cgo->Name(); + if (name == "Background" || name == "BG" || name == "bg_image" || name == "LoadingBackground" + || name == "LoginBG" || name == "SplashBG" || name == "Image") return img; } - void* found = FindLoginBGImage(child, depth + 1, maxDepth, fn_cc, fn_ch, fn_gg, fn_nm, fn_gc, imageType, outFirst); - if (found) return found; + if (auto* found = FindLoginBGImage(child, depth + 1, maxDepth, outFirst)) return found; } return nullptr; } -static void LS_LoginSequence_Awake_Hook(auto original, void* _this) +static void LS_LoginSequence_Awake_Hook(auto original, LoginSequence* _this) { original(_this); - try { - if (!Config::Get().loader_enabled) return; - - ResetLoadingScreenState(); - ls::ResetLoadingAssetsForScene(); - - auto* asset = ls::GetLoadingAsset(); - if (!asset) return; - - static auto ls_h = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Login", "LoginSequence"); - if (!ls_h.isValidHelper()) return; - static auto f_mc = ls_h.GetField("_mainCanvas"); - if (!f_mc.isValidHelper()) return; - void* canvas = *reinterpret_cast((char*)_this + f_mc.offset()); - if (!canvas) return; - - static auto co_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Component"); - static auto fn_ct = co_h.GetMethod("get_transform"); - if (!fn_ct) return; - void* canvasTrans = reinterpret_cast(fn_ct)(canvas); - if (!canvasTrans) return; - - static auto tr_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Transform"); - static auto go_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "GameObject"); - static auto im_h = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "Image"); - static auto fn_cc = tr_h.GetMethod("get_childCount"); - static auto fn_ch = tr_h.GetMethod("GetChild"); - static auto fn_gg = tr_h.GetMethod("get_gameObject"); - static auto fn_nm = go_h.GetMethod("get_name"); - static auto fn_gc = go_h.GetMethod("GetComponent", 1); - if (!fn_cc || !fn_ch || !fn_gg || !fn_nm || !fn_gc) return; - - void* imgType = im_h.GetType(); - if (!imgType) return; - - void* first = nullptr; - void* bgImg = FindLoginBGImage(canvasTrans, 0, 4, fn_cc, fn_ch, fn_gg, fn_nm, fn_gc, imgType, &first); - if (!bgImg) bgImg = first; - if (!bgImg) return; + if (!Config::Get().loader_enabled) return; + + ResetLoadingScreenState(); + ls::ResetLoadingAssetsForScene(); + + auto* asset = ls::GetLoadingAsset(); + if (!asset) return; + + auto* canvas = _this->mainCanvas(); + if (!canvas) return; + auto* canvasTrans = canvas->transform(); + if (!canvasTrans) return; + + Image* first = nullptr; + auto* bgImg = FindLoginBGImage(canvasTrans, 0, 4, &first); + if (!bgImg) bgImg = first; + if (!bgImg) return; #ifndef _USE_ORIGINAL_BG - ls::ApplySpriteToImage(bgImg, *asset); + ls::ApplySpriteToImage(bgImg, *asset); #endif - void* bgImgTr = reinterpret_cast(fn_ct)(bgImg); - if (bgImgTr) { - ls::CreateLogoOverlay(bgImgTr, g_loginLogoGO); - ls::CreateCCLogoOverlay(bgImgTr, g_loginCCLogoGO); - } - } catch (...) {} + if (auto* bgImgTr = bgImg->transform()) { + ls::CreateLogoOverlay(bgImgTr, g_loginLogoGO); + ls::CreateCCLogoOverlay(bgImgTr, g_loginCCLogoGO); + } } void InstallLoadingScreenHooks() { - auto ls_h = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Login", "LoginSequence"); - if (!ls_h.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.Login", "LoginSequence"); - return; - } - - if (auto m = ls_h.GetMethod("Awake")) { - SPUD_STATIC_DETOUR(m, LS_LoginSequence_Awake_Hook); - } else { - ErrorMsg::MissingMethod("LoginSequence", "Awake"); - spdlog::error("[LoadingScreen] LoginSequence.Awake not found"); - } + if (auto m = LoginSequence::Class::Method("Awake")) + SPUD_STATIC_DETOUR(m.Pointer(), LS_LoginSequence_Awake_Hook); } diff --git a/mods/src/patches/parts/loading_screen_common.h b/mods/src/patches/parts/loading_screen_common.h index 4f78890c7..348d867de 100644 --- a/mods/src/patches/parts/loading_screen_common.h +++ b/mods/src/patches/parts/loading_screen_common.h @@ -1,10 +1,17 @@ #pragma once #include "config.h" -#include "errormsg.h" -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include #include #include @@ -20,224 +27,77 @@ namespace ls_common { -struct FakeRect { float x, y, width, height; }; -struct FakeVector2 { float x, y; }; -struct FakeVector3 { float x, y, z; }; -struct FakeColor { float r, g, b, a; }; - -inline Il2CppObject* InvokeRuntime(const MethodInfo* method, void* target, void** args, const char* name) -{ - if (!method) return nullptr; - Il2CppObject* result = nullptr; - if (!Il2CppRuntime::TryInvoke(method, target, args, &result)) { - spdlog::warn("[LS] {} invocation failed", name); - return nullptr; - } - return result; -} - -inline bool InvokeVoid(const MethodInfo* method, void* target, void** args, const char* name) -{ - if (!method) return false; - if (!Il2CppRuntime::TryInvoke(method, target, args)) { - spdlog::warn("[LS] {} invocation failed", name); - return false; - } - return true; -} - -inline bool InvokeBool(const MethodInfo* method, void* target, void** args, const char* name) -{ - Il2CppObject* result = InvokeRuntime(method, target, args, name); - bool value = false; - return Il2CppRuntime::TryBoolean(result, value) && value; -} +struct Rect { float x, y, width, height; }; -inline int32_t InvokeInt32(const MethodInfo* method, void* target, int32_t fallback, const char* name) -{ - Il2CppObject* result = InvokeRuntime(method, target, nullptr, name); - if (!result) return fallback; - void* value = il2cpp_object_unbox(result); - return value ? *reinterpret_cast(value) : fallback; -} +struct Texture2D : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Texture2D">; +}; -inline void* InvokeObject(const MethodInfo* method, void* target, void** args, const char* name) -{ - return InvokeRuntime(method, target, args, name); -} +using ImageConversion = il2cpp::Class<"UnityEngine.ImageConversionModule", "UnityEngine", "ImageConversion">; +using Screen = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Screen">; +using Byte = il2cpp::Class<"mscorlib", "System", "Byte">; -inline void SetFullRect(void* rt, FakeVector2 aMin, FakeVector2 aMax, FakeVector2 pivot, FakeVector2 sd, FakeVector2 ap) +inline void SetFullRect(RectTransform* rt, Vector2 aMin, Vector2 aMax, Vector2 pivot, Vector2 sd, Vector2 ap) { if (!rt) return; - static auto rt_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "RectTransform"); - static auto fn_am = rt_h.GetMethodInfo("set_anchorMin"); - static auto fn_ax = rt_h.GetMethodInfo("set_anchorMax"); - static auto fn_pv = rt_h.GetMethodInfo("set_pivot"); - static auto fn_sd = rt_h.GetMethodInfo("set_sizeDelta"); - static auto fn_ap = rt_h.GetMethodInfo("set_anchoredPosition"); - if (!fn_am || !fn_ax || !fn_pv || !fn_sd || !fn_ap) return; - void* amA[1] = {&aMin}; - void* axA[1] = {&aMax}; - void* pvA[1] = {&pivot}; - void* sdA[1] = {&sd}; - void* apA[1] = {&ap}; - InvokeVoid(fn_am, rt, amA, "RT.set_anchorMin"); - InvokeVoid(fn_ax, rt, axA, "RT.set_anchorMax"); - InvokeVoid(fn_pv, rt, pvA, "RT.set_pivot"); - InvokeVoid(fn_sd, rt, sdA, "RT.set_sizeDelta"); - InvokeVoid(fn_ap, rt, apA, "RT.set_anchoredPosition"); + rt->SetAnchorMin(aMin); + rt->SetAnchorMax(aMax); + rt->SetPivot(pivot); + rt->SetSizeDelta(sd); + rt->SetAnchoredPosition(ap); } -inline void* CreateSpriteFromTexture(void* tex, int32_t width, int32_t height) +inline Sprite* CreateSpriteFromTexture(Texture2D* tex, int32_t width, int32_t height) { if (!tex || width <= 0 || height <= 0) return nullptr; - static auto spr_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Sprite"); - static auto fn_cre = spr_h.GetMethodInfo("Create", 3); - if (!fn_cre) return nullptr; - FakeRect rect{0.0f, 0.0f, static_cast(width), static_cast(height)}; - FakeVector2 pivot{0.5f, 0.5f}; - void* args[3] = {tex, &rect, &pivot}; - return InvokeObject(fn_cre, nullptr, args, "Sprite.Create"); + Rect rect{0.0f, 0.0f, static_cast(width), static_cast(height)}; + Vector2 pivot{0.5f, 0.5f}; + return Sprite::Class::Call<"Create", Sprite*>(tex, rect, pivot); } -inline bool ConfigureImageSprite(void* imgComp, void* spr, bool preserveAspect) +inline void ConfigureImageSprite(Image* image, Sprite* sprite, bool preserveAspect) { - if (!imgComp || !spr) return false; - static auto img_h = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "Image"); - static auto fn_spr = img_h.GetMethodInfo("set_sprite"); - static auto fn_col = img_h.GetMethodInfo("set_color"); - static auto fn_typ = img_h.GetMethodInfo("set_type"); - static auto fn_asp = img_h.GetMethodInfo("set_preserveAspect"); - static auto fn_ray = img_h.GetMethodInfo("set_raycastTarget"); - if (!fn_spr || !fn_col || !fn_typ || !fn_asp) return false; - - bool ok = true; - void* sprArgs[1] = {spr}; - ok &= InvokeVoid(fn_spr, imgComp, sprArgs, "Image.set_sprite"); - FakeColor white{1.0f, 1.0f, 1.0f, 1.0f}; - void* colArgs[1] = {&white}; - ok &= InvokeVoid(fn_col, imgComp, colArgs, "Image.set_color"); - int32_t simple = 0; - void* typArgs[1] = {&simple}; - ok &= InvokeVoid(fn_typ, imgComp, typArgs, "Image.set_type"); - void* aspArgs[1] = {&preserveAspect}; - ok &= InvokeVoid(fn_asp, imgComp, aspArgs, "Image.set_preserveAspect"); - if (fn_ray) { - bool rayOff = false; - void* rayArgs[1] = {&rayOff}; - ok &= InvokeVoid(fn_ray, imgComp, rayArgs, "Image.set_raycastTarget(false)"); - } - return ok; + if (!image || !sprite) return; + image->SetSprite(sprite); + image->SetColor({1.0f, 1.0f, 1.0f, 1.0f}); + image->SetType(Image::Type::Simple); + image->SetPreserveAspect(preserveAspect); + image->SetRaycastTarget(false); } -inline void HideImage(void* imgComp) +inline void HideImage(Image* image) { - if (!imgComp) return; - static auto img_h = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "Image"); - static auto fn_col = img_h.GetMethodInfo("set_color"); - FakeColor clear{0.0f, 0.0f, 0.0f, 0.0f}; - void* args[1] = {&clear}; - InvokeVoid(fn_col, imgComp, args, "Image.set_color (transparent)"); + if (image) image->SetColor({0.0f, 0.0f, 0.0f, 0.0f}); } -inline void SafeDestroy(void* obj) +// Overlays parent to the nearest ancestor carrying a Canvas, so they cover the whole screen. +inline Transform* FindRootCanvas(Transform* parentTransform) { - if (!obj) return; - try { - static auto obj_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Object"); - static auto fn_destroy = obj_h.GetMethodInfo("Destroy", 1); - if (fn_destroy) { - void* args[1] = {obj}; - InvokeVoid(fn_destroy, nullptr, args, "Object.Destroy"); - } - } catch (...) {} -} - -inline void ReadIl2CppString(void* strObj, char* buf, int bufSize) -{ - buf[0] = '\0'; - if (!strObj || bufSize <= 1) return; - const auto* chars = reinterpret_cast((ptrdiff_t)strObj + 0x14); - int i = 0; - for (; i < bufSize - 1 && chars[i]; ++i) - buf[i] = static_cast(chars[i]); - buf[i] = '\0'; -} - -inline void* FindRootCanvas(void* parentTransform) -{ - static auto go_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "GameObject"); - static auto tr_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Transform"); - static auto canvas_h = il2cpp_get_class_helper("UnityEngine.UIModule", "UnityEngine", "Canvas"); - static auto fn_get_parent = tr_h.GetMethod("get_parent"); - static auto fn_get_go = tr_h.GetMethod("get_gameObject"); - static auto fn_gc_go = go_h.GetMethod("GetComponent", 1); - void* canvasType = canvas_h.GetType(); - - void* cur = parentTransform; - while (cur) { - void* curGO = fn_get_go ? reinterpret_cast(fn_get_go)(cur) : nullptr; - if (curGO && fn_gc_go && canvasType) { - if (reinterpret_cast(fn_gc_go)(curGO, canvasType)) - return cur; - } - cur = fn_get_parent ? reinterpret_cast(fn_get_parent)(cur) : nullptr; + for (auto* cur = parentTransform; cur; cur = cur->parent()) { + auto* game_object = cur->gameObject(); + if (game_object && game_object->GetComponent()) + return cur; } return parentTransform; } -inline Il2CppGCHandle LoadTextureFromBytes(const uint8_t* data, size_t size) +inline Texture2D* LoadTextureFromBytes(const uint8_t* data, size_t size) { - if (!data || size == 0) return nullptr; - static auto tex_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Texture2D"); - static auto conv_h = il2cpp_get_class_helper("UnityEngine.ImageConversionModule", "UnityEngine", "ImageConversion"); - static auto fn_load = conv_h.GetMethodInfoSpecial("LoadImage", [](int param_count, const Il2CppType** param) { - return param_count == 3 && param[1]->type == IL2CPP_TYPE_SZARRAY && param[2]->type == IL2CPP_TYPE_BOOLEAN; - }); - if (!tex_h.isValidHelper() || !fn_load) return nullptr; - - static auto byte_h = il2cpp_get_class_helper("mscorlib", "System", "Byte"); - Il2CppArray* arr = il2cpp_array_new(byte_h.get_cls(), size); - if (!arr) return nullptr; - std::memcpy(((Il2CppArraySize*)arr)->vector, data, size); - - auto* tex = il2cpp_object_new(tex_h.get_cls()); - if (!tex) return nullptr; - - // Root both managed objects before invoking Unity; native local pointers are - // not sufficient roots if an invocation triggers a collection. - Il2CppGCHandle bytes_handle = il2cpp_gchandle_new(reinterpret_cast(arr), false); - Il2CppGCHandle texture_handle = il2cpp_gchandle_new(tex, false); - if (!bytes_handle || !texture_handle) { - if (bytes_handle) il2cpp_gchandle_free(bytes_handle); - if (texture_handle) il2cpp_gchandle_free(texture_handle); + if (!data || size == 0) return nullptr; - } - static auto ctor = tex_h.GetMethodInfoSpecial(".ctor", [](int param_count, const Il2CppType** param) { - return param_count == 4 && param[3]->type == IL2CPP_TYPE_BOOLEAN; - }); - if (ctor) { - int32_t width = 2, height = 2, textureFormat = 4; - bool mipChain = false; - void* ctorArgs[4] = {&width, &height, &textureFormat, &mipChain}; - if (!InvokeVoid(ctor, il2cpp_gchandle_get_target(texture_handle), ctorArgs, "Texture2D.ctor")) { - il2cpp_gchandle_free(bytes_handle); - il2cpp_gchandle_free(texture_handle); - return nullptr; - } - } + auto* bytes = il2cpp::Array::New(Byte::Raw(), size); + if (!bytes) + return nullptr; + std::memcpy(bytes->data(), data, size); - bool markNonReadable = false; - void* loadArgs[3] = {il2cpp_gchandle_get_target(texture_handle), il2cpp_gchandle_get_target(bytes_handle), - &markNonReadable}; - const bool loaded = InvokeBool(fn_load, nullptr, loadArgs, "ImageConversion.LoadImage"); - il2cpp_gchandle_free(bytes_handle); - if (!loaded) { - il2cpp_gchandle_free(texture_handle); + // Texture2D(width, height, TextureFormat.RGBA32, mipChain). LoadImage resizes it. + auto* tex = il2cpp::New(int32_t{2}, int32_t{2}, int32_t{4}, false); + if (!tex) return nullptr; - } - return texture_handle; + if (!ImageConversion::Call<"LoadImage", bool>(tex, bytes, false)) + return nullptr; + return tex; } // --- Texture management (shared state) --- @@ -284,7 +144,7 @@ class RootedImageAsset final spdlog::warn("[LS] {} is not a supported PNG; using fallback dimensions {}x{}", name, width_, height_); } - texture_handle_ = LoadTextureFromBytes(data, size); + texture_handle_ = il2cpp::GcHandle::Strong(LoadTextureFromBytes(data, size)); if (!texture_handle_) { spdlog::warn("[LS] failed to load and root {} texture", name); return false; @@ -294,38 +154,30 @@ class RootedImageAsset final return true; } - [[nodiscard]] void* Texture() const - { - return texture_handle_ ? il2cpp_gchandle_get_target(texture_handle_) : nullptr; - } + [[nodiscard]] Texture2D* Texture() const + { return texture_handle_.Get(); } - [[nodiscard]] void* Sprite() + [[nodiscard]] ::Sprite* Sprite() { - if (sprite_handle_) return il2cpp_gchandle_get_target(sprite_handle_); + if (sprite_handle_) + return sprite_handle_.Get<::Sprite>(); - void* texture = Texture(); + auto* texture = Texture(); if (!texture) return nullptr; - auto* sprite = reinterpret_cast(CreateSpriteFromTexture(texture, width_, height_)); + auto* sprite = CreateSpriteFromTexture(texture, width_, height_); if (!sprite) { spdlog::warn("[LS] failed to create {} sprite", name_); return nullptr; } - sprite_handle_ = il2cpp_gchandle_new(sprite, false); - if (!sprite_handle_) spdlog::warn("[LS] failed to root {} sprite", name_); - return sprite_handle_ ? il2cpp_gchandle_get_target(sprite_handle_) : nullptr; + sprite_handle_ = il2cpp::GcHandle::Strong(sprite); + return sprite; } void Reset() { - if (sprite_handle_) { - il2cpp_gchandle_free(sprite_handle_); - sprite_handle_ = nullptr; - } - if (texture_handle_) { - il2cpp_gchandle_free(texture_handle_); - texture_handle_ = nullptr; - } + sprite_handle_.Reset(); + texture_handle_.Reset(); width_ = 0; height_ = 0; name_ = "loading asset"; @@ -335,8 +187,8 @@ class RootedImageAsset final [[nodiscard]] int32_t Height() const { return height_; } private: - Il2CppGCHandle texture_handle_ = nullptr; - Il2CppGCHandle sprite_handle_ = nullptr; + il2cpp::GcHandle texture_handle_; + il2cpp::GcHandle sprite_handle_; int32_t width_ = 0; int32_t height_ = 0; const char* name_ = "loading asset"; @@ -373,8 +225,10 @@ inline RootedImageAsset* GetLoadingAsset() auto& asset = LoadingAsset(); if (asset.Texture()) return &asset; + // Hooks call this, so nothing may throw here: an unreadable path falls back to the embedded image. const std::string& path = Config::Get().loader_image; - if (!path.empty() && std::filesystem::exists(path)) { + std::error_code error; + if (!path.empty() && std::filesystem::exists(path, error)) { std::ifstream f(path, std::ios::binary); if (f.is_open()) { std::vector data(std::istreambuf_iterator(f), {}); @@ -406,161 +260,96 @@ inline RootedImageAsset* GetCCLogoAsset() : nullptr; } -inline void* CreateImageOverlay(const char* name, RootedImageAsset& asset, void* parentTransform, FakeVector2 aMin, - FakeVector2 aMax, FakeVector2 pivot, FakeVector2 sd, FakeVector2 ap, - bool preserveAspect, bool placeAsFirstSibling) +inline GameObject* CreateImageOverlay(const char* name, RootedImageAsset& asset, Transform* parentTransform, + Vector2 aMin, Vector2 aMax, Vector2 pivot, Vector2 sd, Vector2 ap, + bool preserveAspect, bool placeAsFirstSibling) { if (!parentTransform) return nullptr; - static auto go_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "GameObject"); - static auto rt_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "RectTransform"); - static auto tr_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Transform"); - static auto img_h = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "Image"); - - static auto fn_go_ctor = go_h.GetMethodInfoSpecial(".ctor", [](int n, const Il2CppType** p) { - return n == 1 && p[0]->type == IL2CPP_TYPE_STRING; - }); - static auto fn_get_tr = go_h.GetMethodInfo("get_transform"); - static auto fn_setpar = tr_h.GetMethodInfoSpecial("SetParent", [](int n, const Il2CppType**) { return n == 2; }); - static auto fn_setfst = tr_h.GetMethodInfo("SetAsFirstSibling"); - static auto fn_addcomp = go_h.GetMethodInfo("AddComponent", 1); - static auto fn_gc = go_h.GetMethodInfo("GetComponent", 1); - if (!fn_go_ctor || !fn_get_tr || !fn_setpar || !fn_addcomp || !fn_gc) return nullptr; - - void* spr = asset.Sprite(); + auto* spr = asset.Sprite(); if (!spr) return nullptr; - auto* go = il2cpp_object_new(go_h.get_cls()); + auto* go = il2cpp::New(il2cpp_string_new(name)); if (!go) return nullptr; - Il2CppGCHandle go_handle = il2cpp_gchandle_new(go, false); - if (!go_handle) return nullptr; - void* nameStr = il2cpp_string_new(name); - void* goArgs[1] = {nameStr}; - if (!InvokeVoid(fn_go_ctor, go, goArgs, "GameObject.ctor")) { - il2cpp_gchandle_free(go_handle); - return nullptr; - } - - void* tr = InvokeObject(fn_get_tr, go, nullptr, "get_transform"); - if (!tr) { - il2cpp_gchandle_free(go_handle); - return nullptr; - } - bool worldStays = false; - void* parArgs[2] = {FindRootCanvas(parentTransform), &worldStays}; - if (!parArgs[0] || !InvokeVoid(fn_setpar, tr, parArgs, "Transform.SetParent")) { - il2cpp_gchandle_free(go_handle); - return nullptr; - } - if (placeAsFirstSibling && fn_setfst) - InvokeVoid(fn_setfst, tr, nullptr, "Transform.SetAsFirstSibling"); + auto* tr = go->GetComponent(); + if (!tr) return nullptr; + tr->SetParent(FindRootCanvas(parentTransform), false); + if (placeAsFirstSibling) + tr->SetAsFirstSibling(); - void* imgType = img_h.GetType(); - if (!imgType) { - il2cpp_gchandle_free(go_handle); - return nullptr; - } - void* acArgs[1] = {imgType}; - void* imgComp = InvokeObject(fn_addcomp, go, acArgs, "AddComponent"); - if (!imgComp) { - il2cpp_gchandle_free(go_handle); - return nullptr; - } + // Adding a Graphic turns the plain Transform into a RectTransform. + auto* image = go->AddComponent(); + if (!image) return nullptr; - void* rtType = rt_h.GetType(); - if (!rtType) { - il2cpp_gchandle_free(go_handle); - return nullptr; - } - void* rtArgs[1] = {rtType}; - void* rt = InvokeObject(fn_gc, go, rtArgs, "GetComponent"); - if (!rt) { - il2cpp_gchandle_free(go_handle); - return nullptr; - } + auto* rt = go->GetComponent(); + if (!rt) return nullptr; SetFullRect(rt, aMin, aMax, pivot, sd, ap); + ConfigureImageSprite(image, spr, preserveAspect); - if (!ConfigureImageSprite(imgComp, spr, preserveAspect)) { - il2cpp_gchandle_free(go_handle); - return nullptr; - } - - void* result = il2cpp_gchandle_get_target(go_handle); - il2cpp_gchandle_free(go_handle); spdlog::debug("[LS] created {} overlay", name); - return result; + return go; } -inline void CreateLogoOverlayEx(const char* name, RootedImageAsset* asset, void* parentTransform, void*& outGO, - float xSide) +inline void CreateLogoOverlayEx(const char* name, RootedImageAsset* asset, Transform* parentTransform, + GameObject*& outGO, float xSide) { - if (outGO) return; - try { - if (!asset || !parentTransform) return; - - constexpr float kRefScreenWidth = 1920.0f; - constexpr float kRefLogoWidth = 150.0f; - constexpr float kRefPadX = 30.0f; - constexpr float kRefPadY = 185.0f; - - static auto screen_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Screen"); - static auto fn_sw = screen_h.GetMethodInfo("get_width"); - static auto fn_sh = screen_h.GetMethodInfo("get_height"); - float sw = (float)(fn_sw ? InvokeInt32(fn_sw, nullptr, 0, "Screen.get_width") : 0); - float sh = (float)(fn_sh ? InvokeInt32(fn_sh, nullptr, 0, "Screen.get_height") : 0); - if (sw <= 0.0f) sw = 1334.0f; - if (sh <= 0.0f) sh = 750.0f; - - float scale = (sw / kRefScreenWidth) * Config::Get().loader_logo_scale; - - const int32_t lw = asset->Width(); - const int32_t lh = asset->Height(); - float logoWidth = kRefLogoWidth * scale; - float logoPixH = logoWidth * ((lw > 0) ? (float)lh / (float)lw : 1.0f); - float padX = kRefPadX * scale; - float padY = kRefPadY * scale; - - FakeVector2 aMin, aMax; - if (xSide < 0.0f) { - aMin = {padX / sw, padY / sh}; - aMax = {(padX + logoWidth) / sw, (padY + logoPixH) / sh}; - } else { - aMin = {(sw - padX - logoWidth) / sw, padY / sh}; - aMax = {(sw - padX) / sw, (padY + logoPixH) / sh}; - } + if (outGO || !asset || !parentTransform) return; + + constexpr float kRefScreenWidth = 1920.0f; + constexpr float kRefLogoWidth = 150.0f; + constexpr float kRefPadX = 30.0f; + constexpr float kRefPadY = 185.0f; + + float sw = static_cast(Screen::Get<"width", int32_t>()); + float sh = static_cast(Screen::Get<"height", int32_t>()); + if (sw <= 0.0f) sw = 1334.0f; + if (sh <= 0.0f) sh = 750.0f; + + float scale = (sw / kRefScreenWidth) * Config::Get().loader_logo_scale; + + const int32_t lw = asset->Width(); + const int32_t lh = asset->Height(); + float logoWidth = kRefLogoWidth * scale; + float logoPixH = logoWidth * ((lw > 0) ? (float)lh / (float)lw : 1.0f); + float padX = kRefPadX * scale; + float padY = kRefPadY * scale; + + Vector2 aMin, aMax; + if (xSide < 0.0f) { + aMin = {padX / sw, padY / sh}; + aMax = {(padX + logoWidth) / sw, (padY + logoPixH) / sh}; + } else { + aMin = {(sw - padX - logoWidth) / sw, padY / sh}; + aMax = {(sw - padX) / sw, (padY + logoPixH) / sh}; + } - outGO = CreateImageOverlay(name, *asset, parentTransform, aMin, aMax, - {0.5f, 0.5f}, {0.0f, 0.0f}, {0.0f, 0.0f}, - true, false); - } catch (...) {} + outGO = CreateImageOverlay(name, *asset, parentTransform, aMin, aMax, + {0.5f, 0.5f}, {0.0f, 0.0f}, {0.0f, 0.0f}, + true, false); } -inline void CreateLogoOverlay(void* parentTransform, void*& logoGO) +inline void CreateLogoOverlay(Transform* parentTransform, GameObject*& logoGO) { CreateLogoOverlayEx("STFCModLogo", GetLogoAsset(), parentTransform, logoGO, 1.0f); } -inline void CreateCCLogoOverlay(void* parentTransform, void*& ccLogoGO) +inline void CreateCCLogoOverlay(Transform* parentTransform, GameObject*& ccLogoGO) { CreateLogoOverlayEx("STFCCCLogo", GetCCLogoAsset(), parentTransform, ccLogoGO, -1.0f); } -inline void ApplySpriteToImage(void* imageComp, RootedImageAsset& asset) +inline void ApplySpriteToImage(Image* image, RootedImageAsset& asset) { - if (!imageComp) return; - void* spr = asset.Sprite(); + if (!image) return; + auto* spr = asset.Sprite(); if (!spr) return; - static auto img_h = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "Image"); - static auto fn_ovr = img_h.GetMethodInfo("set_overrideSprite"); - static auto fn_drt = img_h.GetMethodInfo("SetVerticesDirty"); - void* sprArgs[1] = {spr}; - InvokeVoid(fn_ovr, imageComp, sprArgs, "Image.set_overrideSprite"); - if (!ConfigureImageSprite(imageComp, spr, false)) return; - InvokeVoid(fn_drt, imageComp, nullptr, "Graphic.SetVerticesDirty"); + image->SetOverrideSprite(spr); + ConfigureImageSprite(image, spr, false); + image->SetVerticesDirty(); } -inline void* CreateBGOverlay(void* parentTransform, RootedImageAsset& asset) +inline GameObject* CreateBGOverlay(Transform* parentTransform, RootedImageAsset& asset) { return CreateImageOverlay("STFCModBG", asset, parentTransform, {0.0f, 0.0f}, {1.0f, 1.0f}, {0.5f, 0.5f}, diff --git a/mods/src/patches/parts/loading_tip.cc b/mods/src/patches/parts/loading_tip.cc index 22461f62b..a1f77665f 100644 --- a/mods/src/patches/parts/loading_tip.cc +++ b/mods/src/patches/parts/loading_tip.cc @@ -1,10 +1,7 @@ #include "config.h" -#include "errormsg.h" #include "version.h" -#include -#include -#include +#include #include #include @@ -15,7 +12,19 @@ namespace static bool g_isLoadingScreen = true; static std::mt19937 g_rng{std::random_device{}()}; -constexpr ptrdiff_t kTextMeshProOffset = 0x20; +struct TMP_Text : il2cpp::Object { + using Class = il2cpp::Class<"Unity.TextMeshPro", "TMPro", "TMP_Text">; + + void SetText(Il2CppString* value) + { Set<"text">(value); } +}; + +struct LoadingTipViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Prime.LoadingScreen", "LoadingTipViewController">; + + TMP_Text* textMeshPro() + { return Field<"_textMeshPro", TMP_Text*>(); } +}; struct TipEntry { @@ -41,20 +50,9 @@ std::string GetLoadingScreenTip() VER_FILE_VERSION_STR VERSION_PATCH_STR "! Please check our discord for the latest information!"; } -void SetTMPText(void* tmpObj, const std::string& text) +void SetTMPText(TMP_Text* tmp, const std::string& text) { - if (!tmpObj) return; - - void* tipStr = il2cpp_string_new(text.c_str()); - if (!tipStr) return; - - static auto tmp_h = il2cpp_get_class_helper("Unity.TextMeshPro", "TMPro", "TMP_Text"); - static auto fn_text = tmp_h.GetMethodInfo("set_text"); - if (!fn_text) return; - - void* args[1] = {tipStr}; - Il2CppException* exception = nullptr; - il2cpp_runtime_invoke(fn_text, tmpObj, args, &exception); + if (tmp) tmp->SetText(il2cpp_string_new(text.c_str())); } static size_t g_lastCustomTipIdx = SIZE_MAX; @@ -100,29 +98,26 @@ std::string PickTransitionTip() return kCustomTips[indices[numCandidates - 1]].text; } -void LTVC_SetRandomTipLocalisedText_Hook(auto original, void* _this) +void LTVC_SetRandomTipLocalisedText_Hook(auto original, LoadingTipViewController* _this) { original(_this); - try { - const auto& cfg = Config::Get(); - if (!cfg.loader_tip_enabled) return; + const auto& cfg = Config::Get(); + if (!cfg.loader_tip_enabled) return; - std::string chosenTip; + std::string chosenTip; - if (g_isLoadingScreen) { - chosenTip = GetLoadingScreenTip(); - g_isLoadingScreen = false; - } else { - chosenTip = PickTransitionTip(); - } + if (g_isLoadingScreen) { + chosenTip = GetLoadingScreenTip(); + g_isLoadingScreen = false; + } else { + chosenTip = PickTransitionTip(); + } - if (chosenTip.empty()) - return; + if (chosenTip.empty()) + return; - void* tmp = *reinterpret_cast((char*)_this + kTextMeshProOffset); - SetTMPText(tmp, chosenTip); - } catch (...) {} + SetTMPText(_this->textMeshPro(), chosenTip); } } // namespace @@ -135,21 +130,6 @@ void ResetLoadingTipState() void InstallLoadingTipHooks() { - auto ltv_h = il2cpp_get_class_helper("Assembly-CSharp", "Prime.LoadingScreen", "LoadingTipViewController"); - if (!ltv_h.isValidHelper()) { - ErrorMsg::MissingHelper("Prime.LoadingScreen", "LoadingTipViewController"); - return; - } - - bool ok = false; - - if (auto m = ltv_h.GetMethod("SetRandomTipLocalisedText")) { - SPUD_STATIC_DETOUR(m, LTVC_SetRandomTipLocalisedText_Hook); - ok = true; - } else { - ErrorMsg::MissingMethod("LoadingTipViewController", "SetRandomTipLocalisedText"); - } - - if (!ok) - spdlog::error("[LoadingTip] Failed to install hooks"); + if (auto m = LoadingTipViewController::Class::Method("SetRandomTipLocalisedText")) + SPUD_STATIC_DETOUR(m.Pointer(), LTVC_SetRandomTipLocalisedText_Hook); } diff --git a/mods/src/patches/parts/misc.cc b/mods/src/patches/parts/misc.cc index 1b1231d3d..05ecf0646 100644 --- a/mods/src/patches/parts/misc.cc +++ b/mods/src/patches/parts/misc.cc @@ -1,16 +1,16 @@ #include "config.h" -#include "errormsg.h" +#include "prime/InterstitialViewController.h" #include #include #include -#include #include #include #include -#include +#include +#include #include #if _WIN32 @@ -34,7 +34,7 @@ void InventoryForPopup_set_MaxItemsToUse(auto original, InventoryForPopup* a1, i return; } - if (a1->IsDonationUse && a2 == 50 && Config::Get().extend_donation_slider) { + if (a1->IsDonationUse() && a2 == 50 && Config::Get().extend_donation_slider) { const auto max = Config::Get().extend_donation_max; if (max > 0) { a2 = max; @@ -53,12 +53,12 @@ void InventoryForPopup_set_MaxItemsToUse(auto original, InventoryForPopup* a1, i void InventoryUseRowWidget_SetWidgetData(auto original, InventoryUseRowWidget* widget) { if (widget) { - if (auto* context = widget->Context; context && context->IsChestPurchase) { - const auto native_max = context->MaxItemsToUse; + if (auto* context = widget->Context(); context && context->IsChestPurchase()) { + const auto native_max = context->MaxItemsToUse(); const auto requested_max = static_cast(Config::Get().extend_chest_purchase_max); const auto bounded_max = std::min(requested_max, kMaximumChestPurchaseMax); if (native_max > 0 && bounded_max > native_max) { - context->MaxItemsToUse = bounded_max; + context->SetMaxItemsToUse(bounded_max); } } } @@ -78,7 +78,7 @@ void UnitySlider_set_maxValue(auto original, void* a1, float a2) void BundleDataWidget_OnActionButtonPressedCallback(auto original, BundleDataWidget* _this) { - if (_this->CurrentState & BundleDataWidget::ItemState::CooldownTimerOn) { + if (_this->CurrentState() & BundleDataWidget::ItemState::CooldownTimerOn) { _this->AuxViewButtonPressedHandler(); } else { original(_this); @@ -88,65 +88,32 @@ void BundleDataWidget_OnActionButtonPressedCallback(auto original, BundleDataWid void InstallMiscPatches() { #if _WIN32 - auto h = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Inventories", "InventoryForPopup"); - if (!h.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.Inventories", "InventoryForPopup"); - } else { - auto ptr = h.GetMethod("set_MaxItemsToUse"); - if (!ptr) { - ErrorMsg::MissingMethod("InventoryForPopup", "set_MaxItemsToUse"); - } else { - SPUD_STATIC_DETOUR(ptr, InventoryForPopup_set_MaxItemsToUse); - } - } + if (auto m = InventoryForPopup::Class::Method("set_MaxItemsToUse")) + SPUD_STATIC_DETOUR(m.Pointer(), InventoryForPopup_set_MaxItemsToUse); #endif if (Config::Get().extend_chest_purchase_max > 0) { - auto row_widget = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Inventories", "InventoryUseRowWidget"); - if (!row_widget.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.Inventories", "InventoryUseRowWidget"); - } else { - auto ptr = row_widget.GetMethod("SetWidgetData", 0); - if (!ptr) { - ErrorMsg::MissingMethod("InventoryUseRowWidget", "SetWidgetData"); - } else { - SPUD_STATIC_DETOUR(ptr, InventoryUseRowWidget_SetWidgetData); - } - } + if (auto m = InventoryUseRowWidget::Class::Method("SetWidgetData", 0)) + SPUD_STATIC_DETOUR(m.Pointer(), InventoryUseRowWidget_SetWidgetData); } #if __APPLE__ - auto unity_slider = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "Slider"); - if (!unity_slider.isValidHelper()) { - ErrorMsg::MissingHelper("UnityEngine.UI", "Slider"); - } else { - auto set_max_ptr = unity_slider.GetMethod("set_maxValue"); - if (!set_max_ptr) { - ErrorMsg::MissingMethod("Slider", "set_maxValue"); - } else { - SPUD_STATIC_DETOUR(set_max_ptr, UnitySlider_set_maxValue); - } - } + using Slider = il2cpp::Class<"UnityEngine.UI", "UnityEngine.UI", "Slider">; + if (auto m = Slider::Method("set_maxValue")) + SPUD_STATIC_DETOUR(m.Pointer(), UnitySlider_set_maxValue); #endif - auto bundle_data_widget = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Shop", "BundleDataWidget"); - if (!bundle_data_widget.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.Shop", "BundleDataWidget"); - } else { - auto ptr = bundle_data_widget.GetMethod("OnActionButtonPressedCallback"); - if (!ptr) { - ErrorMsg::MissingMethod("BundleDataWidget", "OnActionButtonPressedCallback"); - } else - SPUD_STATIC_DETOUR(ptr, BundleDataWidget_OnActionButtonPressedCallback); - } + if (auto m = BundleDataWidget::Class::Method("OnActionButtonPressedCallback")) + SPUD_STATIC_DETOUR(m.Pointer(), BundleDataWidget_OnActionButtonPressedCallback); } -IList* ExtractBuffsOfType_Hook(auto original, ClientModifierType modifier, IList* list) +// List in and out. +il2cpp::List* ExtractBuffsOfType_Hook(auto original, ClientModifierType modifier, + il2cpp::List* list) { if (list) { - for (int i = 0; i < list->Count; ++i) { - auto item = list->Get(i); - if (item == 0) { + for (auto* item : *list) { + if (!item) { return nullptr; } } @@ -163,188 +130,57 @@ bool ShouldShowRevealHook(auto original, void* _this, bool ignore) return result; } -struct ShopCategory { -public: - __declspec(property(get = __get__flagValue)) int Value; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.Prime.Shop", "ShopCategory"); - return class_helper; - } -public: - int __get__flagValue() - { - static auto field = get_class_helper().GetProperty("Value"); - return *field.GetUnboxedSelf(this); - } -}; - -struct CurrencyType { -public: - __declspec(property(get = __get__flagValue)) int Value; - // - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimePlatform.Content", "CurrencyType"); - return class_helper; - } -public: - int __get__flagValue() - { - static auto field = get_class_helper().GetProperty("value"); - return *field.GetUnboxedSelf(this); - } -}; - -struct BundleGroupConfig { -public: - __declspec(property(get = __get__category)) int _category; - __declspec(property(get = __get__currency)) int _currency; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Shop", "BundleGroupConfig"); - return class_helper; - } - -public: - int __get__category() - { - static auto field = get_class_helper().GetField("_category"); - return *(int*)((ptrdiff_t)this + field.offset()); - } - - int __get__currency() - { - static auto field = get_class_helper().GetField("_currency"); - return *(int*)((ptrdiff_t)this + field.offset()); - } -}; - -class ShopSectionContext +void ActionQueueManager_AddActionToQueue(auto original, ActionQueueManager* _this, int64_t fleet_id) { -public: - __declspec(property(get = __get__bundleConfig)) BundleGroupConfig* _bundleConfig; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Shop", "ShopSectionContext"); - return class_helper; - } + spdlog::warn("ActionQueueManager_AddActionToQueue({})", fleet_id); + original(_this, fleet_id); +} + +// const auto section_data = Hub::get_SectionManager()->_sectionStorage->GetState(sectionID); -public: - BundleGroupConfig* __get__bundleConfig() - { - static auto field = get_class_helper().GetProperty("BundleGroup"); - return field.GetRaw(this); - } -}; -static std::atomic_bool g_first_popup_shown{false}; +std::atomic_bool popup_shown{false}; bool ShouldSuppressAdditionalPopup() { bool expected = false; - return !g_first_popup_shown.compare_exchange_strong(expected, true); -} - -void InterstitialViewController_AboutToShow(auto original, InterstitialViewController* _this) -{ - original(_this); - if (Config::Get().only_show_first_popup && _this != nullptr) { - if (ShouldSuppressAdditionalPopup()) { - spdlog::debug("InterstitialViewController_AboutToShow: suppressing interstitial popup (already shown one)"); - _this->CloseWhenReady(); - } else { - spdlog::debug("InterstitialViewController_AboutToShow: allowing first popup of the session to show"); - } - } + return !popup_shown.compare_exchange_strong(expected, true); } -void ShopSceneManager_ShowPlcOfferPopup(auto original, void* _this) +void InterstitialViewController_AboutToShow_Hook(auto original, InterstitialViewController* _this) { - if (Config::Get().only_show_first_popup && ShouldSuppressAdditionalPopup()) { - spdlog::debug("ShopSceneManager_ShowPlcOfferPopup: suppressing PLC offer popup (already shown one)"); - return; - } original(_this); + if (Config::Get().only_show_first_popup && _this && ShouldSuppressAdditionalPopup()) + _this->CloseWhenReady(); } -void ActionQueueManager_AddActionToQueue(auto original, ActionQueueManager* _this, long fleet_id) +void ShopSceneManager_ShowPlcOfferPopup_Hook(auto original, void* _this) { - spdlog::warn("ActionQueueManager_AddActionToQueue({})", fleet_id); - original(_this, fleet_id); + if (!Config::Get().only_show_first_popup || !ShouldSuppressAdditionalPopup()) + original(_this); } -// const auto section_data = Hub::get_SectionManager()->_sectionStorage->GetState(sectionID); - void InstallTempCrashFixes() { - auto BuffService_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "BuffService"); - if (!BuffService_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Services", "BuffService"); - } else { - auto ptr_extract_buffs_of_type = BuffService_helper.GetMethod("ExtractBuffsOfType"); - if (ptr_extract_buffs_of_type == nullptr) { - ErrorMsg::MissingMethod("BuffService", "ExtractBuffsOfType"); - } else { - SPUD_STATIC_DETOUR(ptr_extract_buffs_of_type, ExtractBuffsOfType_Hook); - } - } + using BuffService = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "BuffService">; + if (auto m = BuffService::Method("ExtractBuffsOfType")) + SPUD_STATIC_DETOUR(m.Pointer(), ExtractBuffsOfType_Hook); - static auto shop_scene_manager = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Shop", "ShopSceneManager"); - if (!shop_scene_manager.isValidHelper()) { - ErrorMsg::MissingHelper("Shop", "ShopSceneManager"); - } else { - auto reveal_show = shop_scene_manager.GetMethod("ShouldShowRevealSequence"); - if (reveal_show == nullptr) { - ErrorMsg::MissingMethod("ShopSceneManager", "ShouldShowRevealSequence"); - } else { - SPUD_STATIC_DETOUR(reveal_show, ShouldShowRevealHook); - } + using ShopSceneManager = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Shop", "ShopSceneManager">; + if (auto m = ShopSceneManager::Method("ShouldShowRevealSequence")) + SPUD_STATIC_DETOUR(m.Pointer(), ShouldShowRevealHook); - auto show_plc = shop_scene_manager.GetMethod("ShowPlcOfferPopup", 0); - if (!show_plc) { - ErrorMsg::MissingMethod("ShopSceneManager", "ShowPlcOfferPopup"); - } else { - SPUD_STATIC_DETOUR(show_plc, ShopSceneManager_ShowPlcOfferPopup); - } - } - static auto interstitial_controller = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Interstitial", "InterstitialViewController"); - if (!interstitial_controller.isValidHelper()) { - ErrorMsg::MissingHelper("Interstitial", "InterstitialViewController"); - } else { - auto interstitial_show = interstitial_controller.GetMethod("AboutToShow"); - if (interstitial_show == nullptr) { - ErrorMsg::MissingMethod("InterstitialViewController", "AboutToShow"); - } else { - SPUD_STATIC_DETOUR(interstitial_show, InterstitialViewController_AboutToShow); - } - } + // Debug-only trace, left disabled. + // if (auto m = ActionQueueManager::Class::Method("AddActionToQueue")) + // SPUD_STATIC_DETOUR(m.Pointer(), ActionQueueManager_AddActionToQueue); - static auto actionqueue_manager = - il2cpp_get_class_helper("Assembly-CSharp", "Prime.ActionQueue", "ActionQueueManager"); - if (!actionqueue_manager.isValidHelper()) { - ErrorMsg::MissingHelper("ActionQueue", "ActionQueueManager"); - } else { - auto addtoqueue_method = actionqueue_manager.GetMethod("AddActionToQueue"); - if (addtoqueue_method == nullptr) { - ErrorMsg::MissingMethod("ActionQueueManager", "AddActionToQueue"); - } else { - // SPUD_STATIC_DETOUR(addtoqueue_method, ActionQueueManager_AddActionToQueue); - } - } + using PopupShop = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Shop", "ShopSceneManager">; + if (auto m = PopupShop::Method("ShowPlcOfferPopup", 0)) + SPUD_STATIC_DETOUR(m.Pointer(), ShopSceneManager_ShowPlcOfferPopup_Hook); + if (auto m = InterstitialViewController::Class::Method("AboutToShow", 0)) + SPUD_STATIC_DETOUR(m.Pointer(), InterstitialViewController_AboutToShow_Hook); } diff --git a/mods/src/patches/parts/mission_hud_tweaks.cc b/mods/src/patches/parts/mission_hud_tweaks.cc index 8eeee11fc..589352a2f 100644 --- a/mods/src/patches/parts/mission_hud_tweaks.cc +++ b/mods/src/patches/parts/mission_hud_tweaks.cc @@ -1,8 +1,7 @@ #include "config.h" -#include "errormsg.h" -#include -#include +#include +#include #include #include @@ -12,32 +11,34 @@ #include #include #include +#include #include namespace { +struct MissionsHudViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.HUD", "MissionsHudViewController">; +}; + +// The buttons are a mix of Button and GenericButtonWidget fields; only their GameObject matters here. +template Component* ButtonField(MissionsHudViewController* controller) +{ return controller->Field(); } + struct MissionHudButtonDefinition { const char* canonical_name; - const char* field_name; - MissionHudVisibility visibility = MissionHudVisibility::Auto; - ptrdiff_t field_offset = 0; - bool field_valid = false; + Component* (*button)(MissionsHudViewController*); + MissionHudVisibility visibility = MissionHudVisibility::Auto; }; -using ComponentGetGameObjectFn = void* (*)(void*); -using GameObjectSetActiveFn = void (*)(void*, bool); - -std::array g_button_definitions{{ - {"q_trials", "_challengesButton"}, - {"field_training", "_achievementsButton"}, - {"outposts", "_outpostsButton"}, - {"missions", "_missionsButton"}, +std::array g_button_definitions{{ + {"q_trials", ButtonField<"_challengesButton">}, + {"field_training", ButtonField<"_achievementsButton">}, + {"outposts", ButtonField<"_outpostsButton">}, + {"daily_goals", ButtonField<"_dailyGoalsButton">}, + {"missions", ButtonField<"_missionsButton">}, }}; std::vector g_configured_buttons; -ComponentGetGameObjectFn g_get_game_object = nullptr; -GameObjectSetActiveFn g_set_active = nullptr; -bool g_ready = false; std::string_view to_string(MissionHudVisibility visibility) { @@ -79,193 +80,46 @@ std::string ConfiguredButtonModes() return modes; } -bool ResolveButtonFields(IL2CppClassHelper& controller_helper, Il2CppClass* component_class) -{ - auto valid_count = 0; - for (auto* button : g_configured_buttons) { - auto field = controller_helper.GetField(button->field_name); - auto* info = field.get_info(); - const auto* type = info ? info->type : nullptr; - auto* field_class = type ? il2cpp_class_from_type(type) : nullptr; - if (!info || !type || type->byref || (il2cpp_field_get_flags(info) & FIELD_ATTRIBUTE_STATIC) - || (type->type != IL2CPP_TYPE_CLASS && type->type != IL2CPP_TYPE_GENERICINST) - || !field_class || !il2cpp_class_is_assignable_from(component_class, field_class) - || info->offset < sizeof(Il2CppObject) - || info->offset + sizeof(void*) > il2cpp_class_instance_size(controller_helper.get_cls())) { - spdlog::error("MissionHudTweaks: unable to find MissionsHudViewController field '{}'", button->field_name); - continue; - } - - button->field_offset = field.offset(); - button->field_valid = true; - valid_count++; - spdlog::info("MissionHudTweaks: mapped {} -> {}", button->canonical_name, button->field_name); - } - - return valid_count > 0; -} - -void ApplyButtonVisibility(void* controller, const MissionHudButtonDefinition& button) +void ApplyButtonVisibility(MissionsHudViewController* controller, const MissionHudButtonDefinition& button) { - if (!controller || !button.field_valid || !g_get_game_object || !g_set_active) { - return; - } - - auto* component = *reinterpret_cast(reinterpret_cast(controller) + button.field_offset); + auto* component = button.button(controller); if (!component) { return; } - if (auto* game_object = g_get_game_object(component)) { - g_set_active(game_object, button.visibility == MissionHudVisibility::Always); + if (auto* game_object = component->gameObject()) { + game_object->SetActive(button.visibility == MissionHudVisibility::Always); } } } // namespace -// Visibility is now refreshed through several independent paths. Keep the game's -// setup/notification work, then apply only explicitly configured overrides. -void ApplyConfiguredButtonVisibility(void* controller) +int32_t MissionsHudViewController_UpdateButtons_Hook(auto original, MissionsHudViewController* controller) { - if (!g_ready) - return; + const auto result = original(controller); + for (auto* button : g_configured_buttons) { button->visibility = Config::Get().MissionHudButtonVisibility(button->canonical_name); - if (button->visibility != MissionHudVisibility::Auto) + if (button->visibility != MissionHudVisibility::Auto) { ApplyButtonVisibility(controller, *button); + } } -} - -void MissionsHudViewController_OnEnable_Hook(auto original, void* controller) -{ - original(controller); - ApplyConfiguredButtonVisibility(controller); -} - -void MissionsHudViewController_SetupAchievementsButton_Hook(auto original, void* controller) -{ - original(controller); - ApplyConfiguredButtonVisibility(controller); -} -void MissionsHudViewController_SetupChallengesButton_Hook(auto original, void* controller, bool replace_with_outposts) -{ - original(controller, replace_with_outposts); - ApplyConfiguredButtonVisibility(controller); -} - -void MissionsHudViewController_SetupOutpostsButton_Hook(auto original, void* controller, bool show_outposts) -{ - original(controller, show_outposts); - ApplyConfiguredButtonVisibility(controller); -} - -void MissionsHudViewController_HandleOutpostsAndChallengesHUD_Hook(auto original, void* controller) -{ - // In planetary view this method hides challenges directly, bypassing its setup - // method. Reapply after the whole operation, including the planetary branch. - original(controller); - ApplyConfiguredButtonVisibility(controller); + return result; } void InstallMissionHudTweaksHooks() { g_configured_buttons = LoadConfiguredButtons(); - auto controller_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.HUD", "MissionsHudViewController"); - if (!controller_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.HUD", "MissionsHudViewController"); - return; - } - - auto component_helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Component"); - if (!component_helper.isValidHelper()) { - ErrorMsg::MissingHelper("UnityEngine", "Component"); - return; - } - - if (!ResolveButtonFields(controller_helper, component_helper.get_cls())) - return; - const auto* get_game_object = method_contract::Resolve( - component_helper.get_cls(), "get_gameObject", false, "UnityEngine.GameObject", {}); - g_get_game_object = reinterpret_cast(method_contract::Pointer(get_game_object)); - if (!g_get_game_object) { - ErrorMsg::MissingMethod("Component", "get_gameObject"); - return; - } - - auto game_object_helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "GameObject"); - if (!game_object_helper.isValidHelper()) { - ErrorMsg::MissingHelper("UnityEngine", "GameObject"); - return; - } - - const auto* set_active = method_contract::Resolve( - game_object_helper.get_cls(), "SetActive", false, "System.Void", {"System.Boolean"}); - g_set_active = reinterpret_cast(method_contract::Pointer(set_active)); - if (!g_set_active) { - ErrorMsg::MissingMethod("GameObject", "SetActive"); + auto update_buttons = MissionsHudViewController::Class::Method("UpdateButtons", 0); + if (!update_buttons) { return; } - // Resolve the complete surface before installing anything. Avoid the tiny - // planetary/outpost event wrappers: these substantive methods own the work. - const auto* on_enable = method_contract::Resolve( - controller_helper.get_cls(), "OnEnable", false, "System.Void", {}); - const auto* achievements = method_contract::Resolve( - controller_helper.get_cls(), "SetupAchievementsButton", false, "System.Void", {}); - const auto* challenges = method_contract::Resolve( - controller_helper.get_cls(), "SetupChallengesButton", false, "System.Void", {"System.Boolean"}); - const auto* outposts = method_contract::Resolve( - controller_helper.get_cls(), "SetupOutpostsButton", false, "System.Void", {"System.Boolean"}); - const auto* combined = method_contract::Resolve( - controller_helper.get_cls(), "HandleOutpostsAndChallengesHUD", false, "System.Void", {}); - const std::array targets{on_enable, achievements, challenges, outposts, combined}; - const std::array names{"OnEnable()", "SetupAchievementsButton()", "SetupChallengesButton(Boolean)", - "SetupOutpostsButton(Boolean)", "HandleOutpostsAndChallengesHUD()"}; - for (std::size_t i = 0; i < targets.size(); ++i) { - if (!targets[i]) { - ErrorMsg::MissingMethod("MissionsHudViewController", names[i]); - return; - } - } - for (std::size_t i = 0; i < targets.size(); ++i) { - if (targets[i]->has_full_generic_sharing_signature) { - spdlog::error("MissionHudTweaks: MissionsHudViewController.{} uses unsupported generic sharing; overrides disabled", - names[i]); - return; - } - for (std::size_t j = 0; j < i; ++j) - if (targets[i]->methodPointer == targets[j]->methodPointer) { - spdlog::error("MissionHudTweaks: MissionsHudViewController.{} and {} share a native target; overrides disabled", - names[i], names[j]); - return; - } - } - if (get_game_object->has_full_generic_sharing_signature || set_active->has_full_generic_sharing_signature) { - spdlog::error("MissionHudTweaks: {} uses unsupported generic sharing; overrides disabled", - get_game_object->has_full_generic_sharing_signature ? "Component.get_gameObject()" - : "GameObject.SetActive(Boolean)"); - return; - } - const auto modes = ConfiguredButtonModes(); - if (!modes.empty()) - spdlog::info("MissionHudTweaks: applying {}", modes); - const bool enabled = SPUD_STATIC_DETOUR(on_enable->methodPointer, MissionsHudViewController_OnEnable_Hook); - const bool achievement_hook = SPUD_STATIC_DETOUR(achievements->methodPointer, MissionsHudViewController_SetupAchievementsButton_Hook); - const bool challenge_hook = SPUD_STATIC_DETOUR(challenges->methodPointer, MissionsHudViewController_SetupChallengesButton_Hook); - const bool outpost_hook = SPUD_STATIC_DETOUR(outposts->methodPointer, MissionsHudViewController_SetupOutpostsButton_Hook); - const bool combined_hook = SPUD_STATIC_DETOUR(combined->methodPointer, MissionsHudViewController_HandleOutpostsAndChallengesHUD_Hook); - const std::array installed{enabled, achievement_hook, challenge_hook, outpost_hook, combined_hook}; - for (std::size_t i = 0; i < installed.size(); ++i) - if (!installed[i]) - spdlog::error("MissionHudTweaks: failed to install MissionsHudViewController.{}; overrides disabled", names[i]); - g_ready = enabled && achievement_hook && challenge_hook && outpost_hook && combined_hook; - if (g_ready) { - spdlog::info("MissionHudTweaks: installed current HUD lifecycle/setup hooks"); - } else { - // Successfully installed detours still call through, but must not partially - // enforce preferences when another refresh path could undo them. - g_configured_buttons.clear(); - spdlog::error("MissionHudTweaks: hook installation incomplete; overrides disabled"); + const auto configured_modes = ConfiguredButtonModes(); + if (!configured_modes.empty()) { + spdlog::info("MissionHudTweaks: applying {}", configured_modes); } + SPUD_STATIC_DETOUR(update_buttons.Pointer(), MissionsHudViewController_UpdateButtons_Hook); + spdlog::info("MissionHudTweaks: installed MissionsHudViewController.UpdateButtons hook"); } diff --git a/mods/src/patches/parts/object_tracker.cc b/mods/src/patches/parts/object_tracker.cc index a50138649..964f1fd59 100644 --- a/mods/src/patches/parts/object_tracker.cc +++ b/mods/src/patches/parts/object_tracker.cc @@ -1,5 +1,6 @@ -#include -#include +#include "patches/object_tracker.h" + +#include #include "prime/AllianceStarbaseObjectViewerWidget.h" #include "prime/AnimatedRewardsScreenViewController.h" @@ -155,26 +156,6 @@ void track_free(auto original, void* _this) #undef GET_CLASS } -void artifact_details_bind_hook(auto original, Il2CppObject* _this) -{ - original(_this); - if (!_this || !_this->klass) - return; - - std::scoped_lock lk{tracked_objects_mutex}; - remove_from_tracking_all(_this); - add_to_tracking_recursive(_this->klass, _this); -} - -void artifact_details_release_hook(auto original, Il2CppObject* _this) -{ - if (_this) { - std::scoped_lock lk{tracked_objects_mutex}; - remove_from_tracking_all(_this); - } - original(_this); -} - void calc_liveness_hook(auto original, void* state) { original(state); @@ -205,38 +186,19 @@ void calc_liveness_hook(auto original, void* state) static eastl::unordered_set seen_ctor; static eastl::unordered_set seen_destroy; +// OnDestroy (and sometimes .ctor) resolves to a shared base implementation; detour each entry point once. template void TrackObject() { - auto& object_class = T::get_class_helper(); - auto ctor = object_class.GetMethod(".ctor"); - auto on_destroy = object_class.GetMethod("OnDestroy"); - if (seen_ctor.find(ctor) == seen_ctor.end()) { - SPUD_STATIC_DETOUR(ctor, track_ctor); - seen_ctor.emplace(ctor); - } - - if (seen_destroy.find(on_destroy) == seen_destroy.end()) { - SPUD_STATIC_DETOUR(on_destroy, track_destroy); - seen_destroy.emplace(on_destroy); + if (auto ctor = T::Class::Method(".ctor"); ctor && seen_ctor.find(ctor.Pointer()) == seen_ctor.end()) { + SPUD_STATIC_DETOUR(ctor.Pointer(), track_ctor); + seen_ctor.emplace(ctor.Pointer()); } -} -void TrackArtifactDetailsCanvas() -{ - auto* cls = ArtifactHallDetailsViewController::get_class_helper().get_cls(); - const auto* bind = method_contract::Resolve(cls, "OnDidBindCanvasContext", false, "System.Void", {}); - const auto* release = method_contract::Resolve(cls, "OnAboutToReleaseCanvasContext", false, "System.Void", {}); - if (!bind || !release || bind->has_full_generic_sharing_signature || release->has_full_generic_sharing_signature) { - spdlog::warn("Object tracker: artifact details canvas lifecycle unavailable; artifact arrow navigation disabled"); - return; + if (auto on_destroy = T::Class::Method("OnDestroy"); + on_destroy && seen_destroy.find(on_destroy.Pointer()) == seen_destroy.end()) { + SPUD_STATIC_DETOUR(on_destroy.Pointer(), track_destroy); + seen_destroy.emplace(on_destroy.Pointer()); } - - const bool bind_installed = SPUD_STATIC_DETOUR(bind->methodPointer, artifact_details_bind_hook); - const bool release_installed = SPUD_STATIC_DETOUR(release->methodPointer, artifact_details_release_hook); - if (bind_installed && release_installed) - spdlog::info("Object tracker: tracking artifact details through canvas lifecycle"); - else - spdlog::warn("Object tracker: artifact details canvas hooks were not installed; artifact arrow navigation disabled"); } void InstallObjectTrackers() @@ -247,7 +209,7 @@ void InstallObjectTrackers() TrackObject(); TrackObject(); TrackObject(); - TrackArtifactDetailsCanvas(); + TrackObject(); TrackObject(); TrackObject(); TrackObject(); diff --git a/mods/src/patches/parts/officer_sort.cc b/mods/src/patches/parts/officer_sort.cc index 8be779dc7..bdaf35826 100644 --- a/mods/src/patches/parts/officer_sort.cc +++ b/mods/src/patches/parts/officer_sort.cc @@ -1,13 +1,12 @@ #include "config.h" -#include "errormsg.h" +#include "str_utils.h" -#include -#include +#include #include #include -#include +#include // Restores the "Below Deck Ability" (BDA) sort option removed from the game UI // in m93.1. Hooks OfficerSortGenerators.InitializeOfficerSorters / @@ -19,40 +18,62 @@ namespace { constexpr char kBelowDeckKey[] = "below_deck_ability"; constexpr char kBelowDeckAssignmentKey[] = "_below_deck_ability"; -using SortMethodPtr = int (*)(void* obj1, void* obj2, void* delegatePtr); +using SortingPredicates = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Core", "SortingPredicates">; + +struct SortFunction : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.Client.Core", "SortFunction">; -struct OfficerSortState { - IL2CppClassHelper* sortingPredicates = nullptr; - IL2CppClassHelper* sortFunction = nullptr; - IL2CppClassHelper* processingOption = nullptr; - IL2CppClassHelper* officerSortGenerators = nullptr; - - const MethodInfo* ascendingMethod = nullptr; - const MethodInfo* descendingMethod = nullptr; - - SortMethodPtr unlockedStatePtr = nullptr; - SortMethodPtr bdaAscPtr = nullptr; - SortMethodPtr bdaDescPtr = nullptr; - SortMethodPtr indexAscPtr = nullptr; - - IL2CppFieldHelper* sortFunctionDisplayKey = nullptr; - IL2CppFieldHelper* sortFunctionAscending = nullptr; - IL2CppFieldHelper* sortFunctionDescending = nullptr; - IL2CppFieldHelper* rosterSortersField = nullptr; - IL2CppFieldHelper* rosterOptionsField = nullptr; - IL2CppFieldHelper* assignmentOptionsField = nullptr; - IL2CppFieldHelper* processingOptionDisplayKey = nullptr; - IL2CppFieldHelper* processingOptionSortingOption = nullptr; - - Il2CppClass* funcClass = nullptr; - bool valid = false; + // Func delegates. + il2cpp::Object* ascending() + { return Field<"_ascending", il2cpp::Object*>(); } + void SetAscending(Il2CppObject* value) + { SetField<"_ascending">(value); } + void SetDescending(Il2CppObject* value) + { SetField<"_descending">(value); } + void SetDisplayKey(Il2CppString* value) + { SetField<"_displayKey">(value); } }; -OfficerSortState& State() -{ - static OfficerSortState s; - return s; -} +struct ProcessingOption : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Sorting", "SortComparer/ProcessingOption">; + + Il2CppString* displayKey() + { return Field<"_displayKey", Il2CppString*>(); } + void SetDisplayKey(Il2CppString* value) + { SetField<"_displayKey">(value); } + SortFunction* sortingOption() + { return Field<"_sortingOption", SortFunction*>(); } + void SetSortingOption(SortFunction* value) + { SetField<"_sortingOption">(value); } +}; + +struct OfficerSortGenerators : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Officers", "OfficerSortGenerators">; + + il2cpp::List* rosterSorters() + { return Field<"_rosterSorters", il2cpp::List*>(); } + il2cpp::List* rosterOptions() + { return Field<"_rosterOptions", il2cpp::List*>(); } + il2cpp::List* assignmentOptions() + { return Field<"_assignmentOptions", il2cpp::List*>(); } +}; + +// Native code of the static SortingPredicates comparers. The delegates below are bound to the BDA +// predicates but jump to the wrappers, which chain these directly. +using SortMethodPtr = int (*)(void* obj1, void* obj2, void* delegatePtr); + +il2cpp::Method g_bdaAscMethod; +il2cpp::Method g_bdaDescMethod; +SortMethodPtr g_unlockedStatePtr = nullptr; +SortMethodPtr g_bdaAscPtr = nullptr; +SortMethodPtr g_bdaDescPtr = nullptr; +SortMethodPtr g_indexAscPtr = nullptr; +// Func, discovered from the game's own options; there is no binding for the +// inflated generic class. +Il2CppClass* g_funcClass = nullptr; + +SortMethodPtr PointerOf(il2cpp::Method method) +{ return method ? reinterpret_cast(method.Info()->methodPointer) : nullptr; } // SortComparer.Compare always calls the _ascending delegate; for Descending // direction it negates the result. The _descending delegate is never called. @@ -62,338 +83,131 @@ OfficerSortState& State() int RosterAscendingWrapper(void* obj1, void* obj2, void* /*delegatePtr*/) { - auto& s = State(); - if (s.unlockedStatePtr) { - int r = s.unlockedStatePtr(obj1, obj2, nullptr); + if (g_unlockedStatePtr) { + int r = g_unlockedStatePtr(obj1, obj2, nullptr); if (r != 0) return r; } - if (s.bdaDescPtr) { - int r = s.bdaDescPtr(obj1, obj2, nullptr); + if (g_bdaDescPtr) { + int r = g_bdaDescPtr(obj1, obj2, nullptr); if (r != 0) return r; } - return s.indexAscPtr ? -s.indexAscPtr(obj1, obj2, nullptr) : 0; + return g_indexAscPtr ? -g_indexAscPtr(obj1, obj2, nullptr) : 0; } int RosterDescendingWrapper(void* obj1, void* obj2, void* /*delegatePtr*/) { - auto& s = State(); - if (s.unlockedStatePtr) { - int r = s.unlockedStatePtr(obj1, obj2, nullptr); + if (g_unlockedStatePtr) { + int r = g_unlockedStatePtr(obj1, obj2, nullptr); if (r != 0) return r; } - if (s.bdaAscPtr) { - int r = s.bdaAscPtr(obj1, obj2, nullptr); + if (g_bdaAscPtr) { + int r = g_bdaAscPtr(obj1, obj2, nullptr); if (r != 0) return r; } - return s.indexAscPtr ? -s.indexAscPtr(obj1, obj2, nullptr) : 0; + return g_indexAscPtr ? -g_indexAscPtr(obj1, obj2, nullptr) : 0; } int AssignmentAscendingWrapper(void* obj1, void* obj2, void* /*delegatePtr*/) { - auto& s = State(); - if (s.unlockedStatePtr) { - int r = s.unlockedStatePtr(obj1, obj2, nullptr); + if (g_unlockedStatePtr) { + int r = g_unlockedStatePtr(obj1, obj2, nullptr); if (r != 0) return r; } - if (s.bdaAscPtr) { - int r = s.bdaAscPtr(obj1, obj2, nullptr); + if (g_bdaAscPtr) { + int r = g_bdaAscPtr(obj1, obj2, nullptr); if (r != 0) return r; } - return s.indexAscPtr ? s.indexAscPtr(obj1, obj2, nullptr) : 0; + return g_indexAscPtr ? g_indexAscPtr(obj1, obj2, nullptr) : 0; } int AssignmentDescendingWrapper(void* obj1, void* obj2, void* /*delegatePtr*/) { - auto& s = State(); - if (s.unlockedStatePtr) { - int r = s.unlockedStatePtr(obj1, obj2, nullptr); + if (g_unlockedStatePtr) { + int r = g_unlockedStatePtr(obj1, obj2, nullptr); if (r != 0) return r; } - if (s.bdaDescPtr) { - int r = s.bdaDescPtr(obj1, obj2, nullptr); + if (g_bdaDescPtr) { + int r = g_bdaDescPtr(obj1, obj2, nullptr); if (r != 0) return r; } - return s.indexAscPtr ? s.indexAscPtr(obj1, obj2, nullptr) : 0; + return g_indexAscPtr ? g_indexAscPtr(obj1, obj2, nullptr) : 0; } -bool InitializeState() +bool InitializePredicates() { - auto& s = State(); - - s.sortingPredicates = new IL2CppClassHelper( - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Core", "SortingPredicates")); - if (!s.sortingPredicates->get_cls()) { - ErrorMsg::MissingHelper("Digit.Client.Core", "SortingPredicates"); - return false; - } - - s.sortFunction = new IL2CppClassHelper( - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.Client.Core", "SortFunction")); - if (!s.sortFunction->get_cls()) { - ErrorMsg::MissingHelper("Digit.Client.Core", "SortFunction"); - return false; - } - - auto sortComparer = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Sorting", "SortComparer"); - if (!sortComparer.get_cls()) { - ErrorMsg::MissingHelper("Digit.Client.Sorting", "SortComparer"); - return false; - } - - // GetNestedType() reads the nestedTypes array directly and crashes on this - // class; use the safe iterator API instead. - Il2CppClass* processingOptionCls = nullptr; - void* iter = nullptr; - while (auto* nested = il2cpp_class_get_nested_types(sortComparer.get_cls(), &iter)) { - if (nested->name && std::strcmp(nested->name, "ProcessingOption") == 0) { - processingOptionCls = nested; - break; - } - } - s.processingOption = new IL2CppClassHelper(processingOptionCls); - if (!s.processingOption->get_cls()) { - ErrorMsg::MissingHelper("Digit.Client.Sorting", "SortComparer.ProcessingOption"); + g_bdaAscMethod = SortingPredicates::Method("OfficerSortByBelowDeckAbilityAscending", 2); + g_bdaDescMethod = SortingPredicates::Method("OfficerSortByBelowDeckAbilityDescending", 2); + if (!g_bdaAscMethod || !g_bdaDescMethod) return false; - } - - s.officerSortGenerators = new IL2CppClassHelper( - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Officers", "OfficerSortGenerators")); - if (!s.officerSortGenerators->get_cls()) { - ErrorMsg::MissingHelper("Digit.Prime.Officers", "OfficerSortGenerators"); - return false; - } - - s.ascendingMethod = s.sortingPredicates->GetMethodInfo("OfficerSortByBelowDeckAbilityAscending", 2); - if (!s.ascendingMethod) { - ErrorMsg::MissingStaticMethod("SortingPredicates", "OfficerSortByBelowDeckAbilityAscending"); - return false; - } - s.descendingMethod = s.sortingPredicates->GetMethodInfo("OfficerSortByBelowDeckAbilityDescending", 2); - if (!s.descendingMethod) { - ErrorMsg::MissingStaticMethod("SortingPredicates", "OfficerSortByBelowDeckAbilityDescending"); - return false; - } - - const MethodInfo* unlockedStateMethod = - s.sortingPredicates->GetMethodInfo("OfficerSortByUnlockedStateAscending", 2); - if (!unlockedStateMethod) { - spdlog::warn("[OfficerSort] OfficerSortByUnlockedStateAscending not found; locked officers may not sort to bottom"); - } else { - s.unlockedStatePtr = reinterpret_cast(unlockedStateMethod->methodPointer); - } - - const MethodInfo* indexMethod = s.sortingPredicates->GetMethodInfo("OfficerSortByIndexAscending", 2); - if (!indexMethod) { - spdlog::warn("[OfficerSort] OfficerSortByIndexAscending not found; no index tiebreaker"); - } else { - s.indexAscPtr = reinterpret_cast(indexMethod->methodPointer); - } - - s.bdaAscPtr = reinterpret_cast(s.ascendingMethod->methodPointer); - s.bdaDescPtr = reinterpret_cast(s.descendingMethod->methodPointer); - - s.sortFunctionDisplayKey = new IL2CppFieldHelper(s.sortFunction->GetField("_displayKey")); - s.sortFunctionAscending = new IL2CppFieldHelper(s.sortFunction->GetField("_ascending")); - s.sortFunctionDescending = new IL2CppFieldHelper(s.sortFunction->GetField("_descending")); - if (!s.sortFunctionDisplayKey->isValidHelper() || !s.sortFunctionAscending->isValidHelper() || - !s.sortFunctionDescending->isValidHelper()) { - spdlog::error("[OfficerSort] SortFunction field layout changed"); - return false; - } - - s.rosterSortersField = new IL2CppFieldHelper(s.officerSortGenerators->GetField("_rosterSorters")); - s.rosterOptionsField = new IL2CppFieldHelper(s.officerSortGenerators->GetField("_rosterOptions")); - s.assignmentOptionsField = new IL2CppFieldHelper(s.officerSortGenerators->GetField("_assignmentOptions")); - if (!s.rosterSortersField->isValidHelper() || !s.rosterOptionsField->isValidHelper() || - !s.assignmentOptionsField->isValidHelper()) { - spdlog::error("[OfficerSort] OfficerSortGenerators field layout changed"); - return false; - } - - s.processingOptionDisplayKey = new IL2CppFieldHelper(s.processingOption->GetField("_displayKey")); - s.processingOptionSortingOption = new IL2CppFieldHelper(s.processingOption->GetField("_sortingOption")); - if (!s.processingOptionDisplayKey->isValidHelper() || !s.processingOptionSortingOption->isValidHelper()) { - spdlog::error("[OfficerSort] ProcessingOption field layout changed"); - return false; - } - - s.valid = true; - return true; -} - -Il2CppClass* DiscoverFuncClassFromList(void* existingList) -{ - if (!existingList) return nullptr; - - auto* listClass = il2cpp_object_get_class(reinterpret_cast(existingList)); - if (!listClass) return nullptr; - - auto* getItem = il2cpp_class_get_method_from_name(listClass, "get_Item", 1); - if (!getItem) return nullptr; - - Il2CppException* exc = nullptr; - int32_t index = 0; - void* idxArgs[1] = {&index}; - auto* firstOption = il2cpp_runtime_invoke(getItem, existingList, idxArgs, &exc); - if (exc || !firstOption) return nullptr; + g_bdaAscPtr = PointerOf(g_bdaAscMethod); + g_bdaDescPtr = PointerOf(g_bdaDescMethod); - auto& s = State(); - auto sortingOptionField = s.processingOption->GetField("_sortingOption"); - if (!sortingOptionField.isValidHelper()) return nullptr; + // The tiebreakers are optional; the library has already reported a missing one. + g_unlockedStatePtr = PointerOf(SortingPredicates::Method("OfficerSortByUnlockedStateAscending", 2)); + g_indexAscPtr = PointerOf(SortingPredicates::Method("OfficerSortByIndexAscending", 2)); - auto* sortFn = *reinterpret_cast(reinterpret_cast(firstOption) + sortingOptionField.offset()); - if (!sortFn) return nullptr; - - auto* ascendingDelegate = - *reinterpret_cast(reinterpret_cast(sortFn) + s.sortFunctionAscending->offset()); - if (!ascendingDelegate) return nullptr; - - return il2cpp_object_get_class(ascendingDelegate); + // Options are allocated without a constructor below, so both classes must exist. + return SortFunction::Class::Raw() && ProcessingOption::Class::Raw(); } -// Called via invoker_method to bypass il2cpp_runtime_invoke, which fails for -// static methods ("Delegate to an instance method cannot have null 'this'"). -Il2CppObject* CreateSortDelegate(const MethodInfo* sortMethod, Il2CppGCHandle& outHandle) +Il2CppClass* DiscoverFuncClassFromList(il2cpp::List* list) { - auto& s = State(); - if (!s.funcClass || !sortMethod) return nullptr; - - auto* del = il2cpp_object_new(s.funcClass); - if (!del) { - spdlog::warn("[OfficerSort] il2cpp_object_new failed for sort delegate"); - return nullptr; - } - - outHandle = il2cpp_gchandle_new(del, false); - if (!outHandle) { - spdlog::warn("[OfficerSort] gchandle_new failed for sort delegate"); + if (list->Count() < 1) return nullptr; - } - - auto* ctor = il2cpp_class_get_method_from_name(s.funcClass, ".ctor", 2); - if (ctor && ctor->invoker_method) { - void* ctorArgs[2] = {nullptr, (void*)&sortMethod}; - ctor->invoker_method(ctor->methodPointer, ctor, del, ctorArgs, nullptr); - return del; - } - - spdlog::warn("[OfficerSort] no .ctor on Func class; cannot create delegate"); - return nullptr; + auto* firstOption = (*list)[0]; + auto* sortFn = firstOption ? firstOption->sortingOption() : nullptr; + auto* ascending = sortFn ? sortFn->ascending() : nullptr; + return ascending ? ascending->klass : nullptr; } // forRoster=true: roster defaults to Ascending, so _ascending produces BDA-first. // forRoster=false: assignment defaults to Descending, so _ascending produces // non-BDA-first (SortComparer negates it, yielding BDA-first). -Il2CppObject* CreateBelowDeckSortFunction(void* existingList, const char* displayKey, - bool forRoster, Il2CppGCHandle& outHandle) +SortFunction* CreateBelowDeckSortFunction(const char* displayKey, bool forRoster) { - auto& s = State(); - - Il2CppGCHandle ascHandle = 0; - Il2CppGCHandle descHandle = 0; - auto* ascDelegate = CreateSortDelegate(s.ascendingMethod, ascHandle); - auto* descDelegate = CreateSortDelegate(s.descendingMethod, descHandle); - if (!ascDelegate || !descDelegate) { - if (ascHandle) il2cpp_gchandle_free(ascHandle); - if (descHandle) il2cpp_gchandle_free(descHandle); - spdlog::warn("[OfficerSort] failed to create below-deck sort delegates"); - return nullptr; - } - - auto* ascDel = reinterpret_cast(ascDelegate); - auto* descDel = reinterpret_cast(descDelegate); - if (forRoster) { - ascDel->method_ptr = reinterpret_cast(&RosterAscendingWrapper); - descDel->method_ptr = reinterpret_cast(&RosterDescendingWrapper); - } else { - ascDel->method_ptr = reinterpret_cast(&AssignmentAscendingWrapper); - descDel->method_ptr = reinterpret_cast(&AssignmentDescendingWrapper); - } - - auto* sortFn = il2cpp_object_new(s.sortFunction->get_cls()); - if (!sortFn) { - il2cpp_gchandle_free(ascHandle); - il2cpp_gchandle_free(descHandle); - spdlog::warn("[OfficerSort] il2cpp_object_new failed for SortFunction"); - return nullptr; - } - - outHandle = il2cpp_gchandle_new(sortFn, false); - if (!outHandle) { - il2cpp_gchandle_free(ascHandle); - il2cpp_gchandle_free(descHandle); + auto* ascending = il2cpp::NewDelegate( + g_funcClass, g_bdaAscMethod, + reinterpret_cast(forRoster ? &RosterAscendingWrapper : &AssignmentAscendingWrapper)); + auto* descending = il2cpp::NewDelegate( + g_funcClass, g_bdaDescMethod, + reinterpret_cast(forRoster ? &RosterDescendingWrapper : &AssignmentDescendingWrapper)); + if (!ascending || !descending) return nullptr; - } - - auto* displayKeyStr = il2cpp_string_new(displayKey); - *reinterpret_cast(reinterpret_cast(sortFn) + s.sortFunctionDisplayKey->offset()) = displayKeyStr; - *reinterpret_cast(reinterpret_cast(sortFn) + s.sortFunctionAscending->offset()) = ascDelegate; - *reinterpret_cast(reinterpret_cast(sortFn) + s.sortFunctionDescending->offset()) = descDelegate; - if (ascHandle) il2cpp_gchandle_free(ascHandle); - if (descHandle) il2cpp_gchandle_free(descHandle); - - return sortFn; + // SortFunction's parameterless constructor has no generated code; set every field Compare reads. + auto* sort = static_cast(il2cpp_object_new(SortFunction::Class::Raw())); + sort->SetDisplayKey(il2cpp_string_new(displayKey)); + sort->SetAscending(ascending); + sort->SetDescending(descending); + return sort; } -bool ListAlreadyHasBelowDeck(void* list) +bool ListAlreadyHasBelowDeck(il2cpp::List* list) { - if (!list) return false; - auto* listClass = il2cpp_object_get_class(reinterpret_cast(list)); - if (!listClass) return false; - - auto* getCount = il2cpp_class_get_method_from_name(listClass, "get_Count", 0); - auto* getItem = il2cpp_class_get_method_from_name(listClass, "get_Item", 1); - if (!getCount || !getItem) return false; - - Il2CppException* exc = nullptr; - auto* countObj = il2cpp_runtime_invoke(getCount, list, nullptr, &exc); - if (exc || !countObj) return false; - auto count = *reinterpret_cast(il2cpp_object_unbox(countObj)); - - auto& s = State(); - auto displayKeyField = s.processingOption->GetField("_displayKey"); - if (!displayKeyField.isValidHelper()) return false; - - for (int32_t i = 0; i < count; ++i) { - void* idxArgs[1] = {&i}; - exc = nullptr; - auto* option = il2cpp_runtime_invoke(getItem, list, idxArgs, &exc); - if (exc || !option) continue; - - auto* keyPtr = - *reinterpret_cast(reinterpret_cast(option) + displayKeyField.offset()); - if (!keyPtr) continue; - - auto len = keyPtr->length; - auto matchesKey = [&](const char* key) { - auto keyLen = (int32_t)strlen(key); - if (len != keyLen) return false; - for (int32_t j = 0; j < len; ++j) { - if ((char)keyPtr->chars[j] != key[j]) return false; - } + for (auto* option : *list) { + auto* key = option ? option->displayKey() : nullptr; + if (!key) + continue; + const auto text = to_string(key); + if (text == kBelowDeckKey || text == kBelowDeckAssignmentKey) return true; - }; - if (matchesKey(kBelowDeckKey) || matchesKey(kBelowDeckAssignmentKey)) return true; } return false; } // If sortersList is non-null, also adds the raw SortFunction to it (the roster // screen builds its SortComparer from _rosterSorters, not _rosterOptions). -void AppendBelowDeckOption(void* list, const char* displayKey, bool forRoster, void* sortersList = nullptr) +void AppendBelowDeckOption(il2cpp::List* list, const char* displayKey, bool forRoster, + il2cpp::List* sortersList = nullptr) { if (!list) { spdlog::warn("[OfficerSort] AppendBelowDeckOption: list is null"); return; } - auto& s = State(); - if (!s.valid) return; - - if (!s.funcClass) { - s.funcClass = DiscoverFuncClassFromList(list); - if (!s.funcClass) { + if (!g_funcClass) { + g_funcClass = DiscoverFuncClassFromList(list); + if (!g_funcClass) { spdlog::warn("[OfficerSort] could not discover Func class; skipping below-deck option"); return; } @@ -401,102 +215,48 @@ void AppendBelowDeckOption(void* list, const char* displayKey, bool forRoster, v if (ListAlreadyHasBelowDeck(list)) return; - Il2CppGCHandle sortFnHandle = 0; - auto* sortFn = CreateBelowDeckSortFunction(list, displayKey, forRoster, sortFnHandle); + auto* sortFn = CreateBelowDeckSortFunction(displayKey, forRoster); if (!sortFn) { - if (sortFnHandle) il2cpp_gchandle_free(sortFnHandle); - return; - } - - if (sortersList) { - auto* sortersListClass = il2cpp_object_get_class(reinterpret_cast(sortersList)); - auto* sortersAddMethod = sortersListClass ? il2cpp_class_get_method_from_name(sortersListClass, "Add", 1) : nullptr; - if (sortersAddMethod) { - void* sortersAddArgs[1] = {sortFn}; - Il2CppException* sortersExc = nullptr; - il2cpp_runtime_invoke(sortersAddMethod, sortersList, sortersAddArgs, &sortersExc); - if (sortersExc) spdlog::warn("[OfficerSort] sorters list.Add raised an exception"); - } - } - - auto* option = il2cpp_object_new(s.processingOption->get_cls()); - if (!option) { - spdlog::warn("[OfficerSort] il2cpp_object_new failed for ProcessingOption"); - il2cpp_gchandle_free(sortFnHandle); - return; - } - - Il2CppGCHandle optionHandle = il2cpp_gchandle_new(option, false); - if (!optionHandle) { - il2cpp_gchandle_free(sortFnHandle); - return; - } - - auto* idStr = il2cpp_string_new(displayKey); - *reinterpret_cast(reinterpret_cast(option) + s.processingOptionDisplayKey->offset()) = idStr; - *reinterpret_cast(reinterpret_cast(option) + s.processingOptionSortingOption->offset()) = sortFn; - - auto* listClass = il2cpp_object_get_class(reinterpret_cast(list)); - auto* addMethod = listClass ? il2cpp_class_get_method_from_name(listClass, "Add", 1) : nullptr; - if (!addMethod) { - spdlog::warn("[OfficerSort] list has no Add method; cannot insert below-deck option"); - il2cpp_gchandle_free(optionHandle); - il2cpp_gchandle_free(sortFnHandle); + spdlog::warn("[OfficerSort] failed to create sort function"); return; } - void* addArgs[1] = {option}; - Il2CppException* exc = nullptr; - il2cpp_runtime_invoke(addMethod, list, addArgs, &exc); - if (exc) spdlog::warn("[OfficerSort] list.Add raised an exception; below-deck option not inserted"); + if (sortersList) + sortersList->Add(sortFn); - il2cpp_gchandle_free(optionHandle); - il2cpp_gchandle_free(sortFnHandle); + auto* option = static_cast(il2cpp_object_new(ProcessingOption::Class::Raw())); + option->SetDisplayKey(il2cpp_string_new(displayKey)); + option->SetSortingOption(sortFn); + list->Add(option); } -void InitializeOfficerSorters_Hook(auto original, void* _this) +void InitializeOfficerSorters_Hook(auto original, OfficerSortGenerators* _this) { original(_this); if (!Config::Get().officer_sort) return; - auto& s = State(); - auto* optionsList = *reinterpret_cast(reinterpret_cast(_this) + s.rosterOptionsField->offset()); - auto* sortersList = *reinterpret_cast(reinterpret_cast(_this) + s.rosterSortersField->offset()); - AppendBelowDeckOption(optionsList, kBelowDeckKey, true, sortersList); + AppendBelowDeckOption(_this->rosterOptions(), kBelowDeckKey, true, _this->rosterSorters()); } -void InitializeAssignmentSorters_Hook(auto original, void* _this) +void InitializeAssignmentSorters_Hook(auto original, OfficerSortGenerators* _this) { original(_this); if (!Config::Get().officer_sort) return; - auto& s = State(); - auto* list = *reinterpret_cast(reinterpret_cast(_this) + s.assignmentOptionsField->offset()); - AppendBelowDeckOption(list, kBelowDeckAssignmentKey, false); + AppendBelowDeckOption(_this->assignmentOptions(), kBelowDeckAssignmentKey, false); } } // namespace void InstallOfficerSortHooks() { - if (!InitializeState()) { - spdlog::error("[OfficerSort] initialization failed; hooks not installed"); + if (!InitializePredicates()) return; - } - - auto& s = State(); - - auto rosterInitPtr = s.officerSortGenerators->GetMethod("InitializeOfficerSorters", 0); - if (!rosterInitPtr) { - ErrorMsg::MissingMethod("OfficerSortGenerators", "InitializeOfficerSorters"); - return; - } - auto assignmentInitPtr = s.officerSortGenerators->GetMethod("InitializeAssignmentSorters", 0); - if (!assignmentInitPtr) { - ErrorMsg::MissingMethod("OfficerSortGenerators", "InitializeAssignmentSorters"); + auto roster = OfficerSortGenerators::Class::Method("InitializeOfficerSorters", 0); + auto assignment = OfficerSortGenerators::Class::Method("InitializeAssignmentSorters", 0); + if (!roster || !assignment) return; - } - SPUD_STATIC_DETOUR(rosterInitPtr, InitializeOfficerSorters_Hook); - SPUD_STATIC_DETOUR(assignmentInitPtr, InitializeAssignmentSorters_Hook); + SPUD_STATIC_DETOUR(roster.Pointer(), InitializeOfficerSorters_Hook); + SPUD_STATIC_DETOUR(assignment.Pointer(), InitializeAssignmentSorters_Hook); spdlog::info("Officer sort: restored Below Deck Ability sort option"); } diff --git a/mods/src/patches/parts/open_bulk_claim_gifts.cc b/mods/src/patches/parts/open_bulk_claim_gifts.cc index 01aef6de1..f4f90bbf3 100644 --- a/mods/src/patches/parts/open_bulk_claim_gifts.cc +++ b/mods/src/patches/parts/open_bulk_claim_gifts.cc @@ -1,84 +1,31 @@ #include "config.h" -#include "errormsg.h" -#include -#include -#include +#include -#include +#include +#include namespace { -class DrawerContext -{ -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Prime.SharedFeatures.Scripts.UI.Widgets", "DrawerContext"); - return class_helper; - } - -public: - static IL2CppClassHelper& ClassHelper() - { return get_class_helper(); } +struct DrawerContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Prime.SharedFeatures.Scripts.UI.Widgets", "DrawerContext">; bool Enabled() - { - static auto field = get_class_helper().GetField("Enabled"); - return field.isValidHelper() && *(bool*)((ptrdiff_t)this + field.offset()); - } + { return Field<"Enabled", bool>(); } }; -class DrawerWidget -{ -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Prime.SharedFeatures.Scripts.UI.Widgets", "DrawerWidget"); - return class_helper; - } - -public: - static IL2CppClassHelper& ClassHelper() - { return get_class_helper(); } - - DrawerContext* Context() - { - static auto widget_base = get_class_helper().GetParent("Widget`1"); - static auto field = widget_base.GetProperty("Context"); - return field.GetRaw(this); - } +struct DrawerWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Prime.SharedFeatures.Scripts.UI.Widgets", "DrawerWidget">; void OpenViaButtonCallback() - { - static auto method = get_class_helper().GetMethod("OnOpenButtonClicked"); - if (method != nullptr) { - method(this); - } - } + { Call<"OnOpenButtonClicked">(); } }; -class ShopListScrollerViewController -{ -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Shop", "ShopListScrollerViewController"); - return class_helper; - } - -public: - static IL2CppClassHelper& ClassHelper() - { return get_class_helper(); } +struct ShopListScrollerViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Shop", "ShopListScrollerViewController">; DrawerWidget* SelectionDrawer() - { - static auto field = get_class_helper().GetField("_selectionDrawerWidget"); - return field.isValidHelper() ? *(DrawerWidget**)((ptrdiff_t)this + field.offset()) : nullptr; - } + { return Field<"_selectionDrawerWidget", DrawerWidget*>(); } }; ShopListScrollerViewController* g_active_scroller = nullptr; @@ -132,69 +79,11 @@ void ShopListScrollerViewController_UpdateDrawer(auto original, ShopListScroller void InstallGiftsBulkClaimHooks() { - bool can_install = true; - - auto drawer_context = DrawerContext::ClassHelper(); - if (!drawer_context.isValidHelper()) { - ErrorMsg::MissingHelper("Prime.SharedFeatures.Scripts.UI.Widgets", "DrawerContext"); - can_install = false; - } else if (!drawer_context.GetField("Enabled").isValidHelper()) { - ErrorMsg::MissingMethod("DrawerContext", "Enabled"); - can_install = false; - } - - auto drawer_widget = DrawerWidget::ClassHelper(); - if (!drawer_widget.isValidHelper()) { - ErrorMsg::MissingHelper("Prime.SharedFeatures.Scripts.UI.Widgets", "DrawerWidget"); - can_install = false; - } else { - auto widget_base = drawer_widget.GetParent("Widget`1"); - if (!widget_base.isValidHelper() || !widget_base.GetProperty("Context").isValidHelper()) { - ErrorMsg::MissingMethod("DrawerWidget", "Widget.Context"); - can_install = false; - } - if (drawer_widget.GetMethod("OnOpenButtonClicked") == nullptr) { - ErrorMsg::MissingMethod("DrawerWidget", "OnOpenButtonClicked"); - can_install = false; - } - } - - auto shop_list_scroller = ShopListScrollerViewController::ClassHelper(); - if (!shop_list_scroller.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.Shop", "ShopListScrollerViewController"); - spdlog::warn("Skipping GiftsBulkClaimHooks: required IL2CPP dependencies are unavailable; no hooks installed."); - return; - } - - if (!shop_list_scroller.GetField("_selectionDrawerWidget").isValidHelper()) { - ErrorMsg::MissingMethod("ShopListScrollerViewController", "_selectionDrawerWidget"); - can_install = false; - } - - auto about_to_show = shop_list_scroller.GetMethod("AboutToShow"); - if (about_to_show == nullptr) { - ErrorMsg::MissingMethod("ShopListScrollerViewController", "AboutToShow"); - can_install = false; - } - - auto about_to_hide = shop_list_scroller.GetMethod("AboutToHide"); - if (about_to_hide == nullptr) { - ErrorMsg::MissingMethod("ShopListScrollerViewController", "AboutToHide"); - can_install = false; - } - - auto update_drawer = shop_list_scroller.GetMethod("UpdateDrawer"); - if (update_drawer == nullptr) { - ErrorMsg::MissingMethod("ShopListScrollerViewController", "UpdateDrawer"); - can_install = false; - } - - if (!can_install) { - spdlog::warn("Skipping GiftsBulkClaimHooks: required IL2CPP dependencies are unavailable; no hooks installed."); - return; - } - - SPUD_STATIC_DETOUR(about_to_show, ShopListScrollerViewController_AboutToShow); - SPUD_STATIC_DETOUR(about_to_hide, ShopListScrollerViewController_AboutToHide); - SPUD_STATIC_DETOUR(update_drawer, ShopListScrollerViewController_UpdateDrawer); + using Scroller = ShopListScrollerViewController::Class; + if (auto m = Scroller::Method("AboutToShow")) + SPUD_STATIC_DETOUR(m.Pointer(), ShopListScrollerViewController_AboutToShow); + if (auto m = Scroller::Method("AboutToHide")) + SPUD_STATIC_DETOUR(m.Pointer(), ShopListScrollerViewController_AboutToHide); + if (auto m = Scroller::Method("UpdateDrawer")) + SPUD_STATIC_DETOUR(m.Pointer(), ShopListScrollerViewController_UpdateDrawer); } diff --git a/mods/src/patches/parts/pinned_ship_click.cc b/mods/src/patches/parts/pinned_ship_click.cc index cdf52ba7d..f2d95cd0a 100644 --- a/mods/src/patches/parts/pinned_ship_click.cc +++ b/mods/src/patches/parts/pinned_ship_click.cc @@ -1,3 +1,4 @@ +#include "patches/object_tracker.h" #include "config.h" #include "file.h" #include "patches/key.h" @@ -11,7 +12,7 @@ #include "ship_tile_click.h" #include -#include +#include #include #include @@ -34,7 +35,7 @@ bool IsShiftHeld() bool IsShipManagementScreenVisible() { for (auto* controller : ObjectFinder::GetAll()) { - if (!controller || !controller->isActiveAndEnabled) continue; + if (!controller || !controller->isActiveAndEnabled()) continue; auto canvas = GetCanvasControllerFromComponent(controller); if (canvas && canvas->Visible()) return true; @@ -94,7 +95,7 @@ const MethodInfo* FindMethodInHierarchy(Il2CppClass* cls, const char* name, int void RefreshPinnedShipSortNow() { for (auto* controller : ObjectFinder::GetAll()) { - if (!controller || !controller->isActiveAndEnabled) continue; + if (!controller || !controller->isActiveAndEnabled()) continue; auto canvas = GetCanvasControllerFromComponent(controller); if (!canvas || !canvas->Visible()) continue; @@ -104,9 +105,9 @@ void RefreshPinnedShipSortNow() context->Sort(); for (auto* widget : ObjectFinder::GetAll()) { - if (!widget || !widget->isActiveAndEnabled) continue; + if (!widget || !widget->isActiveAndEnabled()) continue; - auto* list = widget->_selectableList; + auto* list = widget->selectableList(); if (!list) continue; auto* list_cls = il2cpp_object_get_class(reinterpret_cast(list)); diff --git a/mods/src/patches/parts/pinned_ship_sort.cc b/mods/src/patches/parts/pinned_ship_sort.cc index 92f7f90aa..0a930f53c 100644 --- a/mods/src/patches/parts/pinned_ship_sort.cc +++ b/mods/src/patches/parts/pinned_ship_sort.cc @@ -1,13 +1,10 @@ #include "config.h" -#include "errormsg.h" #include "ship_name_match.h" #include -#include +#include -#include -#include -#include +#include #include #include @@ -22,231 +19,47 @@ // everyone else. Only the concrete class is hooked, not ISortedFleetsContext, // so HailingFrequenciesInventoryContext is unaffected. Matching is shared // with the instant-warp filters (ship_name_match.h). If multiple ships share -// a pinned name, only the highest-level unclaimed one is pinned; among -// same-hull ships tied on level, the one with forbidden/chaos tech applied -// wins. +// a pinned name, only the highest-level unclaimed one is pinned. namespace { -// Forbidden tech / chaos tech slot spec ids, shared with the ship tech -// indicators patch (ship_tech_indicators.cc). A ship counts as "tech -// applied" when either slot holds an item. -constexpr int64_t kForbiddenTechSlotSpecId = 3502081615; -constexpr int64_t kChaosTechSlotSpecId = 953301906; - -struct PinnedShipSortState { - IL2CppClassHelper* shipManagementViewContext = nullptr; - IL2CppFieldHelper* sortedIdleShipsField = nullptr; - bool valid = false; -}; - -PinnedShipSortState& State() -{ - static PinnedShipSortState s; - return s; -} - -bool InitializeState() -{ - auto& s = State(); - - s.shipManagementViewContext = new IL2CppClassHelper( - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Ships", "ShipManagementViewContext")); - if (!s.shipManagementViewContext->get_cls()) { - ErrorMsg::MissingHelper("Digit.Prime.Ships", "ShipManagementViewContext"); - return false; - } - - s.sortedIdleShipsField = new IL2CppFieldHelper(s.shipManagementViewContext->GetField("SortedIdleShips")); - if (!s.sortedIdleShipsField->isValidHelper()) { - spdlog::error("[PinnedShipSort] ShipManagementViewContext field layout changed"); - return false; - } - - s.valid = true; - return true; -} - -// Bindings for asking ForbiddenTechManager whether a ship has forbidden or -// chaos tech equipped. Resolved once on first use; if anything is missing the -// tech tie-break quietly degrades to "no tech" (previous behaviour). -struct ShipTechQuery { - const MethodInfo* manager_instance = nullptr; - const MethodInfo* active_slots = nullptr; - const MethodInfo* slot_spec = nullptr; - const MethodInfo* slot_item = nullptr; - const MethodInfo* ship_id = nullptr; - bool valid = false; - bool resolved = false; -}; - -ShipTechQuery& TechQuery() -{ - static ShipTechQuery s; - return s; -} - -void ResolveShipTechQuery() -{ - auto& s = TechQuery(); - if (s.resolved) - return; - s.resolved = true; - - auto manager = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ForbiddenTechs", "ForbiddenTechManager"); - auto entity_slot = il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "EntitySlot"); - auto base_ship = il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "BaseShip"); - if (!manager.get_cls() || !entity_slot.get_cls() || !base_ship.get_cls()) { - spdlog::warn("[PinnedShipSort] forbidden tech bindings unavailable; tech tie-break disabled"); - return; - } - - s.manager_instance = manager.GetParent("MonoSingleton`1").GetMethodInfo("get_Instance", 0); - s.active_slots = manager.GetMethodInfo("TryGetActiveForbiddenTechSlot", 2); - s.slot_spec = method_contract::Resolve(entity_slot.get_cls(), "get_SlotSpecId", false, "System.Int64", {}); - s.slot_item = entity_slot.GetMethodInfo("get_SlotItemId", 0); - s.ship_id = method_contract::Resolve(base_ship.get_cls(), "get_Id", false, "System.Int64", {}); - - const bool manager_instance_valid = - s.manager_instance && s.manager_instance->methodPointer && (s.manager_instance->flags & METHOD_ATTRIBUTE_STATIC) - && s.manager_instance->parameters_count == 0 - && method_contract::Type(s.manager_instance->return_type, "Digit.Prime.ForbiddenTechs.ForbiddenTechManager"); - const bool active_slots_valid = - s.active_slots && s.active_slots->methodPointer && !(s.active_slots->flags & METHOD_ATTRIBUTE_STATIC) - && method_contract::Type(s.active_slots->return_type, "System.Boolean") && s.active_slots->parameters_count == 2 - && method_contract::Type(s.active_slots->parameters[0], "System.Int64") && s.active_slots->parameters[1]->byref; - const bool slot_spec_valid = s.slot_spec != nullptr; - const bool slot_item_valid = s.slot_item && s.slot_item->methodPointer - && !(s.slot_item->flags & METHOD_ATTRIBUTE_STATIC) && s.slot_item->parameters_count == 0 - && s.slot_item->return_type->type == IL2CPP_TYPE_GENERICINST; - const bool ship_id_valid = s.ship_id != nullptr; - - s.valid = manager_instance_valid && active_slots_valid && slot_spec_valid && slot_item_valid && ship_id_valid; - if (!s.valid) { - spdlog::warn("[PinnedShipSort] forbidden tech bindings failed validation; tech tie-break disabled"); - } -} - -// True when the ship has forbidden tech or chaos tech equipped in either of -// its tech slots. Best-effort: any failure counts as "no tech". -bool HasAppliedTech(FleetPlayerData* ship) -{ - ResolveShipTechQuery(); - auto& s = TechQuery(); - if (!ship || !s.valid) - return false; - - auto* ship_model = ship->__get_Ship(); - if (!ship_model) - return false; - - Il2CppException* exc = nullptr; - auto* boxed_id = il2cpp_runtime_invoke(s.ship_id, ship_model, nullptr, &exc); - if (exc || !boxed_id) - return false; - int64_t ship_id = *static_cast(il2cpp_object_unbox(boxed_id)); - - exc = nullptr; - auto* manager = il2cpp_runtime_invoke(s.manager_instance, nullptr, nullptr, &exc); - if (exc || !manager) - return false; - - Il2CppObject* slots = nullptr; - void* slot_args[2] = {&ship_id, &slots}; - exc = nullptr; - auto* found = il2cpp_runtime_invoke(s.active_slots, manager, slot_args, &exc); - if (exc || !found || !*static_cast(il2cpp_object_unbox(found)) || !slots) - return false; - - auto* slots_class = il2cpp_object_get_class(slots); - if (!slots_class) - return false; - auto* get_count = il2cpp_class_get_method_from_name(slots_class, "get_Count", 0); - auto* get_item = il2cpp_class_get_method_from_name(slots_class, "get_Item", 1); - if (!get_count || !get_item) - return false; - - exc = nullptr; - auto* count_obj = il2cpp_runtime_invoke(get_count, slots, nullptr, &exc); - if (exc || !count_obj) - return false; - const auto count = *static_cast(il2cpp_object_unbox(count_obj)); - - for (int32_t i = 0; i < count; ++i) { - exc = nullptr; - void* idx[1] = {&i}; - auto* slot = il2cpp_runtime_invoke(get_item, slots, idx, &exc); - if (exc || !slot) - continue; - - exc = nullptr; - auto* spec_obj = il2cpp_runtime_invoke(s.slot_spec, slot, nullptr, &exc); - if (exc || !spec_obj) - continue; - const auto spec_id = *static_cast(il2cpp_object_unbox(spec_obj)); - if (spec_id != kForbiddenTechSlotSpecId && spec_id != kChaosTechSlotSpecId) - continue; - - exc = nullptr; - auto* item = il2cpp_runtime_invoke(s.slot_item, slot, nullptr, &exc); - if (!exc && item) - return true; // non-null Nullable: a tech is equipped - } - return false; -} - struct ShipEntry { - void* item; + FleetPlayerData* item; std::vector match_words; // words from the game's own display name - int64_t level; - int64_t hull_id; - int8_t tech_applied; // -1 unknown, 0 no, 1 yes (resolved lazily) + int64_t level; + int64_t hull_id = 0; + int8_t tech_applied = -1; - // Memoized so the manager is only queried when a level tie actually happens. bool TechApplied() { - if (tech_applied < 0) { - tech_applied = HasAppliedTech(reinterpret_cast(item)) ? 1 : 0; - } + if (tech_applied < 0) + tech_applied = item && item->HasAppliedTech() ? 1 : 0; return tech_applied == 1; } }; -ShipEntry BuildShipEntry(void* item) +ShipEntry BuildShipEntry(FleetPlayerData* ship) { - auto* ship = reinterpret_cast(item); - if (!ship) - return ShipEntry{item, {}, 0, 0, -1}; + ShipEntry entry{ship, {}, 0}; + if (!ship) return entry; - return ShipEntry{item, ShipNameMatch::DisplayWords(ship), ship->Level, ship->HullId, -1}; + entry.level = ship->Level(); + entry.hull_id = ship->HullId(); + entry.match_words = ShipNameMatch::DisplayWords(ship); + return entry; } // Stable-partitions `list` (a List) so that ships matching // cfg.pinned_ships come first, while both the pinned group and everyone else // keep their existing relative order (i.e. whatever sort field is active). -void ReorderPinnedShips(void* list) +void ReorderPinnedShips(il2cpp::List* list) { if (!list) return; const auto& cfg = Config::Get(); if (cfg.pinned_ships.empty()) return; - auto* listClass = il2cpp_object_get_class(reinterpret_cast(list)); - if (!listClass) return; - - auto* getCount = il2cpp_class_get_method_from_name(listClass, "get_Count", 0); - auto* getItem = il2cpp_class_get_method_from_name(listClass, "get_Item", 1); - auto* clear = il2cpp_class_get_method_from_name(listClass, "Clear", 0); - auto* add = il2cpp_class_get_method_from_name(listClass, "Add", 1); - if (!getCount || !getItem || !clear || !add) { - spdlog::warn("[PinnedShipSort] SortedIdleShips list is missing expected List methods"); - return; - } - - Il2CppException* exc = nullptr; - auto* countObj = il2cpp_runtime_invoke(getCount, list, nullptr, &exc); - if (exc || !countObj) return; - const auto count = *reinterpret_cast(il2cpp_object_unbox(countObj)); + const auto count = list->Count(); if (count <= 1) return; if (count > 2000) { spdlog::warn("[PinnedShipSort] SortedIdleShips reported an implausible count ({}); skipping", count); @@ -261,15 +74,8 @@ void ReorderPinnedShips(void* list) std::vector ships; ships.reserve(count); - for (int32_t i = 0; i < count; ++i) { - void* idxArgs[1] = {&i}; - exc = nullptr; - auto* item = il2cpp_runtime_invoke(getItem, list, idxArgs, &exc); - if (exc) item = nullptr; - - auto entry = BuildShipEntry(item); - ships.push_back(std::move(entry)); - } + for (int32_t i = 0; i < count; ++i) + ships.push_back(BuildShipEntry((*list)[i])); constexpr int kPinnedRank = 0; constexpr int kUnpinnedRank = 1; @@ -289,7 +95,6 @@ void ReorderPinnedShips(void* list) best_index = i; } else if (best_index >= 0 && ships[i].level == best_level && ships[i].hull_id == ships[best_index].hull_id && ships[i].TechApplied() && !ships[best_index].TechApplied()) { - // Same-level, same-hull tie: prefer the ship with forbidden/chaos tech applied. best_index = i; } } @@ -306,7 +111,7 @@ void ReorderPinnedShips(void* list) if (!any_pinned) return; - std::vector items; + std::vector items; items.reserve(count); for (const auto& entry : ships) items.push_back(entry.item); @@ -323,51 +128,24 @@ void ReorderPinnedShips(void* list) } if (!changed) return; - exc = nullptr; - il2cpp_runtime_invoke(clear, list, nullptr, &exc); - if (exc) { - spdlog::warn("[PinnedShipSort] list.Clear raised an exception; aborting reorder"); - return; - } - - for (int32_t i = 0; i < count; ++i) { - void* addArgs[1] = {items[order[i]]}; - exc = nullptr; - il2cpp_runtime_invoke(add, list, addArgs, &exc); - if (exc) { - spdlog::warn("[PinnedShipSort] list.Add raised an exception while re-inserting an item"); - } - } + // SortedIdleShips is a List, which always has Clear and Add; the rewrite cannot stop halfway. + list->Call<"Clear">(); + for (int32_t i = 0; i < count; ++i) + list->Add(items[order[i]]); } -void ShipManagementViewContext_Sort_Hook(auto original, void* _this) +void ShipManagementViewContext_Sort_Hook(auto original, ShipManagementViewContext* _this) { original(_this); - - auto& s = State(); - if (!s.valid) return; - - auto* list = *reinterpret_cast(reinterpret_cast(_this) + s.sortedIdleShipsField->offset()); - ReorderPinnedShips(list); + ReorderPinnedShips(_this->SortedIdleShips()); } } // namespace void InstallPinnedShipSortHooks() { - if (!InitializeState()) { - spdlog::error("[PinnedShipSort] initialization failed; hooks not installed"); - return; - } - - auto& s = State(); - - auto sortMethod = s.shipManagementViewContext->GetMethod("Sort", 0); - if (!sortMethod) { - ErrorMsg::MissingMethod("ShipManagementViewContext", "Sort"); - return; + if (auto m = ShipManagementViewContext::Class::Method("Sort", 0)) { + SPUD_STATIC_DETOUR(m.Pointer(), ShipManagementViewContext_Sort_Hook); + spdlog::info("Pinned ship sort: hooks installed"); } - - SPUD_STATIC_DETOUR(sortMethod, ShipManagementViewContext_Sort_Hook); - spdlog::info("Pinned ship sort: hooks installed"); } diff --git a/mods/src/patches/parts/reveal_confirmation.cc b/mods/src/patches/parts/reveal_confirmation.cc index 3ac138ac1..75a8dfdbd 100644 --- a/mods/src/patches/parts/reveal_confirmation.cc +++ b/mods/src/patches/parts/reveal_confirmation.cc @@ -59,7 +59,7 @@ void InstallRevealConfirmationHooks() { auto message_utility_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "MessageUtility"); if (!message_utility_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Client.UI", "MessageUtility"); + spdlog::warn("Missing class {}.{}", "Digit.Client.UI", "MessageUtility"); return; } @@ -67,7 +67,7 @@ void InstallRevealConfirmationHooks() method_contract::Resolve(message_utility_helper.get_cls(), "ShowFreeAbilityConfirmationPopup", true, "System.Void", {"System.Int64", "System.TimeSpan", "System.Action"}); if (!show || !show->methodPointer || show->has_full_generic_sharing_signature) { - ErrorMsg::MissingMethod("MessageUtility", "ShowFreeAbilityConfirmationPopup"); + spdlog::warn("Missing method {}.{}", "MessageUtility", "ShowFreeAbilityConfirmationPopup"); return; } diff --git a/mods/src/patches/parts/ship_tile_click.cc b/mods/src/patches/parts/ship_tile_click.cc index 4046f54cb..21904d555 100644 --- a/mods/src/patches/parts/ship_tile_click.cc +++ b/mods/src/patches/parts/ship_tile_click.cc @@ -7,7 +7,7 @@ #include "pinned_ship_click.h" #include "prime/ShipTileWidget.h" -#include +#include #include #include @@ -21,7 +21,7 @@ void ShipTileWidget_HandleOnClick_Hook(auto original, ShipTileWidget* _this) auto& cfg = Config::Get(); if (!cfg.double_click_to_assign_ship && !cfg.installPinnedShipClickToggle) return; - auto* ship = _this ? _this->Context : nullptr; + auto* ship = _this ? _this->Context() : nullptr; if (!ship) return; PinShipClickOnTileClicked(ship); @@ -39,19 +39,12 @@ bool TryInstallHook() return true; } - auto helper = ShipTileWidget::get_class_helper(); - if (!helper.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.Ships", "ShipTileWidget"); - return false; - } - - auto method = helper.GetMethod("HandleOnClick", 0); + auto method = ShipTileWidget::Class::Method("HandleOnClick", 0); if (!method) { - ErrorMsg::MissingMethod("ShipTileWidget", "HandleOnClick"); + spdlog::warn("Missing method {}.{}", "ShipTileWidget", "HandleOnClick"); return false; } - - installed = SPUD_STATIC_DETOUR(method, ShipTileWidget_HandleOnClick_Hook); + installed = SPUD_STATIC_DETOUR(method.Pointer(), ShipTileWidget_HandleOnClick_Hook); if (installed) { spdlog::info("Ship tile click dispatcher: hooks installed"); } diff --git a/mods/src/patches/parts/sync.cc b/mods/src/patches/parts/sync.cc index c1e99a3e5..562036e7b 100644 --- a/mods/src/patches/parts/sync.cc +++ b/mods/src/patches/parts/sync.cc @@ -5,14 +5,13 @@ #include "patches/screen_update_hook.h" #include "str_utils.h" -#include #include #include #include -#include -#include +#include #include #include +#include #include #include #include @@ -43,7 +42,6 @@ #include #include #include -#include #include #include #include @@ -1602,14 +1600,7 @@ static UserProfile* local_user_profile() return nullptr; } - static auto fn = UserProfileManager::get_class_helper().GetMethodInfo("GetLocalUserProfile"); - if (fn == nullptr) { - return nullptr; - } - - Il2CppException* exception = nullptr; - auto* const profile = reinterpret_cast(il2cpp_runtime_invoke(fn, manager, nullptr, &exception)); - return exception == nullptr ? profile : nullptr; + return manager->GetLocalUserProfile(); } static void planetary_map(std::unique_ptr&& bytes) @@ -1619,19 +1610,19 @@ static void planetary_map(std::unique_ptr&& bytes) const auto& owner = response.owneruserid(); if (!owner.empty()) { - auto localProfile = local_user_profile(); + auto* localProfile = local_user_profile(); if (localProfile == nullptr) { spdlog::error("Failed to get local user profile"); return; } - const auto userId = localProfile->UserId; + const auto userId = localProfile->UserId(); if (userId == nullptr) { spdlog::error("Local user profile has empty UserId"); return; } - if (owner != userId) { + if (owner != to_string(userId)) { spdlog::debug("Planetary map data is not for local user, skipping processing"); return; } @@ -1650,7 +1641,7 @@ static std::string local_user_id(UserProfile* profile) return {}; } - const auto* const user_id = profile->UserId; + auto* const user_id = profile->UserId(); return user_id == nullptr ? std::string{} : to_string(user_id); } @@ -2868,13 +2859,16 @@ namespace rtc static void HandleEntityGroup(EntityGroup* entity_group) { - if (entity_group == nullptr || entity_group->Group == nullptr || entity_group->Group->bytes == nullptr - || entity_group->Group->Length <= 0) { + if (entity_group == nullptr) { + return; + } + auto* const group = entity_group->Group(); + if (group == nullptr || group->bytes() == nullptr || group->Length() <= 0) { return; } - const auto byteCount = static_cast(entity_group->Group->Length); - const auto *bytesPtr = reinterpret_cast(entity_group->Group->bytes->m_Items); + const auto byteCount = static_cast(group->Length()); + const auto *bytesPtr = reinterpret_cast(group->bytes()->data()); // Helper to run processing asynchronously with exception handling auto submit_async = [bytesPtr, byteCount](T&& func) { @@ -2899,7 +2893,7 @@ static void HandleEntityGroup(EntityGroup* entity_group) const auto& sync_options = Config::Get().sync_options; - switch (entity_group->Type_) { + switch (entity_group->Type_()) { // away assignments case EntityGroup::Type::AwayAssignmentsList: if (sync_options.away_assignments) { @@ -2911,7 +2905,6 @@ static void HandleEntityGroup(EntityGroup* entity_group) submit_async(processors::away_assignment_instance); } break; - // battlelogs case EntityGroup::Type::BattleResultHeaders: if (sync_options.battlelogs) { @@ -2931,7 +2924,7 @@ static void HandleEntityGroup(EntityGroup* entity_group) // Skipped if the local profile is not ready; a later response retries. auto* const local = processors::local_user_profile(); if (auto self = processors::local_user_id(local); !self.empty()) { - submit_async([self = std::move(self), level = local->Level](std::unique_ptr&& bytes) { + submit_async([self = std::move(self), level = local->Level()](std::unique_ptr&& bytes) { processors::own_profile(std::move(bytes), self, level); }); } @@ -3100,70 +3093,26 @@ static void HandleEntityGroup(EntityGroup* entity_group) } // Platform responses (store offers among them) reach the same ProcessResultInternal detour as game server responses: -// both model registries share one compiled method. Their entity group types use the Platform enum, so they must not +// both model registries share one compiled method. Their entity groups use the Platform type enum, so they must not // go through HandleEntityGroup. -static bool IsPlatformResponse(ServiceResponse* service_response) -{ - static const Il2CppClass* platform_class = nullptr; - - const auto* const response = reinterpret_cast(service_response); - if (response == nullptr || response->klass == nullptr) { - return false; - } - if (response->klass == platform_class) { - return true; - } - - const char* const name_space = il2cpp_class_get_namespace(response->klass); - const char* const name = il2cpp_class_get_name(response->klass); - if (name_space != nullptr && name != nullptr && std::strcmp(name_space, "Digit.Platform.Models") == 0 - && std::strcmp(name, "ServiceResponse") == 0) { - platform_class = response->klass; - return true; - } - - return false; -} - -static void HandlePlatformResponse(ServiceResponse* service_response) +static void HandlePlatformResponse(PlatformServiceResponse* response) { - auto* const response = reinterpret_cast(service_response); - static auto* const response_class = response->klass; - static auto entity_groups = IL2CppClassHelper{response_class}.GetProperty("EntityGroups"); - if (response->klass != response_class) { + auto* const entity_groups = response->EntityGroups(); + if (entity_groups == nullptr) { return; } - auto* const groups = entity_groups.GetRaw(response); - if (groups == nullptr) { - return; - } - - for (int i = 0; i < groups->Count; ++i) { - auto* const group = reinterpret_cast(groups->get_Item(i)); - if (group == nullptr || group->klass == nullptr) { + for (auto* const entity_group : *entity_groups) { + if (entity_group == nullptr || entity_group->Type_() != PlatformEntityGroup::StoreOffers) { continue; } - - static auto* const group_class = group->klass; - static auto group_type = IL2CppClassHelper{group_class}.GetProperty("Type"); - static auto group_bytes = IL2CppClassHelper{group_class}.GetProperty("Group"); - if (group->klass != group_class) { + auto* const group = entity_group->Group(); + if (group == nullptr || group->bytes() == nullptr || group->Length() <= 0) { continue; } - const auto* const type = group_type.Get(group); - if (type == nullptr || *type != Digit::Platform::Models::EntityGroup::TYPE_STOREOFFERS) { - continue; - } - - auto* const bytes = group_bytes.GetRaw(group); - if (bytes == nullptr || bytes->bytes == nullptr || bytes->Length <= 0) { - continue; - } - - auto payload = std::make_unique(reinterpret_cast(bytes->bytes->m_Items), - static_cast(bytes->Length)); + auto payload = std::make_unique(reinterpret_cast(group->bytes()->data()), + static_cast(group->Length())); try { std::thread([p = std::move(payload)]() mutable { try { @@ -3232,7 +3181,7 @@ template static void put(nlohmann::json& out, const char* key, cons } } -// Game thread only. Missing members are left out. +// Game thread only. Absent objects and unset times are left out; members missing from the client read as zero. static void add_live_state(nlohmann::json& out, FleetPlayerData* fleet, FleetState state) { const auto is_mining = fleet->IsMining(); @@ -3241,57 +3190,57 @@ static void add_live_state(nlohmann::json& out, FleetPlayerData* fleet, FleetSta const auto is_home = fleet->IsHome(); const auto is_destroyed = fleet->IsDestroyed(); - put(out, "is_home", is_home); - put(out, "is_deployed", fleet->IsDeployed()); - put(out, "is_mining", is_mining); - put(out, "is_repairing", is_repairing); - put(out, "is_damaged", fleet->IsDamaged()); - put(out, "is_destroyed", is_destroyed); - put(out, "drydock", fleet->DrydockID()); + out["is_home"] = is_home; + out["is_deployed"] = fleet->IsDeployed(); + out["is_mining"] = is_mining; + out["is_repairing"] = is_repairing; + out["is_damaged"] = fleet->IsDamaged(); + out["is_destroyed"] = is_destroyed; + out["drydock"] = fleet->DrydockID(); // the client only updates this at the station; in space the server's hull points are the live value - if (is_home.value_or(false) || is_destroyed.value_or(false)) { + if (is_home || is_destroyed) { if (const auto damage = fleet->HullDamage()) { out["hull_damage"] = std::llround(*damage); } } - put(out, "cargo_fill", fleet->CargoResourceFillLevel()); + out["cargo_fill"] = fleet->CargoResourceFillLevel(); - if (auto* hold = fleet->GetCargoHoldData()) { + if (auto* hold = fleet->CargoHold()) { if (auto* current = hold->CurrentCargo()) { - put(out, "cargo", current->CurrentValue()); - put(out, "cargo_max", current->MaxValue()); + out["cargo"] = current->CurrentValue(); + out["cargo_max"] = current->MaxValue(); } if (auto* protected_cargo = hold->ProtectedCargoProgress()) { - put(out, "protected_cargo", protected_cargo->CurrentValue()); + out["protected_cargo"] = protected_cargo->CurrentValue(); } } - if (is_mining.value_or(false)) { + if (is_mining) { if (auto* slot = fleet->MiningData()) { - put(out, "node_id", slot->ParentId()); - put(out, "node_resource", slot->ResourceId()); - put(out, "amount_mined", slot->AmountMined()); - put(out, "occupied_since", ManagedRead::DateTimeToUnix(slot->OccupyTimeUtc())); + out["node_id"] = slot->ParentId(); + out["node_resource"] = slot->ResourceId(); + out["amount_mined"] = slot->AmountMined(); + put(out, "occupied_since", slot->OccupyTimeUtc().UnixSeconds()); if (auto* node = slot->PointData()) { - put(out, "node_amount", node->Amount()); + out["node_amount"] = node->Amount(); } } - put(out, "mining_rate", fleet->GetMiningRate()); + out["mining_rate"] = fleet->GetMiningRate(); if (auto* timer = fleet->MiningSlotTimer()) { - put(out, "mining_due", ManagedRead::DateTimeToUnix(timer->DueTime())); + put(out, "mining_due", timer->DueTime().UnixSeconds()); } } - if (is_repairing.value_or(false)) { + if (is_repairing) { if (auto* timer = fleet->Timer()) { - put(out, "repair_due", ManagedRead::DateTimeToUnix(timer->DueTime())); + put(out, "repair_due", timer->DueTime().UnixSeconds()); } } if (state == FleetState::WarpCharging || state == FleetState::Warping || state == FleetState::Impulsing) { if (auto* timer = fleet->Timer()) { - put(out, "travel_due", ManagedRead::DateTimeToUnix(timer->DueTime())); + put(out, "travel_due", timer->DueTime().UnixSeconds()); } } } @@ -3319,16 +3268,16 @@ static size_t late_fields(const nlohmann::json& record) // Game thread only: reads IL2CPP properties of a FleetPlayerData that is valid for the current frame. static nlohmann::json record(int slot, FleetPlayerData* fleet, FleetState state, FleetState previous) { - auto* hull = fleet->Hull; - // HullSpec::Id is read as a 32-bit long; hull ids run past 2^31 (e.g. Kumari 3005922948), so take - // the value as unsigned before widening or those come out negative in the record. - const int64_t hull_id = hull ? static_cast(static_cast(hull->Id)) : 0; + auto* hull = fleet->Hull(); + // Hull ids are 32-bit and run past 2^31 (e.g. Kumari 3005922948); take the value as unsigned before widening so + // those never come out negative in the record. + const int64_t hull_id = hull ? static_cast(static_cast(hull->Id())) : 0; nlohmann::json out = {{"type", SyncConfig::Type::Fleets}, - {"fid", fleet->Id}, + {"fid", fleet->Id()}, {"slot", slot}, {"hull_id", hull_id}, - {"level", fleet->Level}, + {"level", fleet->Level()}, {"state", static_cast(state)}, {"state_name", state_name(state)}, {"prev_state", static_cast(previous)}, @@ -3338,10 +3287,10 @@ static nlohmann::json record(int slot, FleetPlayerData* fleet, FleetState state, try { add_live_state(out, fleet, state); } catch (const std::exception& e) { - spdlog::warn("Sync: fleet live state read failed for {}: {}", fleet->Id, e.what()); + spdlog::warn("Sync: fleet live state read failed for {}: {}", fleet->Id(), e.what()); } - sent_late[fleet->Id] = late_fields(out); + sent_late[fleet->Id()] = late_fields(out); return out; } @@ -3356,7 +3305,7 @@ static void queue_snapshot(const char* reason) auto fleet_array = nlohmann::json::array(); for (int slot = 0; slot < kFleetSlotCount; ++slot) { if (auto* fleet = manager->GetFleetPlayerData(slot); fleet) { - fleet_array.push_back(record(slot, fleet, fleet->CurrentState, fleet->PreviousState)); + fleet_array.push_back(record(slot, fleet, fleet->CurrentState(), fleet->PreviousState())); } } @@ -3415,11 +3364,11 @@ static void reread_requested() for (int slot = 0; slot < kFleetSlotCount; ++slot) { try { auto* fleet = manager->GetFleetPlayerData(slot); - if (!fleet || std::ranges::find(due, fleet->Id) == due.end()) { + if (!fleet || std::ranges::find(due, fleet->Id()) == due.end()) { continue; } - const auto before = sent_late[fleet->Id]; - auto entry = record(slot, fleet, fleet->CurrentState, fleet->PreviousState); + const auto before = sent_late[fleet->Id()]; + auto entry = record(slot, fleet, fleet->CurrentState(), fleet->PreviousState()); if (late_fields(entry) != before) { fleet_array.push_back(std::move(entry)); } @@ -3460,6 +3409,14 @@ static void install() } } // namespace fleets +using GameServerModelRegistry = + il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Core", "GameServerModelRegistry">; +using GameServer = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Core", "GameServer">; +using SlotAssignRtcParser = + il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Parsers", "SlotAssignRtcParser">; +using SlotClearRtcParser = + il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Parsers", "SlotClearRtcParser">; + namespace hooks { // IL2CPP value type: keep this parameter by value so following arguments retain their platform ABI positions. @@ -3473,11 +3430,11 @@ static_assert(sizeof(CentrifugoInfo) == 16); static void* RtcParser_ParseFinalPayload(auto original, void* _this, CentrifugoInfo centrifugoInfo, RealtimeDataPayload* realtimeDataPayload, void* finalPayload) { - if (realtimeDataPayload != nullptr && realtimeDataPayload->Target != nullptr - && realtimeDataPayload->DataType != nullptr && realtimeDataPayload->Data != nullptr) { - if (const auto type_string = to_string(realtimeDataPayload->DataType); std::stoi(type_string) == DataType::JSON) { - const auto target = to_string(realtimeDataPayload->Target); - const auto rtc_data = to_string(realtimeDataPayload->Data); + if (realtimeDataPayload != nullptr && realtimeDataPayload->Target() != nullptr + && realtimeDataPayload->DataType() != nullptr && realtimeDataPayload->Data() != nullptr) { + if (const auto type_string = to_string(realtimeDataPayload->DataType()); std::stoi(type_string) == DataType::JSON) { + const auto target = to_string(realtimeDataPayload->Target()); + const auto rtc_data = to_string(realtimeDataPayload->Data()); // spdlog::debug("Received RTC payload for target '{}': {}", target, rtc_data); auto payload = std::make_unique(rtc_data); @@ -3501,15 +3458,20 @@ static void GameServerModelRegistry_ProcessResultInternal(auto original, void* _ ServiceResponse* service_response, MethodInfo* method) { // Only the store offers option looks at Platform responses; everyone else keeps the unchanged path below. - if (Config::Get().sync_options.store_offers && IsPlatformResponse(service_response)) { - HandlePlatformResponse(service_response); - return original(_this, parsing_context, service_response, method); + if (Config::Get().sync_options.store_offers && service_response != nullptr) { + if (auto* const platform = service_response->As()) { + HandlePlatformResponse(platform); + return original(_this, parsing_context, service_response, method); + } } - auto* const entity_groups = service_response->EntityGroups; - for (int i = 0; i < entity_groups->Count; ++i) { - auto* const entity_group = entity_groups->get_Item(i); - HandleEntityGroup(entity_group); + // The hooked method is shared generic code; PlatformModelRegistry passes its own response type. + auto* const entity_groups = + service_response && service_response->Is() ? service_response->EntityGroups() : nullptr; + if (entity_groups) { + for (auto* const entity_group : *entity_groups) { + HandleEntityGroup(entity_group); + } } return original(_this, parsing_context, service_response, method); @@ -3543,74 +3505,31 @@ void InstallSyncPatches() { trackers::load_previously_sent_logs(); - if (auto game_server_model_registry = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Core", "GameServerModelRegistry"); - !game_server_model_registry.isValidHelper()) { - ErrorMsg::MissingHelper("Core", "GameServerModelRegistry"); - } else { - if (auto *const ptr = game_server_model_registry.GetMethod("ProcessResultInternal"); ptr == nullptr) { - ErrorMsg::MissingMethod("GameServerModelRegistry", "ProcessResultInternal"); - } else { - SPUD_STATIC_DETOUR(ptr, hooks::GameServerModelRegistry_ProcessResultInternal); - } - } + if (auto m = GameServerModelRegistry::Method("ProcessResultInternal")) + SPUD_STATIC_DETOUR(m.Pointer(), hooks::GameServerModelRegistry_ProcessResultInternal); - if (auto prime_app = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Core", "PrimeApp"); - !prime_app.isValidHelper()) { - ErrorMsg::MissingHelper("Core", "PrimeApp"); - } else { - if (auto *const ptr = prime_app.GetMethod("InitPrimeServer"); ptr == nullptr) { - ErrorMsg::MissingMethod("PrimeApp", "InitPrimeServer"); - } else { - SPUD_STATIC_DETOUR(ptr, hooks::PrimeApp_InitPrimeServer); - } - } + if (auto m = PrimeApp::Class::Method("InitPrimeServer")) + SPUD_STATIC_DETOUR(m.Pointer(), hooks::PrimeApp_InitPrimeServer); - if (auto game_server = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Core", "GameServer"); - !game_server.isValidHelper()) { - ErrorMsg::MissingHelper("Core", "GameServer"); - } else { - // This hook only captures the game version for the X-PRIME-VERSION sync header - // (headers::primeVersion has a static fallback). It is optional so users can turn - // it off when another mod detours the same method — a native and a managed detour - // on one prologue corrupt each other and crash at login. - if (!Config::Get().installGameVersionHook) { - spdlog::info("Sync: skipping GameServer::Initialise hook (patches.game_version = false)"); - } else if (auto *const ptr = game_server.GetMethod("Initialise"); ptr == nullptr) { - ErrorMsg::MissingMethod("GameServer", "Initialise"); - } else { - SPUD_STATIC_DETOUR(ptr, hooks::GameServer_Initialise); - } - - if (auto *const ptr = game_server.GetMethod("SetInstanceIdHeader"); ptr == nullptr) { - ErrorMsg::MissingMethod("GameServer", "SetInstanceIdHeader"); - } else { - SPUD_STATIC_DETOUR(ptr, hooks::GameServer_SetInstanceIdHeader); - } + // This hook only captures the game version for the X-PRIME-VERSION sync header + // (headers::primeVersion has a static fallback). It is optional so users can turn + // it off when another mod detours the same method — a native and a managed detour + // on one prologue corrupt each other and crash at login. + if (!Config::Get().installGameVersionHook) { + spdlog::info("Sync: skipping GameServer::Initialise hook (patches.game_version = false)"); + } else if (auto m = GameServer::Method("Initialise")) { + SPUD_STATIC_DETOUR(m.Pointer(), hooks::GameServer_Initialise); } - if (auto slot_assign_parser = il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Parsers", "SlotAssignRtcParser"); - !slot_assign_parser.isValidHelper()) { - ErrorMsg::MissingHelper("Parsers", "SlotAssignRtcParser"); - } else { - if (auto* const ptr = slot_assign_parser.GetMethod("ParseFinalPayload"); ptr == nullptr) { - ErrorMsg::MissingMethod("SlotAssignRtcParser", "ParseFinalPayload"); - } else { - SPUD_STATIC_DETOUR(ptr, hooks::RtcParser_ParseFinalPayload); - } - } + if (auto m = GameServer::Method("SetInstanceIdHeader")) + SPUD_STATIC_DETOUR(m.Pointer(), hooks::GameServer_SetInstanceIdHeader); - if (auto slot_clear_parser = il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Parsers", "SlotClearRtcParser"); - !slot_clear_parser.isValidHelper()) { - ErrorMsg::MissingHelper("Parsers", "SlotClearRtcParser"); - } else { - if (auto* const ptr = slot_clear_parser.GetMethod("ParseFinalPayload"); ptr == nullptr) { - ErrorMsg::MissingMethod("SlotClearRtcParser", "ParseFinalPayload"); - } else { - SPUD_STATIC_DETOUR(ptr, hooks::RtcParser_ParseFinalPayload); - } - } + // Each parser overrides ParseFinalPayload, so these are two distinct native methods. + if (auto m = SlotAssignRtcParser::Method("ParseFinalPayload")) + SPUD_STATIC_DETOUR(m.Pointer(), hooks::RtcParser_ParseFinalPayload); + + if (auto m = SlotClearRtcParser::Method("ParseFinalPayload")) + SPUD_STATIC_DETOUR(m.Pointer(), hooks::RtcParser_ParseFinalPayload); fleets::install(); diff --git a/mods/src/patches/parts/testing.cc b/mods/src/patches/parts/testing.cc index 818ab1cf5..eb1e78092 100644 --- a/mods/src/patches/parts/testing.cc +++ b/mods/src/patches/parts/testing.cc @@ -1,10 +1,9 @@ #include "config.h" -#include "errormsg.h" +#include "prime/ActionQueueManager.h" #include "prime/ActionRequirement.h" #include "prime/BlurController.h" #include "prime/BookmarksManager.h" -#include "prime/CallbackContainer.h" #include "prime/ChatManager.h" #include "prime/ChatMessageListLocalViewController.h" #include "prime/ClientModifierType.h" @@ -21,81 +20,32 @@ #include "prime/ScreenManager.h" #include -#include +#include #include #include -class AppConfig -{ -public: - __declspec(property(get = __get_PlatformSettingsUrl, put = __set_PlatformSettingsUrl)) - Il2CppString* PlatformSettingsUrl; - __declspec(property(get = __get_PlatformApiKey, put = __set_PlatformApiKey)) Il2CppString* PlatformApiKey; - __declspec(property(get = __get_AssetUrlOverride, put = __set_AssetUrlOverride)) Il2CppString* AssetUrlOverride; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Core", "AppConfig"); - return class_helper; - } - -public: - Il2CppString* __get_PlatformSettingsUrl() - { - static auto prop = get_class_helper().GetProperty("PlatformSettingsUrl"); - return prop.GetRaw((void*)this); - } - - void __set_PlatformSettingsUrl(Il2CppString* v) - { - static auto prop = get_class_helper().GetProperty("PlatformSettingsUrl"); - return prop.SetRaw((void*)this, *v); - } - - Il2CppString* __get_PlatformApiKey() - { - static auto prop = get_class_helper().GetProperty("PlatformApiKey"); - return prop.GetRaw((void*)this); - } - - void __set_PlatformApiKey(Il2CppString* v) - { - static auto prop = get_class_helper().GetProperty("PlatformApiKey"); - return prop.SetRaw((void*)this, *v); - } - - Il2CppString* __get_AssetUrlOverride() - { - static auto prop = get_class_helper().GetProperty("AssetUrlOverride"); - return prop.GetRaw((void*)this); - } - - void __set_AssetUrlOverride(Il2CppString* v) - { - static auto prop = get_class_helper().GetProperty("AssetUrlOverride"); - return prop.SetRaw((void*)this, *v); - } +struct AppConfig : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Core", "AppConfig">; + + Il2CppString* PlatformSettingsUrl() + { return Get<"PlatformSettingsUrl", Il2CppString*>(); } + void SetPlatformSettingsUrl(Il2CppString* value) + { Set<"PlatformSettingsUrl">(value); } + Il2CppString* PlatformApiKey() + { return Get<"PlatformApiKey", Il2CppString*>(); } + void SetPlatformApiKey(Il2CppString* value) + { Set<"PlatformApiKey">(value); } + Il2CppString* AssetUrlOverride() + { return Get<"AssetUrlOverride", Il2CppString*>(); } + void SetAssetUrlOverride(Il2CppString* value) + { Set<"AssetUrlOverride">(value); } }; -class Model -{ -public: - __declspec(property(get = __get_AppConfig)) AppConfig* AppConfig_; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Core", "Model"); - return class_helper; - } +struct Model : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Core", "Model">; -public: - AppConfig* __get_AppConfig() - { - static auto field = get_class_helper().GetField("_appConfig"); - return *(AppConfig**)((ptrdiff_t)this + field.offset()); - } + AppConfig* appConfig() + { return Field<"_appConfig", AppConfig*>(); } }; void Cursor_SetCursor(auto original, void* _this, ptrdiff_t texture, Vector2* hotspot, int cursorMode) @@ -111,30 +61,26 @@ void Cursor_SetCursor(auto original, void* _this, ptrdiff_t texture, Vector2* ho return original(_this, texture, hotspot, cursorMode); } -AppConfig* Model_LoadConfigs(auto original, Model* _this) +void Model_LoadConfigs(auto original, Model* _this) { original(_this); - auto config = _this->AppConfig_; + auto config = _this->appConfig(); if (!config) { - return nullptr; + return; } if (!Config::Get().config_settings_url.empty()) { - auto new_settings_url = il2cpp_string_new(Config::Get().config_settings_url.c_str()); - config->PlatformSettingsUrl = new_settings_url; + config->SetPlatformSettingsUrl(il2cpp_string_new(Config::Get().config_settings_url.c_str())); } if (!Config::Get().config_assets_url_override.empty()) { - auto new_url = il2cpp_string_new(Config::Get().config_assets_url_override.c_str()); - config->AssetUrlOverride = new_url; + config->SetAssetUrlOverride(il2cpp_string_new(Config::Get().config_assets_url_override.c_str())); } - - return config; } void SetActive_hook(auto original, void* _this, bool active) { - static auto IsActiveSelf = il2cpp_resolve_icall_typed("UnityEngine.GameObject::get_activeSelf()"); + static auto IsActiveSelf = il2cpp::ResolveICall("UnityEngine.GameObject::get_activeSelf()"); if (active && IsActiveSelf(_this)) { return; @@ -154,55 +100,18 @@ bool IsQueueEnabled(auto original, void* _this) void InstallTestPatches() { - auto cursorManager = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Cursor"); - if (!cursorManager.isValidHelper()) { - ErrorMsg::MissingHelper("UnityEngine", "Cursor"); - } else { - auto cursorMethod = cursorManager.GetMethod("SetCursor_Injected"); - if (cursorMethod == nullptr) { - ErrorMsg::MissingMethod("Cursor", "SetCursor_Injected"); - } else { - SPUD_STATIC_DETOUR(cursorMethod, Cursor_SetCursor); - } - } + using Cursor = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Cursor">; + if (auto m = Cursor::Method("SetCursor_Injected")) + SPUD_STATIC_DETOUR(m.Pointer(), Cursor_SetCursor); - auto model = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Core", "Model"); - if (!model.isValidHelper()) { - ErrorMsg::MissingHelper("Core", "Model"); - } else { - auto load_configs_ptr = model.GetMethod("LoadConfigs"); - if (load_configs_ptr == nullptr) { - ErrorMsg::MissingMethod("Model", "LoadConfigs"); - } else { - SPUD_STATIC_DETOUR(load_configs_ptr, Model_LoadConfigs); - } - } + if (auto m = Model::Class::Method("LoadConfigs", 0)) + SPUD_STATIC_DETOUR(m.Pointer(), Model_LoadConfigs); -// this method is not accessible in v48084 -// auto battle_target_data = -// il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "BattleTargetData"); -// if (!battle_target_data.isValidHelper()) { -// ErrorMsg::MissingHelper("Models", "BattleTargetData"); -// } else { -// static auto SetActive = -// il2cpp_resolve_icall_typed("UnityEngine.GameObject::SetActive(System.Boolean)"); -// if (SetActive == nullptr) { -// ErrorMsg::MissingStaticMethod("GameObject", "SetActive"); -// } else { -// SPUD_STATIC_DETOUR(SetActive, SetActive_hook); -// } -// } - - auto queue_manager = il2cpp_get_class_helper("Assembly-CSharp", "Prime.ActionQueue", "ActionQueueManager"); - if (!queue_manager.isValidHelper()) { - ErrorMsg::MissingHelper("ActionQueue", "ActionQueueManager"); - } else { - - auto is_queue_unlocked = queue_manager.GetMethod("IsQueueUnlocked"); - if (is_queue_unlocked == nullptr) { - ErrorMsg::MissingStaticMethod("ActionQueueManager", "IsQueueUnlocked"); - } else { - SPUD_STATIC_DETOUR(is_queue_unlocked, IsQueueEnabled); - } - } + // this method is not accessible in v48084 + // static auto SetActive = il2cpp::ResolveICall("UnityEngine.GameObject::SetActive(System.Boolean)"); + // if (SetActive) + // SPUD_STATIC_DETOUR(SetActive, SetActive_hook); + + if (auto m = ActionQueueManager::Class::Method("IsQueueUnlocked")) + SPUD_STATIC_DETOUR(m.Pointer(), IsQueueEnabled); } diff --git a/mods/src/patches/parts/transition_screen.cc b/mods/src/patches/parts/transition_screen.cc index b883a6c06..ee0309e48 100644 --- a/mods/src/patches/parts/transition_screen.cc +++ b/mods/src/patches/parts/transition_screen.cc @@ -1,4 +1,3 @@ - #include "loading_screen_common.h" #include @@ -9,14 +8,44 @@ namespace ls = ls_common; void ResetLoadingScreenState(); void ResetLoadingTipState(); +namespace +{ +struct Behaviour : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Behaviour">; + + void SetEnabled(bool value) + { Set<"enabled">(value); } +}; + +struct TransitionViewController : Component { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.LoadingScreen", "TransitionViewController">; + + Image* staticOverride() + { return Field<"_staticOverride", Image*>(); } + // The canvas Animator; Behaviour is enough to toggle it. + Behaviour* animator() + { return Field<"_animator", Behaviour*>(); } +}; + +struct SlideShowViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.SlideShow", "SlideShowViewController">; + + Image* image() + { return Field<"_image", Image*>(); } +}; + +// The non-generic MonoSingleton base; its static PrepareAllForReload runs before a reload. +using MonoSingletonBase = il2cpp::Class<"Assembly-CSharp", "", "MonoSingleton">; +} // namespace + // Per-TVC-instance state (reset in TVC.Awake) -static bool g_spriteApplied = false; -static void* g_bgImageComp = nullptr; -static void* g_bgRectTransform = nullptr; -static void* g_bgOverlayGO = nullptr; -static void* g_logoGO = nullptr; -static void* g_ccLogoGO = nullptr; -static void* g_canvasAnimator = nullptr; +static bool g_spriteApplied = false; +static Image* g_bgImageComp = nullptr; +static Transform* g_bgRectTransform = nullptr; +static GameObject* g_bgOverlayGO = nullptr; +static GameObject* g_logoGO = nullptr; +static GameObject* g_ccLogoGO = nullptr; +static Behaviour* g_canvasAnimator = nullptr; // Called from PrepareAllForReload to null stale pointers before reload. // The Canvas/TVC hierarchy holding our overlays is replaced during @@ -37,268 +66,169 @@ void ResetTransitionScreenState() // Finds the BG Image component on a TransitionViewController instance. // Tries TVC._staticOverride field first, then traverses children for "BGContainer". -static void* FindTVCBGImage(void* _this, void*& outRectTransform) +static Image* FindTVCBGImage(TransitionViewController* _this, Transform*& outRectTransform) { - static auto tv_h = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.LoadingScreen", "TransitionViewController"); - static auto mb_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "MonoBehaviour"); - static auto go_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "GameObject"); - static auto tr_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Transform"); - static auto img_h = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "Image"); - - // Try _staticOverride field first - if (tv_h.isValidHelper()) { - static auto f_ov = tv_h.GetField("_staticOverride"); - if (f_ov.isValidHelper()) { - void* img = *reinterpret_cast((char*)_this + f_ov.offset()); - if (img) return img; - } - } + if (auto* img = _this->staticOverride()) + return img; // Fallback: traverse children to find "BGContainer" → first child's Image - static auto fn_go = mb_h.GetMethod("get_gameObject"); - static auto fn_tr = go_h.GetMethod("get_transform"); - static auto fn_cc = tr_h.GetMethod("get_childCount"); - static auto fn_ch = tr_h.GetMethod("GetChild"); - static auto fn_gg = tr_h.GetMethod("get_gameObject"); - static auto fn_nm = go_h.GetMethod("get_name"); - static auto fn_gc = go_h.GetMethod("GetComponent", 1); - if (!fn_go || !fn_tr || !fn_cc || !fn_ch || !fn_gg || !fn_nm || !fn_gc) return nullptr; - - void* go = reinterpret_cast(fn_go)(_this); - if (!go) return nullptr; - void* root = reinterpret_cast(fn_tr)(go); + auto* root = _this->transform(); if (!root) return nullptr; - void* bgc = nullptr; - int32_t n = reinterpret_cast(fn_cc)(root); - for (int i = 0; i < n; ++i) { - void* ct = reinterpret_cast(fn_ch)(root, i); + Transform* bgc = nullptr; + int32_t n = root->childCount(); + for (int32_t i = 0; i < n; ++i) { + auto* ct = root->GetChild(i); if (!ct) continue; - void* cg = reinterpret_cast(fn_gg)(ct); - if (!cg) continue; - char buf[64] = {}; - ls::ReadIl2CppString(reinterpret_cast(fn_nm)(cg), buf, sizeof(buf)); - if (strcmp(buf, "BGContainer") == 0) { + auto* cg = ct->gameObject(); + if (cg && cg->Name() == "BGContainer") { bgc = ct; break; } } - if (!bgc) return nullptr; - if (reinterpret_cast(fn_cc)(bgc) == 0) return nullptr; + if (!bgc || bgc->childCount() == 0) return nullptr; - void* bgt = reinterpret_cast(fn_ch)(bgc, 0); + auto* bgt = bgc->GetChild(0); if (!bgt) return nullptr; outRectTransform = bgt; - void* bgg = reinterpret_cast(fn_gg)(bgt); - if (!bgg) return nullptr; - void* it = img_h.GetType(); - if (!it) return nullptr; - return reinterpret_cast(fn_gc)(bgg, it); + auto* bgg = bgt->gameObject(); + return bgg ? bgg->GetComponent() : nullptr; } // Applies custom background and logos to a TransitionViewController. // Called from TVC.Awake (as fallback) and TVC.AboutToShow (primary). -static void ApplyTransitionCustomization(void* _this) +static void ApplyTransitionCustomization(TransitionViewController* _this) { - try { - const auto& cfg = Config::Get(); - - // Find the BG image component - void* rt = nullptr; - void* imageComp = g_bgImageComp; - if (!imageComp) { - imageComp = FindTVCBGImage(_this, rt); - if (!imageComp) return; - g_bgImageComp = imageComp; - g_bgRectTransform = rt; - } + const auto& cfg = Config::Get(); + + // Find the BG image component + if (!g_bgImageComp) { + Transform* rt = nullptr; + auto* imageComp = FindTVCBGImage(_this, rt); + if (!imageComp) return; + g_bgImageComp = imageComp; + g_bgRectTransform = rt; + } + auto* imageComp = g_bgImageComp; - // Determine logo parent transform - void* logoParent = g_bgRectTransform; - if (!logoParent && g_bgImageComp) { - static auto comp_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Component"); - static auto fn_get_tr = comp_h.GetMethod("get_transform"); - if (fn_get_tr) - logoParent = reinterpret_cast(fn_get_tr)(g_bgImageComp); - } + // Determine logo parent transform + auto* logoParent = g_bgRectTransform ? g_bgRectTransform : imageComp->transform(); - if (!g_canvasAnimator) { - static auto tv_h = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.LoadingScreen", "TransitionViewController"); - static auto fn_animField = tv_h.GetField("_animator"); - static auto behav_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Behaviour"); - static auto fn_setEn = behav_h.GetMethodInfo("set_enabled"); - if (fn_animField.isValidHelper() && fn_setEn) { - void* anim = *reinterpret_cast((char*)_this + fn_animField.offset()); - if (anim) { - bool off = false; - void* args[1] = {&off}; - ls::InvokeVoid(fn_setEn, anim, args, "canvasAnimator.set_enabled(false)"); - g_canvasAnimator = anim; - } - } + if (!g_canvasAnimator) { + if (auto* anim = _this->animator()) { + anim->SetEnabled(false); + g_canvasAnimator = anim; } + } - if (cfg.loader_transition_black) { - if (logoParent) { - ls::CreateLogoOverlay(logoParent, g_logoGO); - ls::CreateCCLogoOverlay(logoParent, g_ccLogoGO); - } - g_spriteApplied = true; - return; + if (cfg.loader_transition_black) { + if (logoParent) { + ls::CreateLogoOverlay(logoParent, g_logoGO); + ls::CreateCCLogoOverlay(logoParent, g_ccLogoGO); } + g_spriteApplied = true; + return; + } #ifndef _USE_ORIGINAL_BG - { - if (logoParent && !g_bgOverlayGO) { - auto* asset = ls::GetLoadingAsset(); - if (asset) g_bgOverlayGO = ls::CreateBGOverlay(logoParent, *asset); - } + if (logoParent && !g_bgOverlayGO) { + auto* asset = ls::GetLoadingAsset(); + if (asset) g_bgOverlayGO = ls::CreateBGOverlay(logoParent, *asset); + } - // Preserve the game's background unless the custom replacement is fully - // constructed and attached. This keeps failures visible but harmless. - if (!g_bgOverlayGO) { - spdlog::warn("[LS] custom transition background unavailable; preserving game background"); - return; - } + // Preserve the game's background unless the custom replacement is fully + // constructed and attached. This keeps failures visible but harmless. + if (!g_bgOverlayGO) { + spdlog::warn("[LS] custom transition background unavailable; preserving game background"); + return; + } - ls::HideImage(imageComp); - - if (g_bgRectTransform) { - ls::SetFullRect(g_bgRectTransform, {0.0f, 0.0f}, {1.0f, 1.0f}, {0.5f, 0.5f}, {0.0f, 0.0f}, {0.0f, 0.0f}); - static auto tr_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Transform"); - static auto fn_eu = tr_h.GetMethodInfo("set_localEulerAngles"); - if (fn_eu) { - ls::FakeVector3 z{0, 0, 0}; - void* args[1] = {&z}; - ls::InvokeVoid(fn_eu, g_bgRectTransform, args, "Transform.set_localEulerAngles"); - } - static auto fn_sc = tr_h.GetMethodInfo("set_localScale"); - if (fn_sc) { - ls::FakeVector3 o{1, 1, 1}; - void* args[1] = {&o}; - ls::InvokeVoid(fn_sc, g_bgRectTransform, args, "Transform.set_localScale"); - } - } + ls::HideImage(imageComp); - } + if (g_bgRectTransform) { + ls::SetFullRect(g_bgRectTransform->As(), {0.0f, 0.0f}, {1.0f, 1.0f}, {0.5f, 0.5f}, + {0.0f, 0.0f}, {0.0f, 0.0f}); + g_bgRectTransform->SetLocalEulerAngles({0, 0, 0}); + g_bgRectTransform->SetLocalScale({1, 1, 1}); + } #endif - if (logoParent) { - ls::CreateLogoOverlay(logoParent, g_logoGO); - ls::CreateCCLogoOverlay(logoParent, g_ccLogoGO); - } + if (logoParent) { + ls::CreateLogoOverlay(logoParent, g_logoGO); + ls::CreateCCLogoOverlay(logoParent, g_ccLogoGO); + } - { - static auto mb_hR = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "MonoBehaviour"); - static auto go_hR = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "GameObject"); - static auto tr_hR = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Transform"); - static auto rt_hR = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "RectTransform"); - static auto fn_goR = mb_hR.GetMethod("get_gameObject"); - static auto fn_trR = go_hR.GetMethod("get_transform"); - static auto fn_ccR = tr_hR.GetMethod("get_childCount"); - static auto fn_chR = tr_hR.GetMethod("GetChild"); - static auto fn_ggR = tr_hR.GetMethod("get_gameObject"); - static auto fn_nmR = go_hR.GetMethod("get_name"); - static auto fn_gcR = go_hR.GetMethod("GetComponent", 1); - - if (fn_goR && fn_trR && fn_ccR && fn_chR && fn_ggR && fn_nmR && fn_gcR && rt_hR.isValidHelper()) { - void* tvcGO = reinterpret_cast(fn_goR)(_this); - void* tvcTr = tvcGO ? reinterpret_cast(fn_trR)(tvcGO) : nullptr; - int32_t n = tvcTr ? reinterpret_cast(fn_ccR)(tvcTr) : 0; - void* rtType = rt_hR.GetType(); - - for (int32_t i = 0; i < n; ++i) { - void* ct = reinterpret_cast(fn_chR)(tvcTr, i); - if (!ct) continue; - void* cg = reinterpret_cast(fn_ggR)(ct); - if (!cg || !rtType) continue; - char buf[64] = {}; - ls::ReadIl2CppString(reinterpret_cast(fn_nmR)(cg), buf, sizeof(buf)); - - void* rt = nullptr; - if (strcmp(buf, "LogoContainer") == 0) { - rt = reinterpret_cast(fn_gcR)(cg, rtType); - ls::SetFullRect(rt, {1.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 1.0f}, {586.0f, 248.0f}, {-20.0f, -20.0f}); - } else if (strcmp(buf, "LoadingTipsContainer") == 0) { - rt = reinterpret_cast(fn_gcR)(cg, rtType); - ls::SetFullRect(rt, {0.5f, 0.5f}, {0.5f, 0.5f}, {0.5f, 0.5f}, {1024.0f, 100.0f}, {0.0f, -320.0f}); - } - } + if (auto* tvcTr = _this->transform()) { + int32_t n = tvcTr->childCount(); + for (int32_t i = 0; i < n; ++i) { + auto* ct = tvcTr->GetChild(i); + if (!ct) continue; + auto* cg = ct->gameObject(); + if (!cg) continue; + const auto name = cg->Name(); + if (name == "LogoContainer") { + ls::SetFullRect(cg->GetComponent(), {1.0f, 1.0f}, {1.0f, 1.0f}, {1.0f, 1.0f}, + {586.0f, 248.0f}, {-20.0f, -20.0f}); + } else if (name == "LoadingTipsContainer") { + ls::SetFullRect(cg->GetComponent(), {0.5f, 0.5f}, {0.5f, 0.5f}, {0.5f, 0.5f}, + {1024.0f, 100.0f}, {0.0f, -320.0f}); } } + } - g_spriteApplied = true; - } catch (...) {} + g_spriteApplied = true; } // --- Hooks --- -static void TVC_Awake_Hook(auto original, void* _this) +static void TVC_Awake_Hook(auto original, TransitionViewController* _this) { original(_this); - try { - if (!Config::Get().loader_transition) return; + if (!Config::Get().loader_transition) return; - // A TransitionViewController can outlive the LoginSequence generation - // that created the cached Unity assets. Recreate them for this controller - // before touching its background hierarchy. - ls::ResetLoadingAssetsForScene(); + // A TransitionViewController can outlive the LoginSequence generation + // that created the cached Unity assets. Recreate them for this controller + // before touching its background hierarchy. + ls::ResetLoadingAssetsForScene(); - g_spriteApplied = false; - g_bgImageComp = nullptr; - g_bgRectTransform = nullptr; - g_bgOverlayGO = nullptr; - g_logoGO = nullptr; - g_ccLogoGO = nullptr; - g_canvasAnimator = nullptr; + g_spriteApplied = false; + g_bgImageComp = nullptr; + g_bgRectTransform = nullptr; + g_bgOverlayGO = nullptr; + g_logoGO = nullptr; + g_ccLogoGO = nullptr; + g_canvasAnimator = nullptr; - ApplyTransitionCustomization(_this); - } catch (...) {} + ApplyTransitionCustomization(_this); } -static void TVC_AboutToShow_Hook(auto original, void* _this) +static void TVC_AboutToShow_Hook(auto original, TransitionViewController* _this) { original(_this); - try { - if (!Config::Get().loader_transition || g_spriteApplied) return; - ApplyTransitionCustomization(_this); - } catch (...) {} + if (!Config::Get().loader_transition || g_spriteApplied) return; + ApplyTransitionCustomization(_this); } -static void TVC_AboutToHide_Hook(auto original, void* _this) +static void TVC_AboutToHide_Hook(auto original, TransitionViewController* _this) { - try { - if (Config::Get().loader_transition && g_canvasAnimator) { - // Re-enable canvas animator so the hide animation plays - static auto behav_h = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Behaviour"); - static auto fn_setEn = behav_h.GetMethodInfo("set_enabled"); - if (fn_setEn) { - bool on = true; - void* args[1] = {&on}; - ls::InvokeVoid(fn_setEn, g_canvasAnimator, args, "canvasAnimator.set_enabled(true)"); - } - } - } catch (...) {} + if (Config::Get().loader_transition && g_canvasAnimator) { + // Re-enable canvas animator so the hide animation plays + g_canvasAnimator->SetEnabled(true); + } original(_this); } -static void SlideShowViewer_ShowCurrentSlide_Hook(auto original, void* _this) +static void SlideShowViewer_ShowCurrentSlide_Hook(auto original, SlideShowViewController* _this) { original(_this); - try { - const auto& cfg = Config::Get(); - if (!cfg.loader_transition || cfg.loader_transition_black) return; + const auto& cfg = Config::Get(); + if (!cfg.loader_transition || cfg.loader_transition_black) return; - static auto h = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.SlideShow", "SlideShowViewController"); - static auto fi = h.GetField("_image"); - if (!fi.isValidHelper()) return; - void* img = *reinterpret_cast((char*)_this + fi.offset()); - if (img) ls::HideImage(img); - } catch (...) {} + ls::HideImage(_this->image()); } static void TS_MonoSingleton_PrepareAllForReload_Hook(auto original) @@ -309,49 +239,14 @@ static void TS_MonoSingleton_PrepareAllForReload_Hook(auto original) void InstallTransitionScreenHooks() { - auto tv_h = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.LoadingScreen", "TransitionViewController"); - if (!tv_h.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.Prime.LoadingScreen", "TransitionViewController"); - return; - } - - bool ok = true; - - if (auto m = tv_h.GetMethod("Awake")) { - SPUD_STATIC_DETOUR(m, TVC_Awake_Hook); - } else { - ErrorMsg::MissingMethod("TransitionViewController", "Awake"); - ok = false; - } - - if (auto m = tv_h.GetMethod("AboutToShow")) { - SPUD_STATIC_DETOUR(m, TVC_AboutToShow_Hook); - } else { - ErrorMsg::MissingMethod("TransitionViewController", "AboutToShow"); - ok = false; - } - - if (auto m = tv_h.GetMethod("AboutToHide")) { - SPUD_STATIC_DETOUR(m, TVC_AboutToHide_Hook); - } else { - ErrorMsg::MissingMethod("TransitionViewController", "AboutToHide"); - ok = false; - } - - auto ss_h = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.SlideShow", "SlideShowViewController"); - if (ss_h.isValidHelper()) { - if (auto m = ss_h.GetMethod("ShowCurrentSlide")) { - SPUD_STATIC_DETOUR(m, SlideShowViewer_ShowCurrentSlide_Hook); - } - } - - auto ms_h = il2cpp_get_class_helper("Assembly-CSharp", "", "MonoSingleton"); - if (ms_h.isValidHelper()) { - if (auto m = ms_h.GetMethod("PrepareAllForReload")) { - SPUD_STATIC_DETOUR(m, TS_MonoSingleton_PrepareAllForReload_Hook); - } - } - - if (!ok) - spdlog::error("[TransitionScreen] Some hooks failed to install"); + if (auto m = TransitionViewController::Class::Method("Awake")) + SPUD_STATIC_DETOUR(m.Pointer(), TVC_Awake_Hook); + if (auto m = TransitionViewController::Class::Method("AboutToShow")) + SPUD_STATIC_DETOUR(m.Pointer(), TVC_AboutToShow_Hook); + if (auto m = TransitionViewController::Class::Method("AboutToHide")) + SPUD_STATIC_DETOUR(m.Pointer(), TVC_AboutToHide_Hook); + if (auto m = SlideShowViewController::Class::Method("ShowCurrentSlide")) + SPUD_STATIC_DETOUR(m.Pointer(), SlideShowViewer_ShowCurrentSlide_Hook); + if (auto m = MonoSingletonBase::Method("PrepareAllForReload")) + SPUD_STATIC_DETOUR(m.Pointer(), TS_MonoSingleton_PrepareAllForReload_Hook); } diff --git a/mods/src/patches/parts/ui_scale.cc b/mods/src/patches/parts/ui_scale.cc index 7b23d87aa..78736ef74 100644 --- a/mods/src/patches/parts/ui_scale.cc +++ b/mods/src/patches/parts/ui_scale.cc @@ -1,7 +1,7 @@ -#include "errormsg.h" +#include "patches/object_tracker.h" #include -#include +#include #include #include @@ -22,23 +22,9 @@ float ShipScaleMultiplier() return multiplier > 0.0f ? multiplier : 1.0f; } -Transform* GetGameObjectTransform(void* game_object) +Transform* GetGameObjectTransform(GameObject* game_object) { - if (game_object == nullptr) { - return nullptr; - } - - static auto game_object_helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "GameObject"); - if (!game_object_helper.isValidHelper()) { - return nullptr; - } - - static auto transform_property = game_object_helper.GetProperty("transform"); - if (!transform_property.isValidHelper()) { - return nullptr; - } - - return reinterpret_cast(transform_property.GetRaw(game_object)); + return game_object ? game_object->Get<"transform", Transform*>() : nullptr; } } // namespace @@ -60,13 +46,9 @@ void ApplyUiShipScaleToLoadedShips(float old_multiplier, float new_multiplier) continue; } - auto* local_scale = transform->localScale; - if (local_scale == nullptr) { - continue; - } - - Vector3 adjusted_scale{local_scale->x * ratio, local_scale->y * ratio, local_scale->z * ratio}; - transform->localScale = &adjusted_scale; + auto local_scale = transform->localScale(); + Vector3 adjusted_scale{local_scale.x * ratio, local_scale.y * ratio, local_scale.z * ratio}; + transform->SetLocalScale(adjusted_scale); } } @@ -92,8 +74,8 @@ void ScreenManager_UpdateCanvasRootScaleFactor_Hook(auto original, ScreenManager #endif if (Config::Get().ui_scale != 0.0f) { - static auto get_height_method = il2cpp_resolve_icall_typed("UnityEngine.Screen::get_height()"); - static auto get_width_method = il2cpp_resolve_icall_typed("UnityEngine.Screen::get_width()"); + static auto get_height_method = il2cpp::ResolveICall("UnityEngine.Screen::get_height()"); + static auto get_width_method = il2cpp::ResolveICall("UnityEngine.Screen::get_width()"); static auto ref_height = 1080; static auto ref_width = 1920; @@ -114,79 +96,34 @@ void ScreenManager_UpdateCanvasRootScaleFactor_Hook(auto original, ScreenManager } n = std::clamp(n, 0.1f, 5.0f); - _this->m_canvasRootScaler->scaleFactor = n; + // Property setter, not the managed SetScaleFactor method; the scaler applies it on its next Update. + if (auto* scaler = _this->canvasRootScaler()) + scaler->set_scaleFactor(n); } } void CanvasController_Show(auto original, CanvasController* _this, int desiredEntryPoint, bool instant) { const auto ui_scale_viewer = Config::Get().ui_scale_viewer; - if (_this && ui_scale_viewer != 0.0f && to_wstring(_this->name) == L"ObjectViewerTemplate_Canvas") { - auto transform = _this->transform; - if (transform == nullptr) { - return original(_this, desiredEntryPoint, instant); - } - auto localScale = transform->localScale; - if (localScale == nullptr) { - return original(_this, desiredEntryPoint, instant); - } - localScale->x = ui_scale_viewer; - localScale->y = ui_scale_viewer; - localScale->z = ui_scale_viewer; - transform->localScale = localScale; + if (_this && ui_scale_viewer != 0.0f && to_wstring(_this->name()) == L"ObjectViewerTemplate_Canvas") { + if (auto* transform = _this->transform()) + transform->SetLocalScale(Vector3{ui_scale_viewer, ui_scale_viewer, ui_scale_viewer}); } return original(_this, desiredEntryPoint, instant); } void InstallUiScaleHooks() { - auto system_view_ship = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "SystemViewShip"); - if (!system_view_ship.isValidHelper()) { - ErrorMsg::MissingHelper("Navigation", "SystemViewShip"); - } else if (auto try_get_ship_asset = system_view_ship.GetMethod("TryGetShipAssetBundleResource", 2); - try_get_ship_asset == nullptr) { - ErrorMsg::MissingMethod("SystemViewShip", "TryGetShipAssetBundleResource"); - } else { - SPUD_STATIC_DETOUR(try_get_ship_asset, SystemViewShip_TryGetShipAssetBundleResource_Hook); - } + using SystemViewShip = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "SystemViewShip">; - auto screen_manager_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "ScreenManager"); - if (!screen_manager_helper.isValidHelper()) { - ErrorMsg::MissingHelper("UI", "ScreenManager"); - } else { - auto ptr_update_scale = screen_manager_helper.GetMethod("UpdateCanvasRootScaleFactor"); - if (ptr_update_scale == nullptr) { - ErrorMsg::MissingMethod("ScreenManager", "UpdateCanvasRootScaleFactor"); - } else { - SPUD_STATIC_DETOUR(ptr_update_scale, ScreenManager_UpdateCanvasRootScaleFactor_Hook); - } - } + if (auto method = SystemViewShip::Method("TryGetShipAssetBundleResource", 2)) + SPUD_STATIC_DETOUR(method.Pointer(), SystemViewShip_TryGetShipAssetBundleResource_Hook); - auto canvas_controller_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "CanvasController"); - if (!canvas_controller_helper.isValidHelper()) { - ErrorMsg::MissingHelper("UI", "CanvasController"); - } else { - auto ptr_canvas_show = canvas_controller_helper.GetMethodSpecial("Show", [](auto count, const Il2CppType** params) { - if (count != 2) { - return false; - } - - auto p1 = params[0]->type; - auto p2 = params[1]->type; - - if (p1 == IL2CPP_TYPE_I4 && p2 == IL2CPP_TYPE_BOOLEAN) { - return true; - } - return false; - }); - - if (ptr_canvas_show == nullptr) { - ErrorMsg::MissingMethod("CanvasController", "Show"); - } else { - SPUD_STATIC_DETOUR(ptr_canvas_show, CanvasController_Show); - } - } + if (auto method = ScreenManager::Class::Method("UpdateCanvasRootScaleFactor")) + SPUD_STATIC_DETOUR(method.Pointer(), ScreenManager_UpdateCanvasRootScaleFactor_Hook); + + if (auto method = CanvasController::Class::Method("Show", {"System.Int32", "System.Boolean"})) + SPUD_STATIC_DETOUR(method.Pointer(), CanvasController_Show); Config::RefreshDPI(); } diff --git a/mods/src/patches/parts/warp_action_label.cc b/mods/src/patches/parts/warp_action_label.cc index abb934e9c..3a01dd54b 100644 --- a/mods/src/patches/parts/warp_action_label.cc +++ b/mods/src/patches/parts/warp_action_label.cc @@ -1,13 +1,14 @@ #include "config.h" -#include "errormsg.h" +#include "patches/object_tracker.h" #include "patches/instant_warp_policy.h" #include "patches/screen_update_hook.h" #include "prime/DeploymentManager.h" #include "prime/FleetsManager.h" #include "prime/StarNodeObjectViewerWidget.h" +#include "prime/TextLocalizer.h" #include "str_utils.h" -#include +#include #include #include @@ -17,49 +18,40 @@ namespace { // This is a client of the existing frame dispatcher, not another UI detour. // Never mutate the button context, timer, resource cost, or click handler. -bool Alive(Il2CppObject* object) +// Unity's `bool(UnityEngine.Object)`: false for null and for destroyed native objects. +bool Alive(il2cpp::Object* object) { - if (!object) - return false; - static auto helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Object"); - static auto* method = helper.GetMethodInfo("op_Implicit", 1); - if (!method) - return false; - void* args[] = {object}; - Il2CppException* exception = nullptr; - auto* result = il2cpp_runtime_invoke(method, nullptr, args, &exception); - return !exception && Il2CppChecked::Boolean(result); + using UnityObject = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Object">; + return object && UnityObject::Call<"op_Implicit", bool>(object); } -std::string CurrentText(Il2CppObject* label) +std::string CurrentText(TextLocalizer* label) { - auto* text = Il2CppChecked::Invoke(label, "CurrentText"); - if (!text || il2cpp_class_get_type(text->klass)->type != IL2CPP_TYPE_STRING) + auto* text = label->CurrentText(); + if (!text) throw std::runtime_error("expected label text"); - return to_string(reinterpret_cast(text)); + return to_string(text); } struct OwnedLabel { - Il2CppGCHandle handle = nullptr; - std::string text; + il2cpp::GcHandle handle; + std::string text; - Il2CppObject* Get() const - { return handle ? il2cpp_gchandle_get_target(handle) : nullptr; } + TextLocalizer* Get() const + { return handle.Get(); } void Clear() { auto* label = Get(); auto previous = std::move(text); - if (handle) - il2cpp_gchandle_free(handle); - handle = nullptr; + handle.Reset(); text.clear(); // Forget ownership even if native cleanup fails. - if (Alive(label) && Il2CppChecked::BooleanField(label, "_textOverride") && CurrentText(label) == previous) - Il2CppChecked::Invoke(label, "ClearTextOverride"); + if (Alive(label) && label->textOverride() && CurrentText(label) == previous) + label->ClearTextOverride(); } - void Apply(Il2CppObject* label, const char* desired) + void Apply(TextLocalizer* label, const char* desired) { if (!label || !desired) { Clear(); @@ -68,24 +60,20 @@ struct OwnedLabel { if (Get() != label) { Clear(); // Don't take ownership from an existing text override. - if (Il2CppChecked::BooleanField(label, "_textOverride")) + if (label->textOverride()) return; - handle = il2cpp_gchandle_new_weakref(label, false); + handle = il2cpp::GcHandle::Weak(label); if (!handle) return; } const auto current = CurrentText(label); - if (!text.empty() && Il2CppChecked::BooleanField(label, "_textOverride") && current != text) { + if (!text.empty() && label->textOverride() && current != text) { Clear(); return; } - if (text == desired && Il2CppChecked::BooleanField(label, "_textOverride") && current == desired) + if (text == desired && label->textOverride() && current == desired) return; - auto* value = il2cpp_string_new(desired); - if (!value) - return; - void* args[] = {value}; - Il2CppChecked::Invoke(label, "OverrideLocalizedText", 1, args); + label->OverrideLocalizedText(il2cpp_string_new(desired)); text = desired; } }; @@ -99,25 +87,20 @@ void Update() return; next = now + std::chrono::milliseconds(100); try { - auto* widget = reinterpret_cast(ObjectFinder::Get()); - const bool active = - Alive(widget) && Il2CppChecked::Boolean(Il2CppChecked::Invoke(widget, "get_isActiveAndEnabled")); - auto* button = active ? Il2CppChecked::ReferenceField(widget, "_visitButton", "GenericButtonWidget") : nullptr; - const bool enabled = Alive(button) - && Il2CppChecked::Boolean(Il2CppChecked::Invoke(button, "get_isActiveAndEnabled")) - && Il2CppChecked::Boolean(Il2CppChecked::Invoke(button, "get_Interactable")); + auto* widget = ObjectFinder::Get(); + const bool active = Alive(widget) && widget->isActiveAndEnabled(); + auto* button = active ? widget->visitButton() : nullptr; + const bool enabled = Alive(button) && button->isActiveAndEnabled() && button->Interactable(); const char* desired = nullptr; // The ordinary visit path uses the currently selected fleet. The card's // cached popup CourseData is populated on click, so it is not a preview source. // Toll/wormhole buttons have separate confirmation paths and retain native text. if (enabled) { - auto* fleets = reinterpret_cast(FleetsManager::Instance()); - auto* deployment = reinterpret_cast(DeploymentManger::Instance()); - auto* fleet = Alive(fleets) ? Il2CppChecked::Invoke(fleets, "GetSelectedFleetData") : nullptr; - void* args[] = {fleet}; - if (fleet && Alive(deployment) - && Il2CppChecked::Boolean(Il2CppChecked::Invoke(deployment, "HasInstantWarpAbility", 1, args))) { - switch (ResolveInstantWarpConfirmation(reinterpret_cast(fleet))) { + auto* fleets = FleetsManager::Instance(); + auto* deployment = DeploymentManger::Instance(); + auto* fleet = Alive(fleets) ? fleets->GetSelectedFleetData() : nullptr; + if (fleet && Alive(deployment) && deployment->HasInstantWarpAbility(fleet)) { + switch (ResolveInstantWarpConfirmation(fleet)) { case InstantWarpConfirmation::Warp: desired = "WARP"; break; @@ -129,7 +112,7 @@ void Update() } } } - auto* label = desired ? Il2CppChecked::ReferenceField(button, "_mainText", "TextLocalizer") : nullptr; + auto* label = desired ? button->mainText() : nullptr; action_label.Apply(Alive(label) ? label : nullptr, desired); } catch (const std::exception& error) { static bool warned = false; diff --git a/mods/src/patches/parts/zoom.cc b/mods/src/patches/parts/zoom.cc index 2fb7af700..49bc8fb6f 100644 --- a/mods/src/patches/parts/zoom.cc +++ b/mods/src/patches/parts/zoom.cc @@ -1,10 +1,8 @@ #include "config.h" -#include "errormsg.h" #include -#include -#include +#include #include #include @@ -52,17 +50,13 @@ const FleetLabelProfile *FleetLabelProfileFor(NavigationFleetWidget *widget) } const auto &config = Config::Get(); - auto *context = widget->Context; + auto *context = widget->Context(); if (context == nullptr) { return &config.zoom_label_non_player; } - DeployedFleetType fleet_type; - if (!context->TryGetFleetType(fleet_type)) { - return &config.zoom_label_non_player; - } - - switch (fleet_type) { + // A missing FleetType property reads as Nonexistent. + switch (context->FleetType()) { case DeployedFleetType::Player: return &config.zoom_label_player; case DeployedFleetType::Marauder: @@ -80,7 +74,7 @@ const FleetLabelProfile *FleetLabelProfileFor(NavigationFleetWidget *widget) const FleetLabelProfile *FleetLabelProfileFor(NavigationLOD *lod) { const auto widget = fleet_label_widgets.find(lod); - if (widget == fleet_label_widgets.end() || widget->second == nullptr || widget->second->Context == nullptr) { + if (widget == fleet_label_widgets.end() || widget->second == nullptr || widget->second->Context() == nullptr) { return nullptr; } return FleetLabelProfileFor(widget->second); @@ -197,24 +191,15 @@ void UpdateFleetLabelThreshold() } } // namespace -vec3 GetMouseWorldPos(void *cam, vec3 *pos) +vec3 GetMouseWorldPos(Camera *cam, vec3 *pos) { - static auto class_helper = il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.Client.Core", "MathUtils"); - static auto fn = class_helper.GetMethodInfo("GetMouseWorldPos"); - - if (fn == nullptr || cam == nullptr || pos == nullptr) { - return {0.0f, 0.0f, 0.0f}; - } + using MathUtils = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.Client.Core", "MathUtils">; - void *args[2] = {cam, (void *)pos}; - Il2CppException *exception = nullptr; - auto result = il2cpp_runtime_invoke(fn, nullptr, args, &exception); - if (exception != nullptr || result == nullptr) { + if (cam == nullptr || pos == nullptr) { return {0.0f, 0.0f, 0.0f}; } - auto unboxed = il2cpp_object_unbox(result); - return unboxed != nullptr ? *reinterpret_cast(unboxed) : vec3{0.0f, 0.0f, 0.0f}; + return MathUtils::Call<"GetMouseWorldPos", vec3>(cam, *pos); } auto do_default_zoom = false; @@ -222,12 +207,11 @@ auto do_default_zoom = false; inline void StoreZoom(std::string label, float &zoom, NavigationZoom *_this) { auto old_zoom = zoom; - zoom = (_this->Distance - _this->_minimum) / (_this->_maximum - _this->_minimum) * Config::Get().zoom; + zoom = (_this->Distance() - _this->minimum()) / (_this->maximum() - _this->minimum()) * Config::Get().zoom; spdlog::info("Changing {} from {} to {}", label, old_zoom, zoom); } static float s_expectedScale = 0; -static uint64_t s_backdropLoadRevision = 0; static void ApplySystemZoomRange(NavigationZoom *_this, float radius) { @@ -235,9 +219,9 @@ static void ApplySystemZoomRange(NavigationZoom *_this, float radius) return; } - auto ratio = (Config::Get().zoom / radius); - _this->_farRatioSystemNormal = 0.55f * ratio; - _this->_farRatioSystemExtended = ratio; + auto ratio = (Config::Get().zoom / radius); + _this->SetFarRatioSystemNormal(0.55f * ratio); + _this->SetFarRatioSystemExtended(ratio); } static void SetSceneCameraFarClip(NavigationZoom *_this) @@ -246,19 +230,19 @@ static void SetSceneCameraFarClip(NavigationZoom *_this) return; } - auto *cam = _this->_sceneCamera; + auto *cam = _this->sceneCamera(); if (!cam) { return; } - cam->farClipPlane = Config::Get().zoom * 3.75f; - cam->clearFlags = 2; - cam->backgroundColor = {0, 0, 0, 0}; + cam->SetFarClipPlane(Config::Get().zoom * 3.75f); + cam->SetClearFlags(2); + cam->SetBackgroundColor({0, 0, 0, 0}); } static void EnsureSystemZoomRange(NavigationZoom *_this) { - if (!_this || _this->_depth != NodeDepth::SolarSystem) { + if (!_this || _this->depth() != NodeDepth::SolarSystem) { return; } @@ -267,20 +251,20 @@ static void EnsureSystemZoomRange(NavigationZoom *_this) return; } - ApplySystemZoomRange(_this, _this->_viewRadius); - if (_this->_maximum < max_zoom) { - _this->_maximum = max_zoom; + ApplySystemZoomRange(_this, _this->viewRadius()); + if (_this->maximum() < max_zoom) { + _this->SetMaximum(max_zoom); } - const auto zoom_total = _this->_maximum - _this->_minimum; + const auto zoom_total = _this->maximum() - _this->minimum(); if (zoom_total > 0.0f) { - _this->_zoomtotal = zoom_total; + _this->SetZoomtotal(zoom_total); } SetSceneCameraFarClip(_this); } -static void ScaleFR(void *fr) +static void ScaleFR(il2cpp::Object *fr) { if (!fr) { return; @@ -291,40 +275,31 @@ static void ScaleFR(void *fr) return; } - static auto comp_helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Component"); - if (!comp_helper.isValidHelper()) { - return; - } - - static auto get_transform = comp_helper.GetProperty("transform"); - if (!get_transform.isValidHelper()) { - return; - } - - auto *t = reinterpret_cast(get_transform.GetRaw(fr)); + // FlatRenderable is a Component; transform resolves on its runtime class. + auto *t = fr->Get<"transform", Transform *>(); if (t == nullptr) { return; } - auto *scale = t->localScale; - if (scale == nullptr || (s_expectedScale > 0 && fabsf(scale->x - s_expectedScale) < 0.1f)) { + auto scale = t->localScale(); + if (s_expectedScale > 0 && fabsf(scale.x - s_expectedScale) < 0.1f) { return; } - Vector3 newScale = {scale->x * factor, scale->y * factor, scale->z * factor}; - t->localScale = &newScale; + Vector3 newScale = {scale.x * factor, scale.y * factor, scale.z * factor}; + t->SetLocalScale(newScale); s_expectedScale = newScale.x; } void NavigationZoom_Update_Hook(auto original, NavigationZoom *_this) { static auto GetMousePosition = - il2cpp_resolve_icall_typed("UnityEngine.Input::get_mousePosition_Injected(UnityEngine.Vector3&)"); - static auto GetDeltaTime = il2cpp_resolve_icall_typed("UnityEngine.Time::get_deltaTime()"); + il2cpp::ResolveICall("UnityEngine.Input::get_mousePosition_Injected(UnityEngine.Vector3&)"); + static auto GetDeltaTime = il2cpp::ResolveICall("UnityEngine.Time::get_deltaTime()"); if (fleet_label_hooks_installed) { const auto zoom_id = reinterpret_cast(_this); - if (_this->_depth == NodeDepth::SolarSystem) { + if (_this->depth() == NodeDepth::SolarSystem) { BeginFleetLabelSystemState(_this); } else if (active_system_zoom_id == zoom_id) { ResetFleetLabelSystemState(); @@ -400,25 +375,23 @@ void NavigationZoom_Update_Hook(auto original, NavigationZoom *_this) if ((camera_shortcuts_enabled && MapKey::IsPressed(GameFunction::ZoomIn)) || do_absolute_zoom) { vec3 mousePos; GetMousePosition(&mousePos); - _this->_zoomLocation = vec2{.x = mousePos.x, .y = mousePos.y}; + _this->SetZoomLocation(vec2{.x = mousePos.x, .y = mousePos.y}); if (do_absolute_zoom) { - auto zoom_distance = _this->_minimum + (_this->_maximum - _this->_minimum) * (zoomDelta / config->zoom); - _this->Distance = zoom_distance; + auto zoom_distance = _this->minimum() + (_this->maximum() - _this->minimum()) * (zoomDelta / config->zoom); + _this->SetDistance(zoom_distance); } else { - _this->_zoomDelta = zoomDelta; - _this->_lastZoomDelta = zoomDelta; + _this->SetZoomDelta(zoomDelta); + _this->SetLastZoomDelta(zoomDelta); } - auto worldPos = GetMouseWorldPos(_this->_sceneCamera, &mousePos); - _this->_worldPoint = worldPos; + _this->SetWorldPoint(GetMouseWorldPos(_this->sceneCamera(), &mousePos)); _this->ZoomCameraAtWorldPoint(); } else if (camera_shortcuts_enabled && MapKey::IsPressed(GameFunction::ZoomOut)) { vec3 mousePos; GetMousePosition(&mousePos); - _this->_zoomLocation = vec2{.x = mousePos.x, .y = mousePos.y}; - _this->_zoomDelta = -1.0f * zoomDelta; - _this->_lastZoomDelta = -1.0f * zoomDelta; - auto worldPos = GetMouseWorldPos(_this->_sceneCamera, &mousePos); - _this->_worldPoint = worldPos; + _this->SetZoomLocation(vec2{.x = mousePos.x, .y = mousePos.y}); + _this->SetZoomDelta(-1.0f * zoomDelta); + _this->SetLastZoomDelta(-1.0f * zoomDelta); + _this->SetWorldPoint(GetMouseWorldPos(_this->sceneCamera(), &mousePos)); _this->ZoomCameraAtWorldPoint(); } } @@ -432,13 +405,13 @@ void NavigationZoom_Update_Hook(auto original, NavigationZoom *_this) original(_this); EnsureSystemZoomRange(_this); - if (fleet_label_hooks_installed && _this->_depth == NodeDepth::SolarSystem) { + if (fleet_label_hooks_installed && _this->depth() == NodeDepth::SolarSystem) { BeginFleetLabelSystemState(_this); const auto state_was_valid = system_zoom_state_valid; const auto threshold_enabled = FleetLabelThresholdEnabled(); - system_zoom_level = _this->_zoomLevel; + system_zoom_level = _this->zoomLevel(); if (threshold_enabled) { - system_normalized_zoom = _this->NormalizedZoom; + system_normalized_zoom = _this->NormalizedZoom(); } system_zoom_state_valid = true; if (threshold_enabled) { @@ -471,11 +444,11 @@ void NavigationFleetWidget_OnEnable_Hook(auto original, NavigationFleetWidget *_ // Fresh pooled widgets are enabled before BindDataContext assigns m_context. Forcing an LOD in that window can fire // OnLODChanged callbacks that dereference the not-yet-bound fleet data. - if (_this->Context == nullptr) { + if (_this->Context() == nullptr) { return; } - auto *lod = _this->_lod; + auto *lod = _this->lod(); if (lod) { fleet_label_widgets.insert_or_assign(lod, _this); if (FleetLabelThresholdEnabled() && !system_zoom_state_valid) { @@ -488,9 +461,9 @@ void NavigationFleetWidget_OnEnable_Hook(auto original, NavigationFleetWidget *_ void NavigationFleetWidget_OnDisable_Hook(auto original, NavigationFleetWidget *_this) { if (_this) { - auto *lod = _this->_lod; + auto *lod = _this->lod(); fleet_label_widgets.erase(lod); - if (_this->Context != nullptr) { + if (_this->Context() != nullptr) { RestoreNativeFleetLabelDetail(lod); } } @@ -504,7 +477,7 @@ void NavigationFleetWidget_OnDidBindContext_Hook(auto original, NavigationFleetW return; } - auto *lod = _this->_lod; + auto *lod = _this->lod(); if (lod != nullptr) { fleet_label_widgets.insert_or_assign(lod, _this); ApplyFleetLabelDetail(lod, system_zoom_level); @@ -514,10 +487,10 @@ void NavigationFleetWidget_OnDidBindContext_Hook(auto original, NavigationFleetW void NavigationFleetWidget_OnAboutToReleaseContext_Hook(auto original, NavigationFleetWidget *_this) { if (_this != nullptr) { - auto *lod = _this->_lod; + auto *lod = _this->lod(); if (lod != nullptr) { fleet_label_widgets.erase(lod); - if (_this->Context != nullptr) { + if (_this->Context() != nullptr) { RestoreNativeFleetLabelDetail(lod); } } @@ -536,49 +509,6 @@ void PlanetViewUtils_CameraZoomedEventHandler_Hook(auto original, PlanetViewUtil } } -void PlanetViewUtils_CreateBackdrop_Hook(auto original, PlanetViewUtils *_this, Il2CppObject *backdrop, - Il2CppObject *resource, bool usingDefault) -{ - original(_this, backdrop, resource, usingDefault); - // Native creation has now assigned the selected scenery's authored transform. - // Reuse the existing getter/scaling path before any player zoom event. - if (_this && resource) { - ++s_backdropLoadRevision; - // A newly authored scale must not be compared with the previous system. - s_expectedScale = 0; - _this->GetFlatRenderable(); - } -} - -void PlanetViewUtils_CreateBackdropCollection_Hook(auto original, PlanetViewUtils *_this, Il2CppObject *backdrop, - Il2CppObject *resource, bool usingDefault) -{ - original(_this, backdrop, resource, usingDefault); - // The collection overload can load scenery without the Object callback. - if (_this && resource) { - ++s_backdropLoadRevision; - s_expectedScale = 0; - _this->GetFlatRenderable(); - } -} - -void PlanetViewUtils_Populate_Hook(auto original, PlanetViewUtils *_this, Il2CppObject *system, int64_t nodeId, - NodeDepth depth) -{ - auto *previous_population = _this ? _this->_popData : nullptr; - const auto load_revision = s_backdropLoadRevision; - original(_this, system, nodeId, depth); - - // Cached variations reload their authored transforms in Populate rather than CreateBackdrop. - // A cached prefab backdrop already retains its scaled transform and must not be scaled again. - auto *population = _this ? _this->_popData : nullptr; - if (depth == NodeDepth::SolarSystem && population && population != previous_population - && population->VariationCollection && load_revision == s_backdropLoadRevision) { - s_expectedScale = 0; - _this->GetFlatRenderable(); - } -} - void NavigationZoom_SetViewParameters_Hook(auto original, NavigationZoom *_this, float radius, NodeDepth depth) { if (fleet_label_hooks_installed && depth != NodeDepth::SolarSystem) { @@ -613,7 +543,7 @@ void NavigationZoom_SetDepth_Hook(auto original, NavigationZoom *_this, NodeDept } if (depth == NodeDepth::SolarSystem) { - ApplySystemZoomRange(_this, _this->_viewRadius); + ApplySystemZoomRange(_this, _this->viewRadius()); SetSceneCameraFarClip(_this); original(_this, depth); @@ -632,182 +562,48 @@ void *PlanetViewUtils_get_FlatRenderable_Hook(auto original, PlanetViewUtils *_t return fr; } - ScaleFR(fr); + ScaleFR(static_cast(fr)); return fr; } void InstallZoomHooks() { - auto navigation_zoom_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationZoom"); - auto ptr_update = navigation_zoom_helper.isValidHelper() ? navigation_zoom_helper.GetMethod("Update") : nullptr; - auto *navigation_zoom_class = navigation_zoom_helper.get_cls(); - auto *zoom_level_field = navigation_zoom_class != nullptr - ? il2cpp_class_get_field_from_name(navigation_zoom_class, "_zoomLevel") - : nullptr; - auto *normalized_zoom_property = navigation_zoom_class != nullptr - ? il2cpp_class_get_property_from_name(navigation_zoom_class, "NormalizedZoom") - : nullptr; - if (FleetLabelProfilesEnabled()) { - auto lod_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationLOD"); - auto fleet_widget_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationFleetWidget"); - auto fleet_data_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "FleetDeployedData"); - auto ptr_update_lod = lod_helper.isValidHelper() ? lod_helper.GetMethod("UpdateLOD") : nullptr; - auto ptr_on_enable = fleet_widget_helper.isValidHelper() ? fleet_widget_helper.GetMethod("OnEnable") : nullptr; - auto ptr_on_disable = fleet_widget_helper.isValidHelper() ? fleet_widget_helper.GetMethod("OnDisable") : nullptr; - auto ptr_on_did_bind_context = - fleet_widget_helper.isValidHelper() ? fleet_widget_helper.GetMethod("OnDidBindContext") : nullptr; - auto ptr_on_about_to_release_context = - fleet_widget_helper.isValidHelper() ? fleet_widget_helper.GetMethod("OnAboutToReleaseContext") : nullptr; - auto *lod_class = lod_helper.get_cls(); - auto *target_level_field = - lod_class != nullptr ? il2cpp_class_get_field_from_name(lod_class, "k__BackingField") : nullptr; - auto *fleet_widget_class = fleet_widget_helper.get_cls(); - auto *lod_field = - fleet_widget_class != nullptr ? il2cpp_class_get_field_from_name(fleet_widget_class, "_lod") : nullptr; - auto *context_field = NavigationFleetWidget::ContextField(); - auto *fleet_data_class = fleet_data_helper.get_cls(); - auto *fleet_type_property = - fleet_data_class != nullptr ? il2cpp_class_get_property_from_name(fleet_data_class, "FleetType") : nullptr; - auto *fleet_type_getter = - fleet_type_property != nullptr ? il2cpp_property_get_get_method((PropertyInfo *)fleet_type_property) : nullptr; - - if (!lod_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Navigation", "NavigationLOD"); - } else { - if (ptr_update_lod == nullptr) { - ErrorMsg::MissingMethod("NavigationLOD", "UpdateLOD"); - } - if (target_level_field == nullptr) { - ErrorMsg::MissingMethod("NavigationLOD", "k__BackingField"); - } - } - if (!fleet_widget_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Navigation", "NavigationFleetWidget"); - } else { - if (ptr_on_enable == nullptr) { - ErrorMsg::MissingMethod("NavigationFleetWidget", "OnEnable"); - } - if (ptr_on_disable == nullptr) { - ErrorMsg::MissingMethod("NavigationFleetWidget", "OnDisable"); - } - if (ptr_on_did_bind_context == nullptr) { - ErrorMsg::MissingMethod("NavigationFleetWidget", "OnDidBindContext"); - } - if (ptr_on_about_to_release_context == nullptr) { - ErrorMsg::MissingMethod("NavigationFleetWidget", "OnAboutToReleaseContext"); - } - if (lod_field == nullptr) { - ErrorMsg::MissingMethod("NavigationFleetWidget", "_lod"); - } - if (context_field == nullptr) { - ErrorMsg::MissingMethod("NavigationFleetWidget", "m_context"); - } - } - if (!fleet_data_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Digit.PrimeServer.Models", "FleetDeployedData"); - } else if (fleet_type_property == nullptr || fleet_type_getter == nullptr) { - ErrorMsg::MissingMethod("FleetDeployedData", "FleetType"); - } - if (!navigation_zoom_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Navigation", "NavigationZoom"); - } else { - if (ptr_update == nullptr) { - ErrorMsg::MissingMethod("NavigationZoom", "Update"); - } - if (zoom_level_field == nullptr) { - ErrorMsg::MissingMethod("NavigationZoom", "_zoomLevel"); - } - if (normalized_zoom_property == nullptr) { - ErrorMsg::MissingMethod("NavigationZoom", "NormalizedZoom"); - } - } + auto update = NavigationZoom::Class::Method("Update"); - const auto fleet_label_dependencies_valid = - lod_helper.isValidHelper() && ptr_update_lod != nullptr && target_level_field != nullptr - && fleet_widget_helper.isValidHelper() && ptr_on_enable != nullptr && ptr_on_disable != nullptr - && ptr_on_did_bind_context != nullptr && ptr_on_about_to_release_context != nullptr && lod_field != nullptr - && context_field != nullptr && fleet_data_helper.isValidHelper() && fleet_type_property != nullptr - && fleet_type_getter != nullptr && navigation_zoom_helper.isValidHelper() && ptr_update != nullptr - && zoom_level_field != nullptr && normalized_zoom_property != nullptr; - if (fleet_label_dependencies_valid) { + if (FleetLabelProfilesEnabled()) { + // Installed as a set. Missing fields and properties (_lod, Context, FleetType) read as null or zero, which the hooks + // treat as native labels, so only the methods gate installation. + auto update_lod = NavigationLOD::Class::Method("UpdateLOD"); + auto on_enable = NavigationFleetWidget::Class::Method("OnEnable"); + auto on_disable = NavigationFleetWidget::Class::Method("OnDisable"); + auto on_did_bind_context = NavigationFleetWidget::Class::Method("OnDidBindContext"); + auto on_about_to_release_context = NavigationFleetWidget::Class::Method("OnAboutToReleaseContext"); + + if (update && update_lod && on_enable && on_disable && on_did_bind_context && on_about_to_release_context) { fleet_label_hooks_installed = true; - SPUD_STATIC_DETOUR(ptr_update_lod, NavigationLOD_UpdateLOD_Hook); - SPUD_STATIC_DETOUR(ptr_on_enable, NavigationFleetWidget_OnEnable_Hook); - SPUD_STATIC_DETOUR(ptr_on_disable, NavigationFleetWidget_OnDisable_Hook); - SPUD_STATIC_DETOUR(ptr_on_did_bind_context, NavigationFleetWidget_OnDidBindContext_Hook); - SPUD_STATIC_DETOUR(ptr_on_about_to_release_context, NavigationFleetWidget_OnAboutToReleaseContext_Hook); + SPUD_STATIC_DETOUR(update_lod.Pointer(), NavigationLOD_UpdateLOD_Hook); + SPUD_STATIC_DETOUR(on_enable.Pointer(), NavigationFleetWidget_OnEnable_Hook); + SPUD_STATIC_DETOUR(on_disable.Pointer(), NavigationFleetWidget_OnDisable_Hook); + SPUD_STATIC_DETOUR(on_did_bind_context.Pointer(), NavigationFleetWidget_OnDidBindContext_Hook); + SPUD_STATIC_DETOUR(on_about_to_release_context.Pointer(), NavigationFleetWidget_OnAboutToReleaseContext_Hook); } else { spdlog::error("Fleet label detail hooks were not installed; using native fleet labels"); } } - { - auto pv_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "PlanetViewUtils"); - if (pv_helper.isValidHelper()) { - auto ptr_populate = pv_helper.GetMethod("Populate"); - if (ptr_populate) - SPUD_STATIC_DETOUR(ptr_populate, PlanetViewUtils_Populate_Hook); - else - ErrorMsg::MissingMethod("PlanetViewUtils", "Populate"); - - const auto *create = method_contract::Resolve( - pv_helper.get_cls(), "CreateBackdrop", false, "System.Void", - {"Digit.PrimeServer.Models.ITreeNode", "UnityEngine.Object", "System.Boolean"}); - const auto *create_collection = method_contract::Resolve( - pv_helper.get_cls(), "CreateBackdrop", false, "System.Void", - {"Digit.PrimeServer.Models.ITreeNode", "SystemVariationConfigCollection", "System.Boolean"}); - if (create) - SPUD_STATIC_DETOUR(create->methodPointer, PlanetViewUtils_CreateBackdrop_Hook); - else - ErrorMsg::MissingMethod("PlanetViewUtils", "CreateBackdrop(Object)"); - if (create_collection) - SPUD_STATIC_DETOUR(create_collection->methodPointer, PlanetViewUtils_CreateBackdropCollection_Hook); - else - ErrorMsg::MissingMethod("PlanetViewUtils", "CreateBackdrop(SystemVariationConfigCollection)"); - - auto ptr_zoom = pv_helper.GetMethod("CameraZoomedEventHandler"); - if (ptr_zoom != nullptr) { - SPUD_STATIC_DETOUR(ptr_zoom, PlanetViewUtils_CameraZoomedEventHandler_Hook); - } else { - ErrorMsg::MissingMethod("PlanetViewUtils", "CameraZoomedEventHandler"); - } - - auto ptr_get_fr = pv_helper.GetMethod("get_FlatRenderable"); - if (ptr_get_fr != nullptr) { - SPUD_STATIC_DETOUR(ptr_get_fr, PlanetViewUtils_get_FlatRenderable_Hook); - } else { - ErrorMsg::MissingMethod("PlanetViewUtils", "get_FlatRenderable"); - } - } else { - ErrorMsg::MissingHelper("Navigation", "PlanetViewUtils"); - } - } + if (auto method = PlanetViewUtils::Class::Method("CameraZoomedEventHandler")) + SPUD_STATIC_DETOUR(method.Pointer(), PlanetViewUtils_CameraZoomedEventHandler_Hook); + if (auto method = PlanetViewUtils::Class::Method("get_FlatRenderable")) + SPUD_STATIC_DETOUR(method.Pointer(), PlanetViewUtils_get_FlatRenderable_Hook); - if (!navigation_zoom_helper.isValidHelper()) { - ErrorMsg::MissingHelper("Navigation", "NavigationZoom"); - } else { - if (ptr_update == nullptr) { - ErrorMsg::MissingMethod("NavigationZoom", "Update"); - } else { - SPUD_STATIC_DETOUR(ptr_update, NavigationZoom_Update_Hook); - } + if (update) + SPUD_STATIC_DETOUR(update.Pointer(), NavigationZoom_Update_Hook); #if _WIN32 - auto ptr_set_depth = navigation_zoom_helper.GetMethod("SetDepth"); - if (ptr_set_depth == nullptr) { - ErrorMsg::MissingMethod("NavigationZoom", "SetDepth"); - } else { - SPUD_STATIC_DETOUR(ptr_set_depth, NavigationZoom_SetDepth_Hook); - } + if (auto method = NavigationZoom::Class::Method("SetDepth")) + SPUD_STATIC_DETOUR(method.Pointer(), NavigationZoom_SetDepth_Hook); #endif - auto ptr_set_view_parameters = navigation_zoom_helper.GetMethod("SetViewParameters"); - if (ptr_set_view_parameters == nullptr) { - ErrorMsg::MissingMethod("NavigationZoom", "SetViewParameters"); - } else { - SPUD_STATIC_DETOUR(ptr_set_view_parameters, NavigationZoom_SetViewParameters_Hook); - } - } + if (auto method = NavigationZoom::Class::Method("SetViewParameters")) + SPUD_STATIC_DETOUR(method.Pointer(), NavigationZoom_SetViewParameters_Hook); } diff --git a/mods/src/patches/patches.cc b/mods/src/patches/patches.cc index 7245542e6..05a5a815d 100644 --- a/mods/src/patches/patches.cc +++ b/mods/src/patches/patches.cc @@ -1,9 +1,9 @@ +#include #include "patches.h" #include "file.h" -#include "log_history.h" #include "version.h" -#include +#include #include @@ -82,12 +82,17 @@ __int64 il2cpp_init_hook(auto original, const char* domain_name) std::string rotation_error; try { - RotateLogHistory(File::Log()); + const auto backup = [](int index) { return std::filesystem::path(std::string(File::Log()) + "." + std::to_string(index)); }; + std::filesystem::remove(backup(5)); + for (int index = 4; index >= 1; --index) + if (std::filesystem::exists(backup(index))) + std::filesystem::rename(backup(index), backup(index + 1)); + if (std::filesystem::exists(File::Log())) + std::filesystem::rename(File::Log(), backup(1)); } catch (const std::filesystem::filesystem_error& error) { rotation_error = error.what(); } - - // Append if rotation failed so an existing log is never truncated. + // Append if rotation failed so an existing session log is not truncated. auto file_logger = spdlog::basic_logger_mt("default", File::Log(), false); auto sink = std::make_shared(); file_logger->sinks().push_back(sink); @@ -98,9 +103,9 @@ __int64 il2cpp_init_hook(auto original, const char* domain_name) spdlog::set_level(log_level); spdlog::flush_on(log_level); - if (!rotation_error.empty()) { + if (!rotation_error.empty()) spdlog::warn("Failed to rotate log history; preserving existing log: {}", rotation_error); - } + il2cpp::SetErrorHandler([](std::string_view message) { spdlog::error("{}", message); }); #if VERSION_PATCH if constexpr (sizeof(VERSION_COMMIT_HASH) > 1) { @@ -238,7 +243,6 @@ void ApplyPatches() printf("Loading %s\n", assembly_path); auto assembly = dlopen(assembly_path, RTLD_LAZY | RTLD_GLOBAL); - init_il2cpp_pointers(); #endif if (assembly == nullptr) { @@ -246,6 +250,7 @@ void ApplyPatches() return; } else { try { + il2cpp::Initialize(assembly); #if _WIN32 auto n = GetProcAddress(assembly, "il2cpp_init"); #else @@ -254,8 +259,8 @@ void ApplyPatches() printf("Got il2cpp_init %p\n", n); SPUD_STATIC_DETOUR(n, il2cpp_init_hook); - } catch (...) { - // Failed to Apply at least some patches + } catch (const std::exception& error) { + spdlog::error("Failed to initialize IL2CPP hooks: {}", error.what()); } } } diff --git a/mods/src/prime/ActionData.h b/mods/src/prime/ActionData.h index 851f564be..e7f7505a9 100644 --- a/mods/src/prime/ActionData.h +++ b/mods/src/prime/ActionData.h @@ -1,34 +1,64 @@ #pragma once -#include +#include using IActionData = void*; -enum class ActionType : int32_t { - Invalid = -1, - Upgrade = 1433481724, - Purchase = 1807968545, - Manage = -1997567611, - Harvest = -1933286583, - Repair = -1850668115, - View = 2666181, - Recall = -1851055311, - Promote = 1355442080, - Demote = 2043415476, - Move = 2404337, - Attack = 1971575400, - Assign = 1970629903, - Add = 65665, - Remove = -1850743644, - Claim = 65189916, - Chat = 2099064, - Help = 2245473, - Invite = -2099832023, - Report = -1850654380, - Copy = 2106261, - Share = 79847359 +// Digit.PrimeServer.Models.ActionType is a struct around a flag value, not an enum, so it crosses the +// managed boundary as a 4-byte struct. +struct ActionType { + int32_t flagValue; + + friend bool operator==(ActionType, ActionType) = default; + + static const ActionType Invalid; + static const ActionType Upgrade; + static const ActionType Purchase; + static const ActionType Manage; + static const ActionType Harvest; + static const ActionType Repair; + static const ActionType View; + static const ActionType Recall; + static const ActionType Promote; + static const ActionType Demote; + static const ActionType Move; + static const ActionType Attack; + static const ActionType Assign; + static const ActionType Add; + static const ActionType Remove; + static const ActionType Claim; + static const ActionType Chat; + static const ActionType Help; + static const ActionType Invite; + static const ActionType Report; + static const ActionType Copy; + static const ActionType Share; }; +inline constexpr ActionType ActionType::Invalid {-1}; +inline constexpr ActionType ActionType::Upgrade {1433481724}; +inline constexpr ActionType ActionType::Purchase{1807968545}; +inline constexpr ActionType ActionType::Manage {-1997567611}; +inline constexpr ActionType ActionType::Harvest {-1933286583}; +inline constexpr ActionType ActionType::Repair {-1850668115}; +inline constexpr ActionType ActionType::View {2666181}; +inline constexpr ActionType ActionType::Recall {-1851055311}; +inline constexpr ActionType ActionType::Promote {1355442080}; +inline constexpr ActionType ActionType::Demote {2043415476}; +inline constexpr ActionType ActionType::Move {2404337}; +inline constexpr ActionType ActionType::Attack {1971575400}; +inline constexpr ActionType ActionType::Assign {1970629903}; +inline constexpr ActionType ActionType::Add {65665}; +inline constexpr ActionType ActionType::Remove {-1850743644}; +inline constexpr ActionType ActionType::Claim {65189916}; +inline constexpr ActionType ActionType::Chat {2099064}; +inline constexpr ActionType ActionType::Help {2245473}; +inline constexpr ActionType ActionType::Invite {-2099832023}; +inline constexpr ActionType ActionType::Report {-1850654380}; +inline constexpr ActionType ActionType::Copy {2106261}; +inline constexpr ActionType ActionType::Share {79847359}; + + enum ActionBehaviour { Default = 1, Instant = 2, diff --git a/mods/src/prime/ActionQueueManager.h b/mods/src/prime/ActionQueueManager.h index 75c1565ea..1e9dae326 100644 --- a/mods/src/prime/ActionQueueManager.h +++ b/mods/src/prime/ActionQueueManager.h @@ -1,110 +1,25 @@ #pragma once -#include - #include "FleetPlayerData.h" #include "MonoSingleton.h" -#include "errormsg.h" + +#include struct ActionQueueManager : MonoSingleton { - friend struct MonoSingleton; + using Class = il2cpp::Class<"Assembly-CSharp", "Prime.ActionQueue", "ActionQueueManager">; -public: void ClearQueue(FleetPlayerData* playerData) - { - static auto ClearQueueMethod = - get_class_helper().GetMethod("ClearQueue"); - static auto CLearQueueWarn = true; - - if (ClearQueueMethod) { - ClearQueueMethod(this, playerData); - } else if (CLearQueueWarn) { - CLearQueueWarn = false; - ErrorMsg::MissingMethod("ActionQueueManager", "ClearQueue"); - } - } + { Call<"ClearQueue">(playerData); } bool IsQueueFull(FleetPlayerData* playerData) - { - static auto IsQueueFullMethod = - get_class_helper().GetMethod("IsQueueFull"); - static auto IsQueueFullWarn = true; - - if (IsQueueFullMethod) { - return IsQueueFullMethod(this, playerData); - } else if (IsQueueFullWarn) { - IsQueueFullWarn = false; - ErrorMsg::MissingMethod("ActionQueueManager", "IsQueueFull"); - } - - return false; - } - + { return Call<"IsQueueFull", bool>(playerData); } bool IsFleetInQueue(FleetPlayerData* playerData) - { - static auto IsFleetInQueueMethod = - get_class_helper().GetMethod("IsFleetInQueue"); - static auto IsFleetInQueueWarn = true; - - if (IsFleetInQueueMethod) { - return IsFleetInQueueMethod(this, playerData); - } else if (IsFleetInQueueWarn) { - IsFleetInQueueWarn = false; - ErrorMsg::MissingMethod("ActionQueueManager", "IsFleetInQueue"); - } - - return false; - } - + { return Call<"IsFleetInQueue", bool>(playerData); } bool IsQueueUnlocked() - { - static auto IsQueueUnlockedMethod = get_class_helper().GetMethod("IsQueueUnlocked"); - static auto IsQueueUnlockedWarn = true; - if (IsQueueUnlockedMethod) { - return IsQueueUnlockedMethod(this); - } else if (IsQueueUnlockedWarn) { - IsQueueUnlockedWarn = false; - ErrorMsg::MissingMethod("ActionQueueManager", "IsQueueUnlocked"); - } - - return false; - } + { return Call<"IsQueueUnlocked", bool>(); } bool CanAddToQueue(FleetPlayerData* playerData) - { - if (IsQueueUnlocked()) { - if (!IsQueueFull(playerData)) { - return true; - } - } - - return false; - } - - void AddToQueue(long targetId) - { - static auto AddToQueueMethod = get_class_helper().GetMethod("AddActionToQueue"); - static auto AddToQueueWarn = true; - - if (AddToQueueMethod != nullptr) { - spdlog::warn("AddToQueue({})", targetId); - AddToQueueMethod(this, targetId); - } else if (AddToQueueWarn) { - AddToQueueWarn = false; - ErrorMsg::MissingMethod("ActionQueueManager", "AddActionToQueue"); - } - } + { return IsQueueUnlocked() && !IsQueueFull(playerData); } -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Prime.ActionQueue", "ActionQueueManager"); - static auto class_warn = true; - if (!class_helper.isValidHelper()) { - if (class_warn) { - class_warn = false; - ErrorMsg::MissingHelper("ActionQueue", "ActionQueueManager"); - } - } - return class_helper; - } + void AddToQueue(int64_t targetId) + { Call<"AddActionToQueue">(targetId); } }; diff --git a/mods/src/prime/ActionRequirement.h b/mods/src/prime/ActionRequirement.h index 26607c579..7f6aa85af 100644 --- a/mods/src/prime/ActionRequirement.h +++ b/mods/src/prime/ActionRequirement.h @@ -1,29 +1,12 @@ #pragma once -#include +#include -template struct ActionRequirement { - -public: - __declspec(property(get = __get_IsMet)) bool IsMet; +struct ActionRequirement : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "ActionRequirement">; + bool IsMet() + { return Get<"IsMet", bool>(); } bool CheckIsMet() - { - static auto CheckIsMetMethod = get_class_helper().GetVirtualMethod("CheckIsMet"); - return CheckIsMetMethod(this); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static IL2CppClassHelper class_helper = Y::get_class_helper().GetParent("ActionRequirement"); - return class_helper; - } - -public: - bool __get_IsMet() - { - static auto field = get_class_helper().GetProperty("IsMet"); - return field.GetRaw(this); - } -}; \ No newline at end of file + { return Call<"CheckIsMet", bool>(); } +}; diff --git a/mods/src/prime/ActivatedAbilityManager.h b/mods/src/prime/ActivatedAbilityManager.h index 886bc4f59..630755604 100644 --- a/mods/src/prime/ActivatedAbilityManager.h +++ b/mods/src/prime/ActivatedAbilityManager.h @@ -1,45 +1,12 @@ #pragma once - -#include "errormsg.h" -#include - #include "MonoSingleton.h" +#include "FleetPlayerData.h" -// Digit.Prime.ActivatedAbilities.ActivatedAbilityManager : MonoSingleton struct ActivatedAbilityManager : MonoSingleton { - friend struct MonoSingleton; - -public: - // Digit.PrimeServer.Models.ActivatedAbilityType.RevealHostileVger — the type of the - // U.S.S. Excelsior's Decloaking Deflector, the only reveal ability this patch targets. + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ActivatedAbilities", "ActivatedAbilityManager">; static constexpr int32_t RevealHostileVgerAbilityType = 86002; - int32_t GetActivatedAbilityType(int64_t abilityId) - { - static auto method = - get_class_helper().GetMethod("GetActivatedAbilityType", 1); - static auto warn = true; - if (method) { - return method(this, abilityId); - } else if (warn) { - warn = false; - ErrorMsg::MissingMethod("ActivatedAbilityManager", "GetActivatedAbilityType"); - } - return 0; - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ActivatedAbilities", "ActivatedAbilityManager"); - static auto class_warn = true; - if (!class_helper.isValidHelper()) { - if (class_warn) { - class_warn = false; - ErrorMsg::MissingHelper("Digit.Prime.ActivatedAbilities", "ActivatedAbilityManager"); - } - } - return class_helper; - } + { return Call<"GetActivatedAbilityType", int32_t>(abilityId); } + bool CanUseAbility(FleetPlayerData* fleet, int64_t abilityId) + { return Call<"CanUseAbility", bool>(fleet, abilityId); } }; diff --git a/mods/src/prime/AllianceStarbaseObjectViewerWidget.h b/mods/src/prime/AllianceStarbaseObjectViewerWidget.h index 2963da5c8..fa6504a64 100644 --- a/mods/src/prime/AllianceStarbaseObjectViewerWidget.h +++ b/mods/src/prime/AllianceStarbaseObjectViewerWidget.h @@ -1,22 +1,7 @@ #pragma once -#include - -#include "Widget.h" - -#include "NavigationInteractionUIContext.h" #include "ObjectViewerBaseWidget.h" -#include "ScanEngageButtonsWidget.h" - -struct AllianceStarbaseObjectViewerWidget : public ObjectViewerBaseWidget { -public: - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "AllianceStarbaseObjectViewerWidget"); - return class_helper; - } +struct AllianceStarbaseObjectViewerWidget : ObjectViewerBaseWidget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "AllianceStarbaseObjectViewerWidget">; }; diff --git a/mods/src/prime/AnimatedRewardsScreenViewController.h b/mods/src/prime/AnimatedRewardsScreenViewController.h index 1cd7ff5d8..156f3155d 100644 --- a/mods/src/prime/AnimatedRewardsScreenViewController.h +++ b/mods/src/prime/AnimatedRewardsScreenViewController.h @@ -1,57 +1,14 @@ #pragma once -#include "errormsg.h" +#include -#include - -struct AnimatedRewardsScreenViewController { -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Missions.UI", "AnimatedRewardsScreenViewController"); - return class_helper; - } +struct AnimatedRewardsScreenViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Missions.UI", "AnimatedRewardsScreenViewController">; void GoBackToLastSection() - { - static auto GoBackToLastSectionMethod = - get_class_helper().GetMethod("GoBackToLastSection"); - static auto GoBackToLastSectionWarn = true; - if (GoBackToLastSectionMethod) { - GoBackToLastSectionMethod(this); - } else if (GoBackToLastSectionWarn) { - GoBackToLastSectionWarn = false; - ErrorMsg::MissingMethod("AnimatedRewardsScreenViewController", "GoBackToLastSection"); - } - } - + { Call<"GoBackToLastSection">(); } void OnCollectClicked() - { - static auto OnCollectClickedMethod = - get_class_helper().GetMethod("OnCollectClicked"); - static auto OnCollectClickedWarn = true; - - if (OnCollectClickedMethod) { - OnCollectClickedMethod(this); - } else if (OnCollectClickedWarn) { - OnCollectClickedWarn = false; - ErrorMsg::MissingMethod("AnimatedRewardsScreenViewController", "OnCollectClicked"); - } - } - + { Call<"OnCollectClicked">(); } bool IsActive() - { - auto IsActiveMethod = get_class_helper().GetMethod("IsActive"); - auto IsActiveWarn = true; - - if (IsActiveMethod) { - return IsActiveMethod(this); - } else if (IsActiveWarn) { - IsActiveWarn = false; - ErrorMsg::MissingMethod("AnimatedRewardsScreenViewController", "IsActive"); - } - - return false; - } + { return Call<"IsActive", bool>(); } }; diff --git a/mods/src/prime/ArmadaObjectViewerWidget.h b/mods/src/prime/ArmadaObjectViewerWidget.h index fd7217001..b86ff3a2f 100644 --- a/mods/src/prime/ArmadaObjectViewerWidget.h +++ b/mods/src/prime/ArmadaObjectViewerWidget.h @@ -1,81 +1,21 @@ #pragma once -#include "errormsg.h" -#include - #include "FleetPlayerData.h" #include "GenericButtonContext.h" #include "ObjectViewerBaseWidget.h" -#include "Widget.h" - -struct ArmadaObjectViewerWidget : public ObjectViewerBaseWidget { -public: - void SetCourseToArmada() - { - static auto SetCourseToArmadaMethod = - get_class_helper().GetMethod("SetCourseToArmada"); - static auto SetCourseToAramdaWarn = true; - if (SetCourseToArmadaMethod) { - SetCourseToArmadaMethod(this); - } else if (SetCourseToAramdaWarn) { - SetCourseToAramdaWarn = false; - ErrorMsg::MissingMethod("ArmadaObjectViewerWidget", "SetCourseToArmada"); - } - } +struct ArmadaObjectViewerWidget : ObjectViewerBaseWidget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "ArmadaObjectViewerWidget">; + void SetCourseToArmada() + { Call<"SetCourseToArmada">(); } void ValidateThenJoinArmada() - { - static auto ValidateThenJoinArmadaMethod = - get_class_helper().GetMethod("ValidateThenJoinArmada"); - static auto ValidateThenJoinArmadaWarn = true; - - if (ValidateThenJoinArmadaMethod) { - ValidateThenJoinArmadaMethod(this); - } else if (ValidateThenJoinArmadaWarn) { - ValidateThenJoinArmadaWarn = false; - ErrorMsg::MissingMethod("ArmadaObjectViewerWidget", "ValidateThenJoinArmada"); - } - } - + { Call<"ValidateThenJoinArmada">(); } void JoinArmada() - { - static auto JoinArmadaMethod = get_class_helper().GetMethod("JoinArmada"); - static auto JoinArmadaWarn = true; - - if (JoinArmadaMethod) { - JoinArmadaMethod(this); - } else if (JoinArmadaWarn) { - JoinArmadaWarn = false; - ErrorMsg::MissingMethod("ArmadaObjectViewerWidget", "JoinArmada"); - } - } - + { Call<"JoinArmada">(); } bool IsAllowedToJoinArmada(FleetPlayerData* fleet) - { - static auto method = - get_class_helper().GetMethod("IsAllowedToJoinArmada"); - bool result = method(this, fleet); - spdlog::info("IsAllowedToJoinArmada: {}", result); - return result; - } - -private: - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; - -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "ArmadaObjectViewerWidget"); - return class_helper; - } + { return Call<"IsAllowedToJoinArmada", bool>(fleet); } -public: - GenericButtonContext* __get__joinContext() - { - static auto field = get_class_helper().GetField("_joinContext").offset(); - return *(GenericButtonContext**)((char*)this + field); - } + GenericButtonContext* joinContext() + { return Field<"_joinContext", GenericButtonContext*>(); } }; diff --git a/mods/src/prime/ArtifactHallDetailsViewController.h b/mods/src/prime/ArtifactHallDetailsViewController.h index 4f0dde9ff..0d676c219 100644 --- a/mods/src/prime/ArtifactHallDetailsViewController.h +++ b/mods/src/prime/ArtifactHallDetailsViewController.h @@ -2,69 +2,52 @@ #include "SemaphoreButtonListener.h" -#include +#include +#include -struct ArtifactHallDetailsViewController { -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ArtifactHall", "ArtifactHallDetailsViewController"); - return class_helper; - } +struct ArtifactHallDetailsViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ArtifactHall", "ArtifactHallDetailsViewController">; + // Treat the controller as active if the icall is unavailable, so the arrow hotkeys still try. bool isActiveAndEnabled() { - static auto get_isActiveAndEnabled = il2cpp_resolve_icall_typed( + static auto get_isActiveAndEnabled = il2cpp::ResolveICall( "UnityEngine.Behaviour::get_isActiveAndEnabled()"); - if (get_isActiveAndEnabled) { - return get_isActiveAndEnabled(this); - } - return true; + return get_isActiveAndEnabled ? get_isActiveAndEnabled(this) : true; } void PressLeftArrow() { - auto listener = LeftArrowButton(); + auto* listener = leftArrowButton(); if (!listener) { spdlog::trace("No left button listener"); return; } - - if (!listener->TheButton) { + auto* button = listener->TheButton(); + if (!button) { spdlog::trace("No left button"); return; } - - listener->TheButton->Press(); + button->Press(); } void PressRightArrow() { - auto listener = RightArrowButton(); + auto* listener = rightArrowButton(); if (!listener) { spdlog::info("No right button listener"); return; } - - if (!listener->TheButton) { + auto* button = listener->TheButton(); + if (!button) { spdlog::info("No right button"); return; } - - listener->TheButton->Press(); - } - -private: - SemaphoreButtonListener* LeftArrowButton() - { - static auto field = get_class_helper().GetField("_leftArrowButton").offset(); - return *reinterpret_cast(reinterpret_cast(this) + field); + button->Press(); } - SemaphoreButtonListener* RightArrowButton() - { - static auto field = get_class_helper().GetField("_rightArrowButton").offset(); - return *reinterpret_cast(reinterpret_cast(this) + field); - } + SemaphoreButtonListener* leftArrowButton() + { return Field<"_leftArrowButton", SemaphoreButtonListener*>(); } + SemaphoreButtonListener* rightArrowButton() + { return Field<"_rightArrowButton", SemaphoreButtonListener*>(); } }; diff --git a/mods/src/prime/AspectRatioConstraintHandler.h b/mods/src/prime/AspectRatioConstraintHandler.h index 9d13b5c5c..d66498f66 100644 --- a/mods/src/prime/AspectRatioConstraintHandler.h +++ b/mods/src/prime/AspectRatioConstraintHandler.h @@ -2,22 +2,13 @@ #if _WIN32 -#include +#include struct AspectRatioConstraintHandler { -public: - static WNDPROC _unityWndProc() - { - return get_class_helper().GetStaticField("_unityWndProc").Get(); - } + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Utils", "AspectRatioConstraintHandler">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Utils", "AspectRatioConstraintHandler"); - return class_helper; - } + static WNDPROC _unityWndProc() + { return reinterpret_cast(Class::Field<"_unityWndProc", intptr_t>()); } }; #endif diff --git a/mods/src/prime/AssignShipsWidget.h b/mods/src/prime/AssignShipsWidget.h index de34e3bfc..e72e35d1c 100644 --- a/mods/src/prime/AssignShipsWidget.h +++ b/mods/src/prime/AssignShipsWidget.h @@ -1,49 +1,20 @@ #pragma once -#include - #include "ButtonAndContextWrapper.h" #include "InputFieldWidget.h" #include "SelectableList.h" -struct AssignShipsWidget { -public: - __declspec(property(get = __get__inputField)) InputFieldWidget* _inputField; - __declspec(property(get = __get_isActiveAndEnabled)) bool isActiveAndEnabled; - __declspec(property(get = __get__assignButton)) ButtonAndContextWrapper* _assignButton; - __declspec(property(get = __get__selectableList)) SelectableList* _selectableList; - -private: - friend class ObjectFinder; - -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Ships", "AssignShipsWidget"); - return class_helper; - } - - InputFieldWidget* __get__inputField() - { - static auto field = get_class_helper().GetField("_inputField").offset(); - return *(InputFieldWidget**)((uintptr_t)this + field); - } - - bool __get_isActiveAndEnabled() - { - static auto field = get_class_helper().GetProperty("isActiveAndEnabled"); - return field.Get(this); - } +#include - ButtonAndContextWrapper* __get__assignButton() - { - static auto field = get_class_helper().GetField("_assignButton").offset(); - return *(ButtonAndContextWrapper**)((uintptr_t)this + field); - } +struct AssignShipsWidget : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Ships", "AssignShipsWidget">; - SelectableList* __get__selectableList() - { - static auto field = get_class_helper().GetField("_selectableList").offset(); - return *(SelectableList**)((uintptr_t)this + field); - } + InputFieldWidget* inputField() + { return Field<"_inputField", InputFieldWidget*>(); } + bool isActiveAndEnabled() + { return Get<"isActiveAndEnabled", bool>(); } + ButtonAndContextWrapper* assignButton() + { return Field<"_assignButton", ButtonAndContextWrapper*>(); } + SelectableList* selectableList() + { return Field<"_selectableList", SelectableList*>(); } }; diff --git a/mods/src/prime/BattleResultHeader.h b/mods/src/prime/BattleResultHeader.h index d44699ea4..40f5ec1e5 100644 --- a/mods/src/prime/BattleResultHeader.h +++ b/mods/src/prime/BattleResultHeader.h @@ -1,85 +1,55 @@ #pragma once -#include - -enum class BattleType { - Fleet = 0, - Base = 1, - PassiveMarauder = 2, - NpcInstantiated = 3, - DockingPoint = 4, - ActiveMarauder_MarauderInit = 5, - ActiveMarauder_PlayerInit = 6, - ArmadaBase = 7, - ArmadaMarauder = 8, - PveDockingPoint = 9, - ArmadaAsb = 10, - ArmadaMta = 11, - Hazard = 12, - PveCuttingBeam = 13, - PvpCuttingBeam = 14, - PveChainShot = 15, - PvpChainShot = 16 +#include + +#include + +enum class BattleType : int32_t { + Fleet = 0, + Base = 1, + PassiveMarauder = 2, + NpcInstantiated = 3, + DockingPoint = 4, + ActiveMarauder_MarauderInit = 5, + ActiveMarauder_PlayerInit = 6, + ArmadaBase = 7, + ArmadaMarauder = 8, + PveDockingPoint = 9, + ArmadaAsb = 10, + ArmadaMta = 11, + Hazard = 12, + PveCuttingBeam = 13, + PvpCuttingBeam = 14, + PveChainShot = 15, + PvpChainShot = 16 }; -enum class BattleResultType { +enum class BattleResultType : int32_t { Defeat = 0, Victory = 1, PartialVictory = 2 }; -enum class FleetDataType { +enum class FleetDataType : int32_t { DeployedFleet = 0, Starbase = 1, Armada = 2 }; -struct BattleResultHeader { -public: - __declspec(property(get = __get_PlayerShipHullId)) long PlayerShipHullId; - __declspec(property(get = __get_EnemyShipHullId)) long EnemyShipHullId; +struct BattleResultHeader : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "BattleResultHeader">; Il2CppObject* get_PlayerUserProfile() - { - static auto prop = get_class_helper().GetProperty("PlayerUserProfile"); - return prop.GetRaw(this); - } - + { return Get<"PlayerUserProfile", Il2CppObject*>(); } Il2CppObject* get_EnemyUserProfile() - { - static auto prop = get_class_helper().GetProperty("EnemyUserProfile"); - return prop.GetRaw(this); - } - + { return Get<"EnemyUserProfile", Il2CppObject*>(); } Il2CppObject* get_SpecService() - { - return *reinterpret_cast(reinterpret_cast(this) + 0x18); - } - - int get_BattleType() - { - static auto prop = get_class_helper().GetProperty("BattleType"); - return *prop.Get(this); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "BattleResultHeader"); - return class_helper; - } - -public: - long __get_PlayerShipHullId() - { - static auto prop = get_class_helper().GetProperty("PlayerShipHullId"); - return *prop.Get(this); - } - - long __get_EnemyShipHullId() - { - static auto prop = get_class_helper().GetProperty("EnemyShipHullId"); - return *prop.Get(this); - } + { return Field<"_specService", Il2CppObject*>(); } + int32_t get_BattleType() + { return Get<"BattleType", int32_t>(); } + + int64_t PlayerShipHullId() + { return Get<"PlayerShipHullId", int64_t>(); } + int64_t EnemyShipHullId() + { return Get<"EnemyShipHullId", int64_t>(); } }; diff --git a/mods/src/prime/BattleTargetData.h b/mods/src/prime/BattleTargetData.h index 5d8ef50e8..6361b81e7 100644 --- a/mods/src/prime/BattleTargetData.h +++ b/mods/src/prime/BattleTargetData.h @@ -1,24 +1,12 @@ #pragma once -#include #include "FleetDeployedData.h" -struct BattleTargetData { -public: - __declspec(property(get = __get_TargetFleetDeployedData)) FleetDeployedData* TargetFleetDeployedData; +#include -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "BattleTargetData"); - return class_helper; - } +struct BattleTargetData : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "BattleTargetData">; -public: - FleetDeployedData* __get_TargetFleetDeployedData() - { - static auto field = get_class_helper().GetField("TargetFleetDeployedData").offset(); - return *(FleetDeployedData**)((char*)this + field); - } + FleetDeployedData* TargetFleetDeployedData() + { return Field<"TargetFleetDeployedData", FleetDeployedData*>(); } }; diff --git a/mods/src/prime/BlurController.h b/mods/src/prime/BlurController.h index 85425e733..866d65b00 100644 --- a/mods/src/prime/BlurController.h +++ b/mods/src/prime/BlurController.h @@ -1,47 +1,19 @@ #pragma once -#include +#include -struct BlurController { -public: - __declspec(property(get = __get__blurTime, put = __set__blurTime)) float _blurTime; - __declspec(property(get = __get__waitFrames, put = __set__waitFrames)) float _waitFrames; +struct BlurController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.LoadingScreen", "BlurController">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.LoadingScreen", "BlurController"); - return class_helper; - } - -public: - float __get__blurTime() - { - static auto field = get_class_helper().GetField("_blurTime"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - void __set__blurTime(float v) - { - static auto field = get_class_helper().GetField("_blurTime"); - *(float*)((ptrdiff_t)this + field.offset()) = v; - } - - float __get__waitFrames() - { - static auto field = get_class_helper().GetField("_waitFrames"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - void __set__waitFrames(float v) - { - static auto field = get_class_helper().GetField("_waitFrames"); - *(float*)((ptrdiff_t)this + field.offset()) = v; - } + float blurTime() + { return Field<"_blurTime", float>(); } + void SetBlurTime(float value) + { SetField<"_blurTime">(value); } + int32_t waitFrames() + { return Field<"_waitFrames", int32_t>(); } + void SetWaitFrames(int32_t value) + { SetField<"_waitFrames">(value); } void ForceCompletion() - { - static auto method = get_class_helper().GetMethod("ForceCompletion"); - if (method) - reinterpret_cast(method)(this); - } -}; \ No newline at end of file + { Call<"ForceCompletion">(); } +}; diff --git a/mods/src/prime/BookmarksManager.h b/mods/src/prime/BookmarksManager.h index ad162140f..a491a5ba8 100644 --- a/mods/src/prime/BookmarksManager.h +++ b/mods/src/prime/BookmarksManager.h @@ -1,86 +1,41 @@ #pragma once -#include "errormsg.h" -#include -#include - -#include "MonoSingleton.h" #include "Hub.h" +#include "MonoSingleton.h" + +#include + +struct CoordinateSearchContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Bookmarks", "CoordinateSearchContext">; + + void SetDefaultXCoordinate(Il2CppString* value) + { Set<"DefaultXCoordinate">(value); } + void SetDefaultYCoordinate(Il2CppString* value) + { Set<"DefaultYCoordinate">(value); } + void SetDefaultSystemInput(Il2CppString* value) + { Set<"DefaultSystemInput">(value); } +}; struct BookmarksManager : MonoSingleton { - friend struct MonoSingleton; + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Bookmarks", "BookmarksManager">; -public: void ViewBookmarks() - { - static auto ViewBookmarksMethod = get_class_helper().GetMethod("ViewBookmarks"); - static auto ViewBookmarksWarn = true; - - if (ViewBookmarksMethod) { - ViewBookmarksMethod(this); - } else if (ViewBookmarksWarn) { - ViewBookmarksWarn = false; - ErrorMsg::MissingMethod("BookmarksManager", "ViewBookmarks"); - } - } + { Call<"ViewBookmarks">(); } void ViewCoordinateSearch() { - auto* context = CreateCoordinateSearchContext(); + auto* context = il2cpp::New(); + // The binding already reported the missing class or constructor; plain bookmarks are the next best view. if (!context) { - spdlog::error("[CoordSearch] Failed to create CoordinateSearchContext, falling back to bookmarks"); ViewBookmarks(); return; } - Hub::get_SectionManager()->TriggerSectionChange(SectionID::Bookmarks_Search_Coordinates, context, false, false, true); - } - -private: - void* CreateCoordinateSearchContext() - { - static auto coord_class = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Bookmarks", "CoordinateSearchContext"); - if (!coord_class.isValidHelper()) { - spdlog::error("[CoordSearch] Failed to get CoordinateSearchContext class"); - return nullptr; - } - - auto* context = coord_class.New(); - if (!context) { - spdlog::error("[CoordSearch] Failed to create CoordinateSearchContext instance"); - return nullptr; - } - - static auto ctor_method = coord_class.GetMethod(".ctor"); - if (ctor_method) { - ctor_method(context); - } else { - spdlog::warn("[CoordSearch] CoordinateSearchContext .ctor() method not found"); - } - - auto* zero_str = il2cpp_string_new("0"); - static auto set_x_method = coord_class.GetMethod("set_DefaultXCoordinate"); - if (set_x_method && zero_str) { - set_x_method(context, zero_str); - } - - static auto set_y_method = coord_class.GetMethod("set_DefaultYCoordinate"); - if (set_y_method && zero_str) { - set_y_method(context, zero_str); - } - - auto* empty_str = il2cpp_string_new(""); - static auto set_system_method = coord_class.GetMethod("set_DefaultSystemInput"); - if (set_system_method && empty_str) { - set_system_method(context, empty_str); - } - - return context; - } - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Bookmarks", "BookmarksManager"); - return class_helper; + auto* zero = il2cpp_string_new("0"); + context->SetDefaultXCoordinate(zero); + context->SetDefaultYCoordinate(zero); + context->SetDefaultSystemInput(il2cpp_string_new("")); + Hub::get_SectionManager()->TriggerSectionChange(SectionID::Bookmarks_Search_Coordinates, context, false, false, + true); } }; diff --git a/mods/src/prime/Bundle.h b/mods/src/prime/Bundle.h index ad9a52bec..30fe1e564 100644 --- a/mods/src/prime/Bundle.h +++ b/mods/src/prime/Bundle.h @@ -1,3 +1,7 @@ #pragma once -Digit.PrimePlatform.Content \ No newline at end of file +#include + +struct Bundle : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimePlatform.Content", "Bundle">; +}; diff --git a/mods/src/prime/BundleDataWidget.h b/mods/src/prime/BundleDataWidget.h index 01ff7ceb2..0c34585e8 100644 --- a/mods/src/prime/BundleDataWidget.h +++ b/mods/src/prime/BundleDataWidget.h @@ -1,73 +1,38 @@ #pragma once -#include - +#include "Bundle.h" #include "Widget.h" -class Bundle -{ -public: - // -private: - friend struct Widget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimePlatform.Content", "Bundle"); - return class_helper; - } -}; +#include + +#include + +struct BundleDataWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Shop", "BundleDataWidget">; -class BundleDataWidget : public Widget -{ -public: - enum ItemState { + enum ItemState : int32_t { None = 0, Package = 1, Highlighted = 2, LegendOn = 4, ExpiryTimerOn = 8, - DiscountOn = 16, // 0x00000010 - RemoteImage = 32, // 0x00000020 - Showcase = 256, // 0x00000100 - HasBanner = 512, // 0x00000200 - ShowViewButton = 1024, // 0x00000400 - ShowMaxPurchases = 2048, // 0x00000800 - ShowItemRarity = 4096, // 0x00001000 - ShowItemType = 8192, // 0x00002000 - CooldownTimerOn = 16384, // 0x00004000 - ShowSynthetic = 32768, // 0x00008000 - ShowPurchaseLimits = 65536, // 0x00010000 + DiscountOn = 16, // 0x00000010 + RemoteImage = 32, // 0x00000020 + Showcase = 256, // 0x00000100 + HasBanner = 512, // 0x00000200 + ShowViewButton = 1024, // 0x00000400 + ShowMaxPurchases = 2048, // 0x00000800 + ShowItemRarity = 4096, // 0x00001000 + ShowItemType = 8192, // 0x00002000 + CooldownTimerOn = 16384, // 0x00004000 + ShowSynthetic = 32768, // 0x00008000 + ShowPurchaseLimits = 65536, // 0x00010000 + ShowFreeTag = 131072, // 0x00020000 }; - __declspec(property(get = __get_CurrentState)) ItemState CurrentState; + ItemState CurrentState() + { return Field<"_currentState", ItemState>(); } void AuxViewButtonPressedHandler() - { - static auto AuxViewButtonPressedHandler = - get_class_helper().GetMethod("AuxViewButtonPressedHandler"); - static auto AuxViewButtonPressedWarn = true; - - if (AuxViewButtonPressedHandler) { - AuxViewButtonPressedHandler(this); - } else if (AuxViewButtonPressedWarn) { - AuxViewButtonPressedWarn = false; - ErrorMsg::MissingMethod("BundleDataWidget", "AuxViewButtonPressedHandler"); - } - } - -private: - friend struct Widget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Shop", "BundleDataWidget"); - return class_helper; - } - -public: - ItemState __get_CurrentState() - { - static auto field = get_class_helper().GetField("_currentState"); - return *(ItemState*)((ptrdiff_t)this + field.offset()); - } + { Call<"AuxViewButtonPressedHandler">(); } }; diff --git a/mods/src/prime/Button.h b/mods/src/prime/Button.h index 6ee933b25..2a25bdfdb 100644 --- a/mods/src/prime/Button.h +++ b/mods/src/prime/Button.h @@ -1,20 +1,10 @@ #pragma once -#include +#include -class Button -{ -public: - void Press() - { - auto PressMethod = get_class_helper().GetMethod("Press"); - PressMethod(this); - }; +struct Button : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.UI", "UnityEngine.UI", "Button">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "Button"); - return class_helper; - }; + void Press() + { Call<"Press">(); } }; diff --git a/mods/src/prime/ButtonAndContextWrapper.h b/mods/src/prime/ButtonAndContextWrapper.h index 8c36327b0..4e5cc5f44 100644 --- a/mods/src/prime/ButtonAndContextWrapper.h +++ b/mods/src/prime/ButtonAndContextWrapper.h @@ -1,23 +1,12 @@ #pragma once -#include - #include "GenericButtonWidget.h" -struct ButtonAndContextWrapper { -public: - __declspec(property(get = __get_Widget)) GenericButtonWidget* Widget; +#include - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "ButtonAndContextWrapper"); - return class_helper; - } +struct ButtonAndContextWrapper : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "ButtonAndContextWrapper">; - GenericButtonWidget* __get_Widget() - { - static auto field = get_class_helper().GetField("Widget").offset(); - return *(GenericButtonWidget**)((char*)this + field); - } + GenericButtonWidget* Widget() + { return Field<"Widget", GenericButtonWidget*>(); } }; diff --git a/mods/src/prime/CallbackContainer.h b/mods/src/prime/CallbackContainer.h deleted file mode 100644 index c6a5a502e..000000000 --- a/mods/src/prime/CallbackContainer.h +++ /dev/null @@ -1,90 +0,0 @@ -#pragma once - -#include - -struct __declspec(align(8)) Delegate__Fields { - void* method_ptr; - void* invoke_impl; - struct Object* m_target; - void* method; - void* delegate_trampoline; - void* method_code; - struct MethodInfo_1* method_info; - struct MethodInfo_1* original_method_info; - struct DelegateData* data; -}; - -struct Delegate { - struct Delegate__Class* klass; - MonitorData* monitor; - struct Delegate__Fields fields; -}; - -struct MulticastDelegate__Fields { - struct Delegate__Fields _; - struct MulticastDelegate* prev; - struct MulticastDelegate* kpm_next; -}; - -struct OnSuccess_1_System_Int32___Fields { - struct MulticastDelegate__Fields _; -}; - -struct OnSuccess_1_System_Int32_ { - struct OnSuccess_1_System_Int32___Class* klass; - MonitorData* monitor; - struct OnSuccess_1_System_Int32___Fields fields; -}; - -struct OnError__Fields { - struct MulticastDelegate__Fields _; -}; - -struct OnError { - struct OnError__Class* klass; - MonitorData* monitor; - struct OnError__Fields fields; -}; - -struct __declspec(align(8)) CallbackContainer_1_System_Int32___Fields { - struct OnSuccess_1_System_Int32_* _onSuccess; - struct OnError* _onError; - struct OnRetry* _onRetry; -#if defined(_CPLUSPLUS_) - CallbackErrorHandling__Enum _callbackErrorHandling; -#else - int32_t _callbackErrorHandling; -#endif -}; - -struct CallbackContainer_1_System_Int32_ { - struct CallbackContainer_1_System_Int32___Class* klass; - MonitorData* monitor; - struct CallbackContainer_1_System_Int32___Fields fields; -}; - -class CallbackContainer -{ -public: - CallbackContainer() {} - - static CallbackContainer_1_System_Int32_* Create() - { - auto n = get_class_helper().New(); - n = n; - n->fields._onRetry = nullptr; - - n->fields._callbackErrorHandling = 0; - n->fields._onSuccess = (OnSuccess_1_System_Int32_*)n; - n->fields._onError = (OnError*)n; - return n; - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Engine.Utilities.Runtime", "Digit.Networking.Core", "CallbackContainer`1"); - return class_helper; - } -}; \ No newline at end of file diff --git a/mods/src/prime/Camera.h b/mods/src/prime/Camera.h index b9429fef5..d260fc441 100644 --- a/mods/src/prime/Camera.h +++ b/mods/src/prime/Camera.h @@ -2,64 +2,26 @@ #include "Color.h" -class Camera -{ -public: - __declspec(property(get = __get_farClipPlane, put = __set_farClipPlane)) float farClipPlane; - __declspec(property(get = __get_nearClipPlane, put = __set_nearClipPlane)) float nearClipPlane; - __declspec(property(get = __get_clearFlags, put = __set_clearFlags)) int clearFlags; - __declspec(property(get = __get_backgroundColor, put = __set_backgroundColor)) color backgroundColor; +#include -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Camera"); - return class_helper; - } +struct Camera : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Camera">; -public: - float __get_farClipPlane() - { - static auto field = get_class_helper().GetProperty("farClipPlane"); - return *field.GetRaw(this); - } - void __set_farClipPlane(float v) - { - static auto field = get_class_helper().GetProperty("farClipPlane"); - field.SetRaw(this, v); - } - - float __get_nearClipPlane() - { - static auto field = get_class_helper().GetProperty("nearClipPlane"); - return *field.GetRaw(this); - } - void __set_nearClipPlane(float v) - { - static auto field = get_class_helper().GetProperty("nearClipPlane"); - field.SetRaw(this, v); - } - - int __get_clearFlags() - { - static auto field = get_class_helper().GetProperty("clearFlags"); - return *field.GetRaw(this); - } - void __set_clearFlags(int v) - { - static auto field = get_class_helper().GetProperty("clearFlags"); - field.SetRaw(this, v); - } - - color __get_backgroundColor() - { - static auto field = get_class_helper().GetProperty("backgroundColor"); - auto ptr = field.Get(this); - return ptr ? *ptr : color{}; - } - void __set_backgroundColor(color v) - { - static auto field = get_class_helper().GetProperty("backgroundColor"); - field.SetRaw(this, v); - } -}; \ No newline at end of file + float farClipPlane() + { return Get<"farClipPlane", float>(); } + void SetFarClipPlane(float value) + { Set<"farClipPlane">(value); } + float nearClipPlane() + { return Get<"nearClipPlane", float>(); } + void SetNearClipPlane(float value) + { Set<"nearClipPlane">(value); } + // UnityEngine.CameraClearFlags + int32_t clearFlags() + { return Get<"clearFlags", int32_t>(); } + void SetClearFlags(int32_t value) + { Set<"clearFlags">(value); } + color backgroundColor() + { return Get<"backgroundColor", color>(); } + void SetBackgroundColor(color value) + { Set<"backgroundColor">(value); } +}; diff --git a/mods/src/prime/CanRepairRequirement.h b/mods/src/prime/CanRepairRequirement.h index 68a7c8571..7b599c7e3 100644 --- a/mods/src/prime/CanRepairRequirement.h +++ b/mods/src/prime/CanRepairRequirement.h @@ -1,18 +1,8 @@ #pragma once -#include - #include "ActionRequirement.h" -struct CanRepairRequirement : public ActionRequirement { - -public: - friend struct ActionRequirement; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "", "FleetPlayerData.CanRepairRequirement"); - return class_helper; - } -}; \ No newline at end of file +struct CanRepairRequirement : ActionRequirement { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", + "FleetPlayerData/CanRepairRequirement">; +}; diff --git a/mods/src/prime/Canvas.h b/mods/src/prime/Canvas.h index 23712a3df..d2e7ebb8f 100644 --- a/mods/src/prime/Canvas.h +++ b/mods/src/prime/Canvas.h @@ -1,27 +1,12 @@ #pragma once -#include "il2cpp/il2cpp_helper.h" +#include -struct Canvas { -public: - __declspec(property(get = __get_scaleFactor, put = __set_scaleFactor)) float scaleFactor; +struct Canvas : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.UIModule", "UnityEngine", "Canvas">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("UnityEngine.UIModule", "UnityEngine", "Canvas"); - return class_helper; - } - -public: - float __get_scaleFactor() - { - static auto field = get_class_helper().GetProperty("scaleFactor"); - return *field.Get(this); - } - void __set_scaleFactor(float v) - { - static auto field = get_class_helper().GetProperty("scaleFactor"); - field.SetRaw(this, v); - } + float scaleFactor() + { return Get<"scaleFactor", float>(); } + void SetScaleFactor(float value) + { Set<"scaleFactor">(value); } }; diff --git a/mods/src/prime/CanvasController.h b/mods/src/prime/CanvasController.h index c141e8965..51052a545 100644 --- a/mods/src/prime/CanvasController.h +++ b/mods/src/prime/CanvasController.h @@ -1,95 +1,37 @@ #pragma once -#include - -#include "errormsg.h" - #include "Canvas.h" #include "Transform.h" -struct CanvasController { -public: - __declspec(property(get = __get_Transform)) Transform* transform; - __declspec(property(get = __get_Name)) Il2CppString* name; - __declspec(property(get = __get_m_canvas)) Canvas* m_canvas; +#include - void ResetFocus() - { - static auto ResetFocusMethod = get_class_helper().GetMethod("ResetFocus"); - static auto ResetFocusWarn = true; - - if (ResetFocusMethod) { - ResetFocusMethod(this); - } else if (ResetFocusWarn) { - ResetFocusWarn = false; - ErrorMsg::MissingMethod("CanvasController", "ResetFocus"); - } - } +struct CanvasController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "CanvasController">; + bool ResetFocus() + { return Call<"ResetFocus", bool>(); } bool Visible() - { - static auto field = get_class_helper().GetProperty("Visible"); - return field.Get(this); - } - + { return Get<"Visible", bool>(); } + // The bound data context, of the class the canvas's view expects. + il2cpp::Object* Context() + { return Get<"Context", il2cpp::Object*>(); } bool get_enabled() - { - static auto field = get_class_helper().GetProperty("enabled"); - return field.Get(this); - } - + { return Get<"enabled", bool>(); } bool m_Visible() - { - static auto field = get_class_helper().GetField("m_visible").offset(); - return *(bool*)((ptrdiff_t)this + field); - } - - Il2CppString* __get_Name() - { - static auto field = get_class_helper().GetProperty("name"); - return field.GetRaw(this); - } - - Transform* __get_Transform() - { - static auto field = get_class_helper().GetProperty("transform"); - return field.GetRaw(this); - } - - Canvas* __get_m_canvas() - { - static auto field = get_class_helper().GetField("m_canvas").offset(); - return *(Canvas**)((ptrdiff_t)this + field); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "CanvasController"); - return class_helper; - } + { return Field<"m_visible", bool>(); } + + Il2CppString* name() + { return Get<"name", Il2CppString*>(); } + Transform* transform() + { return Get<"transform", Transform*>(); } + Canvas* canvas() + { return Field<"m_canvas", Canvas*>(); } }; -inline CanvasController* GetCanvasControllerFromComponent(void* component) +inline CanvasController* GetCanvasControllerFromComponent(il2cpp::Object* component) { - if (!component) { - return nullptr; - } - - static auto component_class = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Component"); - static auto method = component_class.GetMethodInfo("GetComponentInParent", 1); - if (!method) { - return nullptr; - } - - static auto canvas_type = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "CanvasController").GetType(); - - Il2CppException* exception = nullptr; - void* params[1] = {canvas_type}; - auto result = il2cpp_runtime_invoke(method, component, params, &exception); - if (exception) { - spdlog::error("[CanvasController] GetComponentInParent threw an exception"); + if (!component) return nullptr; - } - return (CanvasController*)result; + // Passing System.Type selects GetComponentInParent(Type) over the generic overload. + return component->Call<"GetComponentInParent", CanvasController*>(CanvasController::Class::TypeObject()); } diff --git a/mods/src/prime/CanvasScaler.h b/mods/src/prime/CanvasScaler.h index 9e277cd31..ae839dfef 100644 --- a/mods/src/prime/CanvasScaler.h +++ b/mods/src/prime/CanvasScaler.h @@ -2,51 +2,22 @@ #include "Vector2.h" -struct CanvasScaler { -public: - __declspec(property(get = __get_scaleFactor, put = __set_scaleFactor)) float scaleFactor; - __declspec(property(get = __get_referenceResolution, put = __set_referenceResolution)) vec2 referenceResolution; +#include - void SetScaleFactor(float v) - { - static auto SetScaleFactorMethod = get_class_helper().GetMethod("SetScaleFactor"); - static auto SetScaleFactorWarn = true; +struct CanvasScaler : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.UI", "UnityEngine.UI", "CanvasScaler">; - if (SetScaleFactorMethod) { - SetScaleFactorMethod(this, v); - } else if (SetScaleFactorWarn) { - SetScaleFactorWarn = false; - ErrorMsg::MissingMethod("CanvasScaler", "SetScaleFactor"); - } - } + // Writes Canvas.scaleFactor directly. The scaleFactor property is only applied on the scaler's next Update. + void SetScaleFactor(float value) + { Call<"SetScaleFactor">(value); } -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.UI", "CanvasScaler"); - return class_helper; - } - -public: - float __get_scaleFactor() - { - static auto field = get_class_helper().GetProperty("scaleFactor"); - return *field.Get(this); - } - void __set_scaleFactor(float v) - { - static auto field = get_class_helper().GetProperty("scaleFactor"); - field.SetRaw(this, v); - } - - vec2 __get_referenceResolution() - { - static auto field = get_class_helper().GetProperty("referenceResolution"); - return *field.Get(this); - } - void __set_referenceResolution(vec2 v) - { - static auto field = get_class_helper().GetProperty("referenceResolution"); - field.SetRaw(this, v); - } + float scaleFactor() + { return Get<"scaleFactor", float>(); } + // Named after the managed setter because SetScaleFactor is the managed method above. + void set_scaleFactor(float value) + { Set<"scaleFactor">(value); } + vec2 referenceResolution() + { return Get<"referenceResolution", vec2>(); } + void SetReferenceResolution(vec2 value) + { Set<"referenceResolution">(value); } }; diff --git a/mods/src/prime/CelestialObjectViewerWidget.h b/mods/src/prime/CelestialObjectViewerWidget.h index c0f115940..9b41638b4 100644 --- a/mods/src/prime/CelestialObjectViewerWidget.h +++ b/mods/src/prime/CelestialObjectViewerWidget.h @@ -1,22 +1,7 @@ #pragma once -#include - -#include "Widget.h" - -#include "NavigationInteractionUIContext.h" #include "ObjectViewerBaseWidget.h" -#include "ScanEngageButtonsWidget.h" - -struct CelestialObjectViewerWidget : public ObjectViewerBaseWidget { -public: - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "CelestialObjectViewerWidget"); - return class_helper; - } +struct CelestialObjectViewerWidget : ObjectViewerBaseWidget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "CelestialObjectViewerWidget">; }; diff --git a/mods/src/prime/ChatManager.h b/mods/src/prime/ChatManager.h index 16ab5d902..df7006ded 100644 --- a/mods/src/prime/ChatManager.h +++ b/mods/src/prime/ChatManager.h @@ -1,11 +1,9 @@ #pragma once -#include "errormsg.h" - -#include - #include "MonoSingleton.h" +#include + enum class ChatChannelCategory : int32_t { None = -1, Newbie = 0, @@ -23,94 +21,30 @@ enum class ChatViewMode : int32_t { }; struct ChatManager : MonoSingleton { - friend struct MonoSingleton; - -public: - __declspec(property(get = __get_IsSideChatAllowed)) bool IsSideChatAllowed; - __declspec(property(get = __get_IsSideChatOpen)) bool IsSideChatOpen; + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Chat", "ChatManager">; void OpenChannel(ChatChannelCategory category = ChatChannelCategory::Alliance) - { - static auto OpenChannelMethod = - get_class_helper().GetMethod("OpenChannel", 2); - static auto OpenChannelWarn = true; - static void* params = il2cpp_string_new(""); - if (OpenChannelMethod) { - OpenChannelMethod(this, category, params, nullptr); - } else if (OpenChannelWarn) { - OpenChannelWarn = false; - ErrorMsg::MissingMethod("ChatManager", "OpenChannel"); - } - } + { Call<"OpenChannel">(category, il2cpp_string_new("")); } void OpenChannel(ChatChannelCategory category, ChatViewMode viewMode) { - this->__set_ViewMode(viewMode); - this->OpenChannel(category); + SetViewMode(viewMode); + OpenChannel(category); } bool CanSeeNewbieChat() - { - static auto CanSeeNewbieChatMethod = get_class_helper().GetMethod("CanSeeNewbieChat"); - static auto CanSeeNewbieChatWarn = true; - - if (CanSeeNewbieChatMethod != nullptr) { - return CanSeeNewbieChatMethod(this); - } - - if (CanSeeNewbieChatWarn) { - CanSeeNewbieChatWarn = false; - ErrorMsg::MissingMethod("ChatManager", "CanSeeNewbieChat"); - } - - return false; - } - + { return Call<"CanSeeNewbieChat", bool>(); } bool CanSeeRegionalChat() - { - static auto CanSeeRegionalChatMethod = get_class_helper().GetMethod("CanSeeRegionalChat"); - static auto CanSeeRegionalChatWarn = true; - - if (CanSeeRegionalChatMethod != nullptr) { - return CanSeeRegionalChatMethod(this); - } - - if (CanSeeRegionalChatWarn) { - CanSeeRegionalChatWarn = false; - ErrorMsg::MissingMethod("ChatManager", "CanSeeRegionalChat"); - } - - return false; - } - -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Chat", "ChatManager"); - return class_helper; - } - - bool __get_IsSideChatAllowed() - { - static auto field = get_class_helper().GetProperty("IsSideChatAllowed"); - return *field.Get(this); - } - - bool __get_IsSideChatOpen() - { - static auto field = get_class_helper().GetProperty("IsSideChatOpen"); - return *field.Get(this); - } - - ChatViewMode __get_ViewMode() - { - static auto field = get_class_helper().GetProperty("ViewMode"); - return *field.Get(this); - } - - void __set_ViewMode(ChatViewMode v) - { - static auto field = get_class_helper().GetProperty("ViewMode"); - field.SetRaw(this, v); - } + { return Call<"CanSeeRegionalChat", bool>(); } + + bool IsSideChatAllowed() + { return Get<"IsSideChatAllowed", bool>(); } + bool IsSideChatOpen() + { return Get<"IsSideChatOpen", bool>(); } + bool isContextInitialised() + { return Field<"_isContextInitialised", bool>(); } + ChatViewMode ViewMode() + { return Get<"ViewMode", ChatViewMode>(); } + void SetViewMode(ChatViewMode value) + { Set<"ViewMode">(value); } }; diff --git a/mods/src/prime/ChatMessageListLocalViewController.h b/mods/src/prime/ChatMessageListLocalViewController.h index c67b8f518..ee2fb7d16 100644 --- a/mods/src/prime/ChatMessageListLocalViewController.h +++ b/mods/src/prime/ChatMessageListLocalViewController.h @@ -1,60 +1,23 @@ #pragma once -#include - #include "ChatManager.h" #include "TMP_InputField.h" +#include "ViewController.h" -struct ChatSectionContext { -public: - __declspec(property(get = __get__category)) ChatChannelCategory _category; - ; +#include -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Chat", "ChatSectionContext"); - return class_helper; - } +struct ChatSectionContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Chat", "ChatSectionContext">; -public: - ChatChannelCategory __get__category() - { - static auto field = get_class_helper().GetField("_category").offset(); - return *(ChatChannelCategory*)((uintptr_t)this + field); - } + ChatChannelCategory category() + { return Field<"_category", ChatChannelCategory>(); } }; -struct ChatMessageListLocalViewController { -public: - __declspec(property(get = __get__inputField)) TMP_InputField* _inputField; - __declspec(property(get = __get__inputFieldSelected)) bool _inputFieldSelected; - __declspec(property(get = __get_CanvasContext)) ChatSectionContext* CanvasContext; - -private: - friend class ObjectFinder; - -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Chat", "ChatMessageListLocalViewController"); - return class_helper; - } +struct ChatMessageListLocalViewController : ViewController { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Chat", "ChatMessageListLocalViewController">; - TMP_InputField* __get__inputField() - { - static auto field = get_class_helper().GetField("_inputField").offset(); - return *(TMP_InputField**)((uintptr_t)this + field); - } - bool __get__inputFieldSelected() - { - static auto field = get_class_helper().GetField("_inputFieldSelected").offset(); - return *(bool*)((uintptr_t)this + field); - } - ChatSectionContext* __get_CanvasContext() - { - static auto field = get_class_helper().GetProperty("CanvasContext"); - return field.Get(this); - } + TMP_InputField* inputField() + { return Field<"_inputField", TMP_InputField*>(); } + bool inputFieldSelected() + { return Field<"_inputFieldSelected", bool>(); } }; diff --git a/mods/src/prime/ChatMessageWidget.h b/mods/src/prime/ChatMessageWidget.h index 10c63b783..1f61620e4 100644 --- a/mods/src/prime/ChatMessageWidget.h +++ b/mods/src/prime/ChatMessageWidget.h @@ -1,21 +1,8 @@ #pragma once - +#include #include "UserProfileWidget.h" - -#include - -struct ChatMessageWidget { - __declspec(property(get = __get__userProfileWidget)) UserProfileWidget* _userProfileWidget; - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Chat", "ChatMessageWidget"); - return class_helper; - } - - UserProfileWidget* __get__userProfileWidget() - { - static auto field = get_class_helper().GetField("_userProfileWidget").offset(); - return *(UserProfileWidget**)((uintptr_t)this + field); - } +struct ChatMessageWidget : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Chat", "ChatMessageWidget">; + UserProfileWidget* userProfileWidget() + { return Field<"_userProfileWidget", UserProfileWidget*>(); } }; diff --git a/mods/src/prime/ChatPreviewController.h b/mods/src/prime/ChatPreviewController.h index b98983ffc..9d4938224 100644 --- a/mods/src/prime/ChatPreviewController.h +++ b/mods/src/prime/ChatPreviewController.h @@ -1,75 +1,31 @@ #pragma once -#include "errormsg.h" - -#include - #include "ChatManager.h" -class SwipeScroller -{ -public: - __declspec(property(get = __get__currentContentIndex)) int32_t _currentContentIndex; +#include - void FocusOnInstantly(int32_t index) - { - static auto FocusOnInstantlyMethod = get_class_helper().GetMethod("FocusOnInstantly"); - static auto FocusOnInstantlyWarn = true; - if (FocusOnInstantlyMethod) { - FocusOnInstantlyMethod(this, index); - } else if (FocusOnInstantlyWarn) { - FocusOnInstantlyWarn = false; - ErrorMsg::MissingMethod("SwipeScroller", "FocusOnInstantly"); - } - } +struct SwipeScroller : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "SwipeScroller">; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "SwipeScroller"); - return class_helper; - } - - int32_t __get__currentContentIndex() - { - static auto field = get_class_helper().GetField("_currentContentIndex"); - return *(int32_t*)((ptrdiff_t)this + field.offset()); - } + int32_t currentContentIndex() + { return Field<"_currentContentIndex", int32_t>(); } + void FocusOnInstantly(int32_t index) + { Call<"FocusOnInstantly">(index); } }; -class ChatPreviewController -{ -public: - __declspec(property(get = __get__panelIndicators)) Il2CppArray* _panelIndicators; - __declspec(property(get = __get__swipeScroller)) SwipeScroller* _swipeScroller; - __declspec(property(get = __get__focusedPanel, put = __set__focusedPanel)) ChatChannelCategory _focusedPanel; - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Chat", "ChatPreviewController"); - return class_helper; - } - - Il2CppArray* __get__panelIndicators() - { - static auto field = get_class_helper().GetField("_panelIndicators"); - return *(Il2CppArray**)((ptrdiff_t)this + field.offset()); - } - - SwipeScroller* __get__swipeScroller() - { - static auto field = get_class_helper().GetField("_swipeScroller"); - return *(SwipeScroller**)((ptrdiff_t)this + field.offset()); - } - - ChatChannelCategory __get__focusedPanel() - { - static auto field = get_class_helper().GetField("_focusedPanel"); - return *(ChatChannelCategory*)((ptrdiff_t)this + field.offset()); - } - - void __set__focusedPanel(ChatChannelCategory v) - { - static auto field = get_class_helper().GetField("_focusedPanel"); - *(ChatChannelCategory*)((ptrdiff_t)this + field.offset()) = v; - } +struct ChatPreviewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Chat", "ChatPreviewController">; + + // ImageSelector[] + il2cpp::Array* panelIndicators() + { return Field<"_panelIndicators", il2cpp::Array*>(); } + SwipeScroller* swipeScroller() + { return Field<"_swipeScroller", SwipeScroller*>(); } + ChatChannelCategory focusedPanel() + { return Field<"_focusedPanel", ChatChannelCategory>(); } + void SetFocusedPanel(ChatChannelCategory value) + { SetField<"_focusedPanel">(value); } + // Set by the game after its visibility refresh for a section change. + bool isSectionChangedCompleted() + { return Field<"_isSectionChangedCompleted", bool>(); } }; diff --git a/mods/src/prime/ClientModifierType.h b/mods/src/prime/ClientModifierType.h index 3e8f75397..9b10a38ca 100644 --- a/mods/src/prime/ClientModifierType.h +++ b/mods/src/prime/ClientModifierType.h @@ -1,6 +1,8 @@ +#pragma once +#include -enum class ClientModifierType { +enum class ClientModifierType : int32_t { Invalid = -0x7FFF, FleetOfficerBonusHealth = -17, FleetOfficerBonusDefense = -16, diff --git a/mods/src/prime/Component.h b/mods/src/prime/Component.h new file mode 100644 index 000000000..fc0f55df3 --- /dev/null +++ b/mods/src/prime/Component.h @@ -0,0 +1,16 @@ +#pragma once + +#include "GameObject.h" +#include "Transform.h" + +#include + +// UnityEngine.Component, the base of every MonoBehaviour. Use As() on other bindings. +struct Component : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Component">; + + Transform* transform() + { return Get<"transform", Transform*>(); } + GameObject* gameObject() + { return Get<"gameObject", GameObject*>(); } +}; diff --git a/mods/src/prime/CourseData.h b/mods/src/prime/CourseData.h index d406507e6..65fa5cfde 100644 --- a/mods/src/prime/CourseData.h +++ b/mods/src/prime/CourseData.h @@ -2,23 +2,11 @@ #include "FleetPlayerData.h" -#include +#include -struct CourseData { -public: - __declspec(property(get = __get_PlayerFleet)) FleetPlayerData* PlayerFleet; +struct CourseData : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "CourseData">; - FleetPlayerData* __get_PlayerFleet() - { - static auto field = get_class_helper().GetProperty("PlayerFleet"); - return field.GetRaw(this); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "CourseData"); - return class_helper; - } + FleetPlayerData* PlayerFleet() + { return Get<"PlayerFleet", FleetPlayerData*>(); } }; diff --git a/mods/src/prime/CoursePromptPopupWidget.h b/mods/src/prime/CoursePromptPopupWidget.h index 3668d81c9..8f1935004 100644 --- a/mods/src/prime/CoursePromptPopupWidget.h +++ b/mods/src/prime/CoursePromptPopupWidget.h @@ -2,78 +2,36 @@ #include "CourseData.h" #include "GenericButtonWidget.h" +#include "ViewController.h" #include "Widget.h" -#include +#include -class CoursePromptPopupDataContext -{ -public: - __declspec(property(get = __get_HasInstantWarp)) bool HasInstantWarp; - - bool __get_HasInstantWarp() - { - static auto field = get_class_helper().GetField("HasInstantWarp"); - return *(bool*)((char*)this + field.offset()); - } +struct CoursePromptPopupDataContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "CoursePromptPopupDataContext">; + bool HasInstantWarp() + { return Field<"HasInstantWarp", bool>(); } CourseData* GetCourseData() - { - static auto field = get_class_helper().GetField("CourseData"); - return *(CourseData**)((char*)this + field.offset()); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "CoursePromptPopupDataContext"); - return class_helper; - } + { return Field<"CourseData", CourseData*>(); } }; -struct CoursePromptPopupWidget : public Widget { -public: - __declspec(property(get = __get_LeftButton)) GenericButtonWidget* LeftButton; - __declspec(property(get = __get_RightButton)) GenericButtonWidget* RightButton; - - GenericButtonWidget* __get_LeftButton() - { - static auto field = get_class_helper().GetField("_leftButton"); - return *(GenericButtonWidget**)((char*)this + field.offset()); - } - - GenericButtonWidget* __get_RightButton() - { - static auto field = get_class_helper().GetField("_rightButton"); - return *(GenericButtonWidget**)((char*)this + field.offset()); - } - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "CoursePromptPopupWidget"); - return class_helper; - } - -private: - friend struct Widget; +struct CoursePromptPopupWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "CoursePromptPopupWidget">; + + GenericButtonWidget* LeftButton() + { return Field<"_leftButton", GenericButtonWidget*>(); } + GenericButtonWidget* RightButton() + { return Field<"_rightButton", GenericButtonWidget*>(); } + void InitiateRegularWarp() + { Call<"InitiateRegularWarp">(); } + void OnInstantWarpButtonClick() + { Call<"OnInstantWarpButtonClick">(); } }; -struct CoursePromptPopupViewController { -public: - __declspec(property(get = __get_PopupWidget)) CoursePromptPopupWidget* PopupWidget; - - CoursePromptPopupWidget* __get_PopupWidget() - { - static auto field = get_class_helper().GetField("_coursePromptPopupWidget"); - return *(CoursePromptPopupWidget**)((char*)this + field.offset()); - } +struct CoursePromptPopupViewController : ViewController { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "CoursePromptPopupViewController">; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "CoursePromptPopupViewController"); - return class_helper; - } + CoursePromptPopupWidget* PopupWidget() + { return Field<"_coursePromptPopupWidget", CoursePromptPopupWidget*>(); } }; diff --git a/mods/src/prime/DeploymentManager.h b/mods/src/prime/DeploymentManager.h index b85548e76..188acb2f7 100644 --- a/mods/src/prime/DeploymentManager.h +++ b/mods/src/prime/DeploymentManager.h @@ -1,99 +1,50 @@ #pragma once -#include "CallbackContainer.h" +#include "FleetDeployedData.h" +#include "FleetPlayerData.h" #include "MonoSingleton.h" #include "Vector3.h" -#include "FleetPlayerData.h" -#include "FleetDeployedData.h" - -#include -class DeploymentService -{ -public: - class IEnumerator_PlanCourse - { - public: - bool MoveNext() - { - static auto MoveNext = get_class_helper().GetMethodSpecial("MoveNext"); - static auto MoveWarn = true; - - if (MoveNext) { - return MoveNext(this); - } else if (MoveWarn) { - MoveWarn = false; - ErrorMsg::MissingMethod("IEnumerator_PlanCourse", "MoveNext"); - } +#include - return false; - } +struct DeploymentService : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "DeploymentService">; - private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "", "DeploymentService.d__119"); - return class_helper; - } + // The compiler-generated d__N iterator. Unbound so MoveNext resolves on whatever class the game + // returns; the suffix changes between client builds. + struct IEnumerator_PlanCourse : il2cpp::Object { + bool MoveNext() + { return Call<"MoveNext", bool>(); } }; - -private: - friend class IEnumerator_PlanCourse; - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "DeploymentService"); - return class_helper; - } }; +// The managed class is DeploymentManager; the misspelled C++ name is kept for existing callers. struct DeploymentManger : MonoSingleton { - friend struct MonoSingleton; + using Class = il2cpp::Class<"Assembly-CSharp", "", "DeploymentManager">; -public: void SetTowRequest(uint64_t towedFleetId, uint64_t towingFleetId) { - static auto SetTowRequestMethod = - get_class_helper().GetMethod("SetTowRequest"); - static auto SetTowRequestWarn = true; - - if (SetTowRequestMethod) { - auto ptr = CallbackContainer::Create(); - SetTowRequestMethod(this, towedFleetId, towingFleetId, ptr); - } else if (SetTowRequestWarn) { - SetTowRequestWarn = false; - ErrorMsg::MissingMethod("DeploymentManager", "SetTowRequest"); - } + // The game expects a CallbackContainer; an empty one without handlers is enough. Take the concrete + // generic instance from the method signature instead of spelling the inflated class out here. + static Il2CppClass* const callback_class = [] { + auto method = Class::Method("SetTowRequest", 3); + return method ? il2cpp_class_from_type(method.Info()->parameters[2]) : nullptr; + }(); + if (!callback_class) + return; + Call<"SetTowRequest">(towedFleetId, towingFleetId, il2cpp_object_new(callback_class)); } + bool HasInstantWarpAbility(FleetPlayerData* selectedFleet) + { return Call<"HasInstantWarpAbility", bool>(selectedFleet); } + + // targetAddress is a NodeAddress; the trailing data arguments are optional references. DeploymentService::IEnumerator_PlanCourse* PlanCourse(FleetPlayerData* selectedFleet, void* targetAddress, Vector3 targetPosition, FleetDeployedData* targetDeployedFleet, - void* starbaseData, void* allianceStarbaseData) - { - static auto PlanCourseMethod = - get_class_helper() - .GetMethod("PlanCourse"); - static auto PlanCourseWarn = true; - - if (PlanCourseMethod) { - return PlanCourseMethod(this, selectedFleet, targetAddress, &targetPosition, targetDeployedFleet, starbaseData, - allianceStarbaseData); - } else if (PlanCourseWarn) { - PlanCourseWarn = false; - ErrorMsg::MissingMethod("DeploymentService", "PlanCourse"); - } - - return nullptr; - } - -private: - static IL2CppClassHelper& get_class_helper() + void* starbaseData, void* allianceStarbaseData, + void* outpost = nullptr) { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "", "DeploymentManager"); - return class_helper; + return Call<"PlanCourse", DeploymentService::IEnumerator_PlanCourse*>( + selectedFleet, targetAddress, targetPosition, targetDeployedFleet, starbaseData, allianceStarbaseData, outpost); } }; diff --git a/mods/src/prime/ElementSelectorViewController.h b/mods/src/prime/ElementSelectorViewController.h index 2d12464e1..0daefee5a 100644 --- a/mods/src/prime/ElementSelectorViewController.h +++ b/mods/src/prime/ElementSelectorViewController.h @@ -1,27 +1,22 @@ #pragma once #include "Button.h" -#include "errormsg.h" -#include +#include -struct ElementSelectorViewController { -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.UI", "ElementSelectorViewController"); - return class_helper; - } +struct ElementSelectorViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.UI", "ElementSelectorViewController">; - // Field offsets from dump.cs - Button* _decrementButton() { return *reinterpret_cast(reinterpret_cast(this) + 0x38); } - Button* _incrementButton() { return *reinterpret_cast(reinterpret_cast(this) + 0x40); } + Button* decrementButton() + { return Field<"_decrementButton", Button*>(); } + Button* incrementButton() + { return Field<"_incrementButton", Button*>(); } + // Unity's internal call, so a missing binding never hides the selector hotkeys. bool isActiveAndEnabled() { static auto get_isActiveAndEnabled = - il2cpp_resolve_icall_typed("UnityEngine.Behaviour::get_isActiveAndEnabled()"); + il2cpp::ResolveICall("UnityEngine.Behaviour::get_isActiveAndEnabled()"); if (get_isActiveAndEnabled) { return get_isActiveAndEnabled(this); } @@ -29,41 +24,20 @@ struct ElementSelectorViewController { } void Decrement() - { - static auto DecrementMethod = - get_class_helper().GetMethod("Decrement"); - static auto DecrementWarn = true; - if (DecrementMethod) { - DecrementMethod(this); - } else if (DecrementWarn) { - DecrementWarn = false; - ErrorMsg::MissingMethod("ElementSelectorViewController", "Decrement"); - } - } - + { Call<"Decrement">(); } void Increment() - { - static auto IncrementMethod = - get_class_helper().GetMethod("Increment"); - static auto IncrementWarn = true; - if (IncrementMethod) { - IncrementMethod(this); - } else if (IncrementWarn) { - IncrementWarn = false; - ErrorMsg::MissingMethod("ElementSelectorViewController", "Increment"); - } - } + { Call<"Increment">(); } void PressDecrement() { - if (auto btn = _decrementButton()) { + if (auto btn = decrementButton()) { btn->Press(); } } void PressIncrement() { - if (auto btn = _incrementButton()) { + if (auto btn = incrementButton()) { btn->Press(); } } diff --git a/mods/src/prime/EmbassyObjectViewer.h b/mods/src/prime/EmbassyObjectViewer.h index 80c4c042a..e96af1e77 100644 --- a/mods/src/prime/EmbassyObjectViewer.h +++ b/mods/src/prime/EmbassyObjectViewer.h @@ -1,22 +1,7 @@ #pragma once -#include - -#include "Widget.h" - -#include "NavigationInteractionUIContext.h" #include "ObjectViewerBaseWidget.h" -#include "ScanEngageButtonsWidget.h" - -struct EmbassyObjectViewer : public ObjectViewerBaseWidget { -public: - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "EmbassyObjectViewer"); - return class_helper; - } +struct EmbassyObjectViewer : ObjectViewerBaseWidget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "EmbassyObjectViewer">; }; diff --git a/mods/src/prime/EntityGroup.h b/mods/src/prime/EntityGroup.h index 2356a1890..8f89f437d 100644 --- a/mods/src/prime/EntityGroup.h +++ b/mods/src/prime/EntityGroup.h @@ -1,45 +1,22 @@ #pragma once -#include +#include #include -struct System_Byte_array { - Il2CppObject obj; - Il2CppArrayBounds* bounds; - il2cpp_array_size_t max_length; - uint8_t m_Items[65535]; -}; - -struct ByteString { -public: - __declspec(property(get = __get_bytes)) System_Byte_array* bytes; - __declspec(property(get = __get_Length)) int32_t Length; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Google.Protobuf", "Google.Protobuf", "ByteString"); - return class_helper; - } - -public: - System_Byte_array* __get_bytes() - { - static auto field = get_class_helper().GetField("bytes"); - return *(System_Byte_array**)((ptrdiff_t)this + field.offset()); - } +struct ByteString : il2cpp::Object { + using Class = il2cpp::Class<"Google.Protobuf", "Google.Protobuf", "ByteString">; - int32_t __get_Length() - { - static auto prop = get_class_helper().GetProperty("Length"); - return *prop.Get(this); - } + il2cpp::Array* bytes() + { return Field<"bytes", il2cpp::Array*>(); } + int32_t Length() + { return Get<"Length", int32_t>(); } }; -struct EntityGroup { -public: - enum Type { +struct EntityGroup : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "EntityGroup">; + + enum Type : int32_t { UserProfiles = 0, // 0x00000000 HullSpecs = 1, // 0x00000001 ResourceSpecs = 2, // 0x00000002 @@ -290,27 +267,8 @@ struct EntityGroup { ResearchConfig = 91100, // 0x000163DC }; - __declspec(property(get = __get_Type)) Type Type_; - __declspec(property(get = __get_ByteString)) ByteString* Group; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "EntityGroup"); - return class_helper; - } - -public: - ByteString* __get_ByteString() - { - static auto prop = get_class_helper().GetProperty("Group"); - return prop.GetRaw(this); - } - - Type __get_Type() - { - static auto prop = get_class_helper().GetProperty("Type"); - return *prop.Get(this); - } + Type Type_() + { return Get<"Type", Type>(); } + ByteString* Group() + { return Get<"Group", ByteString*>(); } }; diff --git a/mods/src/prime/EventSystem.h b/mods/src/prime/EventSystem.h index 5a55d31c0..bfa0d43cb 100644 --- a/mods/src/prime/EventSystem.h +++ b/mods/src/prime/EventSystem.h @@ -1,49 +1,17 @@ #pragma once -#include - #include "GameObject.h" -struct EventSystem { -public: - static EventSystem* current() - { - static auto field = get_class_helper().GetProperty("current"); - return field.GetRaw(nullptr); - } - - void SetSelectedGameObject(void*) - { - static auto SetSelectedGameObject = get_class_helper().GetMethodSpecial( - "SetSelectedGameObject", [](auto count, auto /*params*/) { - if (count == 1) { - return true; - } - return false; - }); - return SetSelectedGameObject(this, nullptr); - } +#include - __declspec(property(get = __get_currentSelectedGameObject, - put = __set_currentSelectedGameObject)) GameObject* currentSelectedGameObject; +struct EventSystem : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.UI", "UnityEngine.EventSystems", "EventSystem">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.EventSystems", "EventSystem"); - return class_helper; - } - -public: - GameObject* __get_currentSelectedGameObject() - { - static auto field = get_class_helper().GetProperty("currentSelectedGameObject"); - return field.GetRaw(this); - } + static EventSystem* current() + { return Class::Get<"current", EventSystem*>(); } - void __set_currentSelectedGameObject(GameObject* v) - { - static auto field = get_class_helper().GetProperty("currentSelectedGameObject"); - return field.SetRaw(this, v); - } -}; \ No newline at end of file + GameObject* currentSelectedGameObject() + { return Get<"currentSelectedGameObject", GameObject*>(); } + void SetSelectedGameObject(GameObject* object) + { Call<"SetSelectedGameObject">(object); } +}; diff --git a/mods/src/prime/FleetBarViewController.h b/mods/src/prime/FleetBarViewController.h index c0d0ec9a6..f43a17004 100644 --- a/mods/src/prime/FleetBarViewController.h +++ b/mods/src/prime/FleetBarViewController.h @@ -1,96 +1,32 @@ #pragma once -#include - #include "FleetLocalViewController.h" +#include "FleetPlayerData.h" +#include "ViewController.h" + +#include -struct FleetBarContext { -public: - __declspec(property(get = __get_CurrentFleet)) void* CurrentFleet; +#include -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.HUD", "FleetBarContext"); - return class_helper; - } +struct FleetBarContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.HUD", "FleetBarContext">; -public: - Il2CppArraySize* FleetData() - { - static auto property = get_class_helper().GetProperty("FleetData"); - return property.GetRaw(this); - } + il2cpp::Array* FleetData() + { return Get<"FleetData", il2cpp::Array*>(); } - void* __get_CurrentFleet() - { - static auto field = get_class_helper().GetProperty("CurrentFleet"); - return field.Get(this); - } + FleetPlayerData* CurrentFleet() + { return Get<"CurrentFleet", FleetPlayerData*>(); } }; -struct FleetBarViewController { -public: - __declspec(property(get = __get__fleetPanelController)) FleetLocalViewController* _fleetPanelController; +struct FleetBarViewController : ViewController { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.HUD", "FleetBarViewController">; + // The int overload; the other takes the list element Component. void RequestSelect(int32_t index, bool simulated = false) - { - static auto RequestSelectWarn = true; - static auto RequestSelectMethod = get_class_helper().GetMethodSpecial( - "RequestSelect", [](auto count, auto params) { - if (count != 2) { - return false; - } - auto p1 = params[0]->type; - if (p1 == IL2CPP_TYPE_I4) { - return true; - } - return false; - }); - if (RequestSelectMethod) { - RequestSelectMethod(this, index, simulated); - } else if (RequestSelectWarn) { - RequestSelectWarn = false; - ErrorMsg::MissingMethod("FleetBarViewController", "RequestSelect"); - } - } - + { Call<"RequestSelect">(index, simulated); } bool IsIndexSelected(int32_t index) - { - static auto IsIndexSelectedWarn = true; - static auto IsIndexSelectedMethod = - get_class_helper().GetMethod("IsIndexSelected"); - - if (IsIndexSelectedMethod) { - return IsIndexSelectedMethod(this, index); - } else if (IsIndexSelectedWarn) { - IsIndexSelectedWarn = false; - ErrorMsg::MissingMethod("FleetBarViewController", "IsIndexSelected"); - } - - return false; - } - - FleetBarContext* CanvasContext() - { - static auto n = get_class_helper().GetProperty("CanvasContext"); - return n.GetRaw(this); - } - -private: - friend class ObjectFinder; - -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.HUD", "FleetBarViewController"); - return class_helper; - } + { return Call<"IsIndexSelected", bool>(index); } -public: - FleetLocalViewController* __get__fleetPanelController() - { - static auto field = get_class_helper().GetField("_fleetPanelController").offset(); - return *(FleetLocalViewController**)((uintptr_t)this + field); - } + FleetLocalViewController* fleetPanelController() + { return Field<"_fleetPanelController", FleetLocalViewController*>(); } }; diff --git a/mods/src/prime/FleetDeployedData.h b/mods/src/prime/FleetDeployedData.h index fc3bc607c..7197c1143 100644 --- a/mods/src/prime/FleetDeployedData.h +++ b/mods/src/prime/FleetDeployedData.h @@ -1,9 +1,12 @@ #pragma once -#include #include "HullSpec.h" -enum class DeployedFleetType { +#include + +#include + +enum class DeployedFleetType : int32_t { Nonexistent, Player, Marauder, @@ -13,48 +16,14 @@ enum class DeployedFleetType { Challenge, }; -struct FleetDeployedData { -public: - __declspec(property(get = __get_ID)) long ID; - __declspec(property(get = __get_Hull)) HullSpec* Hull; - __declspec(property(get = __get_FleetType)) DeployedFleetType FleetType; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "FleetDeployedData"); - return class_helper; - } - -public: - long __get_ID() - { - static auto field = get_class_helper().GetProperty("ID"); - return *field.Get(this); - } - - HullSpec* __get_Hull() - { - static auto field = get_class_helper().GetProperty("Hull"); - return field.GetRaw(this); - } - - DeployedFleetType __get_FleetType() - { - static auto field = get_class_helper().GetProperty("FleetType"); - return *field.Get(this); - } - - bool TryGetFleetType(DeployedFleetType& fleet_type) - { - static auto property = get_class_helper().GetProperty("FleetType"); - const auto* value = property.Get(this); - if (value == nullptr) { - return false; - } +struct FleetDeployedData : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "FleetDeployedData">; - fleet_type = *value; - return true; - } + int64_t ID() + { return Get<"ID", int64_t>(); } + HullSpec* Hull() + { return Get<"Hull", HullSpec*>(); } + // Nonexistent when the property is missing. + DeployedFleetType FleetType() + { return Get<"FleetType", DeployedFleetType>(); } }; diff --git a/mods/src/prime/FleetLiveData.h b/mods/src/prime/FleetLiveData.h index b527610b8..0fb21159b 100644 --- a/mods/src/prime/FleetLiveData.h +++ b/mods/src/prime/FleetLiveData.h @@ -1,120 +1,73 @@ #pragma once -#include "ManagedRead.h" - -#include +#include #include #include -// Cargo, mining slot and timers off FleetPlayerData. Interface values go through the interface getter because -// FleetMiningSlot implements IMiningSlot explicitly. All accessors are null-safe. - -namespace fleet_live_detail -{ -inline Il2CppClass* models_class(const char* name) -{ - return il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", name).get_cls(); -} +// Cargo, mining slot and timers off FleetPlayerData. The interfaces are bound as such: their getters dispatch to the +// runtime class, which also covers FleetMiningSlot's explicit IMiningSlot implementation. -// interface getter, else the runtime class's -inline const MethodInfo* getter(Il2CppClass* iface, const char* property, void* obj) -{ - if (auto* g = ManagedRead::Getter(iface, property)) { - return g; - } - return ManagedRead::RuntimeGetter(obj, property); -} -} // namespace fleet_live_detail +// System.DateTime: ticks in the low 62 bits, the Kind in the top two. +struct DateTime { + uint64_t dateData; -// Digit.PrimeServer.Models.IProgressData (concrete: ProgressData, CargoProgressData) -struct IProgressData { - static Il2CppClass* get_class() + // Unix seconds, or empty for DateTime.MinValue (unset). + std::optional UnixSeconds() const { - static auto cls = fleet_live_detail::models_class("IProgressData"); - return cls; + const auto ticks = static_cast(dateData & 0x3FFFFFFFFFFFFFFFull); + if (ticks <= 0) + return std::nullopt; + return (ticks - 621355968000000000LL) / 10000000LL; } +}; - std::optional CurrentValue() - { - return ManagedRead::Value(fleet_live_detail::getter(get_class(), "CurrentValue", this), this); - } - std::optional MaxValue() - { - return ManagedRead::Value(fleet_live_detail::getter(get_class(), "MaxValue", this), this); - } +// Concrete: ProgressData, CargoProgressData. +struct IProgressData : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "IProgressData">; + + double CurrentValue() + { return Get<"CurrentValue", double>(); } + double MaxValue() + { return Get<"MaxValue", double>(); } }; -// Digit.PrimeServer.Models.CargoHoldData -struct CargoHoldData { - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "CargoHoldData"); - return class_helper; - } +struct CargoHoldData : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "CargoHoldData">; IProgressData* CurrentCargo() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "CurrentCargo"); - return (IProgressData*)ManagedRead::Object(getter, this); - } + { return Get<"CurrentCargo", IProgressData*>(); } IProgressData* ProtectedCargoProgress() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "ProtectedCargoProgress"); - return (IProgressData*)ManagedRead::Object(getter, this); - } + { return Get<"ProtectedCargoProgress", IProgressData*>(); } }; -// Digit.PrimeServer.Models.IMiningNodeParams. Amount isn't on the interface, so it's read off the runtime class. -struct IMiningNodeParams { - std::optional Amount() - { - return ManagedRead::Value(ManagedRead::RuntimeGetter(this, "Amount"), this); - } +struct IMiningNodeParams : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "IMiningNodeParams">; + + int64_t Amount() + { return Get<"Amount", int64_t>(); } }; -// Digit.PrimeServer.Models.IMiningSlot (concrete: FleetMiningSlot, MiningSlot) -struct IMiningSlot { - static Il2CppClass* get_class() - { - static auto cls = fleet_live_detail::models_class("IMiningSlot"); - return cls; - } +// Concrete: FleetMiningSlot, MiningSlot. +struct IMiningSlot : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "IMiningSlot">; - std::optional ParentId() - { - return ManagedRead::Value(fleet_live_detail::getter(get_class(), "ParentId", this), this); - } - std::optional ResourceId() - { - return ManagedRead::Value(fleet_live_detail::getter(get_class(), "ResourceId", this), this); - } - std::optional AmountMined() - { - return ManagedRead::Value(fleet_live_detail::getter(get_class(), "AmountMined", this), this); - } - std::optional OccupyTimeUtc() - { - return ManagedRead::Value(fleet_live_detail::getter(get_class(), "OccupyTimeUtc", this), this); - } + int64_t ParentId() + { return Get<"ParentId", int64_t>(); } + int64_t ResourceId() + { return Get<"ResourceId", int64_t>(); } + double AmountMined() + { return Get<"AmountMined", double>(); } + DateTime OccupyTimeUtc() + { return Get<"OccupyTimeUtc", DateTime>(); } IMiningNodeParams* PointData() - { - return (IMiningNodeParams*)ManagedRead::Object(fleet_live_detail::getter(get_class(), "PointData", this), this); - } + { return Get<"PointData", IMiningNodeParams*>(); } }; -// Digit.PrimeServer.Models.ITimerData (concrete: MiningSlotTimer and others) -struct ITimerData { - static Il2CppClass* get_class() - { - static auto cls = fleet_live_detail::models_class("ITimerData"); - return cls; - } +// Concrete: MiningSlotTimer and others. +struct ITimerData : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "ITimerData">; - // DateTime _dateData - std::optional DueTime() - { - return ManagedRead::Value(fleet_live_detail::getter(get_class(), "DueTime", this), this); - } + DateTime DueTime() + { return Get<"DueTime", DateTime>(); } }; diff --git a/mods/src/prime/FleetLocalViewController.h b/mods/src/prime/FleetLocalViewController.h index fef72cd8a..fed1e224b 100644 --- a/mods/src/prime/FleetLocalViewController.h +++ b/mods/src/prime/FleetLocalViewController.h @@ -1,72 +1,29 @@ #pragma once -#include - #include "ActionData.h" +#include "ShipActionPanelLocalViewController.h" #include "FleetPlayerData.h" #include "ShipBarItemLocalViewController.h" -struct FleetLocalViewController { -public: - __declspec(property(get = __get_fleet)) FleetPlayerData* fleet; - __declspec(property(get = __get__shipBarItemLocalViewController)) - ShipBarItemLocalViewController* shipBarItemLocalViewController; - - void CancelButtonClicked() - { - static auto CancelButtonWarn = true; - static auto CancelButtonMethod = get_class_helper().GetMethod("CancelButtonClicked"); - if (CancelButtonMethod) { - CancelButtonMethod(this); - } else if (CancelButtonWarn) { - CancelButtonWarn = false; - ErrorMsg::MissingMethod("FleetLocalViewController", "CancelButtonClicked"); - } - } +#include - bool RequestAction(IActionData target, ActionType type, int index, ActionBehaviour behaviourMask, - void* callback = nullptr) - { - static auto RequestActionMethod = - get_class_helper() - .GetMethodSpecial( - "RequestAction", [](int param_count, const Il2CppType**) { - if (param_count == 5) { - return true; - } - return false; - }); - static auto RequestActionWarn = true; +#include - if (RequestActionMethod) { - return RequestActionMethod(this, target, type, index, behaviourMask, callback); - } else if (!RequestActionWarn) { - RequestActionWarn = false; - ErrorMsg::MissingMethod("FleetLocalViewController", "RequestAction"); - } +struct FleetLocalViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Ships", "FleetLocalViewController">; - return false; - } + ShipActionPanelLocalViewController* shipActionPanelLocalViewController() + { return Field<"_shipActionPanelLocalViewController", ShipActionPanelLocalViewController*>(); } + FleetPlayerData* fleet() + { return Get<"fleet", FleetPlayerData*>(); } + ShipBarItemLocalViewController* shipBarItemLocalViewController() + { return Field<"_shipBarItemLocalViewController", ShipBarItemLocalViewController*>(); } -private: - friend class ObjectFinder; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Ships", "FleetLocalViewController"); - return class_helper; - } - -public: - FleetPlayerData* __get_fleet() - { - static auto field = get_class_helper().GetProperty("fleet"); - return field.GetRaw(this); - } + void CancelButtonClicked() + { Call<"CancelButtonClicked">(); } - ShipBarItemLocalViewController* __get__shipBarItemLocalViewController() - { - static auto field = get_class_helper().GetField("_shipBarItemLocalViewController"); - return *(ShipBarItemLocalViewController**)((ptrdiff_t)this + field.offset()); - } + // callback is an Action; null is accepted. + bool RequestAction(IActionData target, ActionType type, int64_t index, ActionBehaviour behaviourMask, + void* callback = nullptr) + { return Call<"RequestAction", bool>(target, type, index, behaviourMask, callback); } }; diff --git a/mods/src/prime/FleetMeshSelector.h b/mods/src/prime/FleetMeshSelector.h index 5429c6e06..cd77a5afb 100644 --- a/mods/src/prime/FleetMeshSelector.h +++ b/mods/src/prime/FleetMeshSelector.h @@ -1,22 +1,12 @@ #pragma once -#include +#include "GameObject.h" -struct FleetMeshSelector { -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "FleetMeshSelector"); - return class_helper; - } +#include - Il2CppObject* LoadedObject() - { - static auto loaded_object = get_class_helper().GetProperty("LoadedObject"); - if (!loaded_object.isValidHelper()) { - return nullptr; - } - return loaded_object.GetRaw(this); - } +struct FleetMeshSelector : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "FleetMeshSelector">; + + GameObject* LoadedObject() + { return Get<"LoadedObject", GameObject*>(); } }; diff --git a/mods/src/prime/FleetPlayerData.h b/mods/src/prime/FleetPlayerData.h index 0a020be96..66340354b 100644 --- a/mods/src/prime/FleetPlayerData.h +++ b/mods/src/prime/FleetPlayerData.h @@ -1,20 +1,17 @@ #pragma once -#include "errormsg.h" - #include "BattleTargetData.h" #include "HullSpec.h" #include "RecallRequirement.h" #include "CanRepairRequirement.h" #include "FleetLiveData.h" -#include "ManagedRead.h" + +#include #include #include -struct Ship; - -enum class FleetState { +enum class FleetState : int32_t { Unknown = 0, IdleInSpace = 1, Docked = 2, @@ -42,198 +39,109 @@ enum class FleetState { Deployed = 8133, CanLocate = 8135 }; - -struct FleetPlayerData { -public: - __declspec(property(get = __get_CurrentState)) FleetState CurrentState; - __declspec(property(get = __get_PreviousState)) FleetState PreviousState; - __declspec(property(get = __get_Id)) uint64_t Id; - __declspec(property(get = __get_Index)) int Index; - __declspec(property(get = __get_Hull)) HullSpec* Hull; - __declspec(property(get = __get_Address)) void* Address; - __declspec(property(get = __get_Level)) int64_t Level; - __declspec(property(get = __get_HasShip)) bool HasShip; - __declspec(property(get = __get_HullId)) int64_t HullId; - __declspec(property(get = __get_Ship)) ::Ship* Ship; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "FleetPlayerData"); - return class_helper; - } +struct FleetPlayerData : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "FleetPlayerData">; -public: - int __get_Index() - { - static auto property = get_class_helper().GetProperty("Index"); - auto* value = property.Get(this); - return value ? *value : -1; - } - - HullSpec* __get_Hull() - { - static auto field = get_class_helper().GetProperty("Hull"); - return field.GetRaw(this); - } - void* __get_Address() - { - static auto field = get_class_helper().GetProperty("Address"); - return field.GetRaw(this); - } - FleetState __get_CurrentState() - { - static auto field = get_class_helper().GetProperty("CurrentState"); - auto* value = field.Get(this); - return value ? *value : FleetState::Unknown; - } - FleetState __get_PreviousState() - { - static auto field = get_class_helper().GetProperty("PreviousState"); - auto* value = field.Get(this); - return value ? *value : FleetState::Unknown; - } - - uint64_t __get_Id() - { - static auto field = get_class_helper().GetProperty("Id"); - auto* value = field.Get(this); - return value ? *value : 0; - } - int64_t __get_Level() - { - static auto field = get_class_helper().GetProperty("Level"); - auto* value = field.Get(this); - return value ? *value : 0; - } - - bool __get_HasShip() - { - static auto field = get_class_helper().GetProperty("HasShip"); - auto* value = field.Get(this); - return value ? *value : false; - } - int64_t __get_HullId() - { - static auto property = get_class_helper().GetProperty("HullId"); - auto* value = property.Get(this); - return value ? *value : 0; - } - ::Ship* __get_Ship() - { - static auto property = get_class_helper().GetProperty("Ship"); - return property.GetRaw(this); + int32_t Index() + { return Get<"Index", int32_t>(); } + HullSpec* Hull() + { return Get<"Hull", HullSpec*>(); } + void* Address() + { return Get<"Address", void*>(); } + FleetState CurrentState() + { return Get<"CurrentState", FleetState>(); } + FleetState PreviousState() + { return Get<"PreviousState", FleetState>(); } + uint64_t Id() + { return Get<"Id", uint64_t>(); } + int64_t Level() + { return Get<"Level", int64_t>(); } + bool HasShip() + { return Get<"HasShip", bool>(); } + int64_t HullId() + { return Get<"HullId", int64_t>(); } + bool HasAppliedTech() + { + struct BaseShip : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "BaseShip">; + int64_t Id() + { return Get<"Id", int64_t>(); } + }; + struct Slot : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "EntitySlot">; + int64_t SpecId() + { return Get<"SlotSpecId", int64_t>(); } + bool HasItem() + { + static const auto* getter = Class::Method("get_SlotItemId", 0).Info(); + if (!getter || (getter->flags & METHOD_ATTRIBUTE_STATIC) + || getter->return_type->type != IL2CPP_TYPE_GENERICINST) + return false; + // Runtime invocation boxes Nullable as null or its value, without assuming its native layout. + return il2cpp::detail::InvokeBoxed(getter, this, nullptr) != nullptr; + } + }; + struct Manager : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ForbiddenTechs", "ForbiddenTechManager">; + bool ActiveSlots(int64_t ship_id, il2cpp::List*& slots) + { return Call<"TryGetActiveForbiddenTechSlot", bool>(ship_id, std::ref(slots)); } + }; + auto* ship = Get<"Ship", BaseShip*>(); + auto* manager = Manager::Class::Get<"Instance", Manager*>(); + il2cpp::List* slots = nullptr; + if (!ship || !manager || !manager->ActiveSlots(ship->Id(), slots) || !slots) + return false; + for (auto* slot : *slots) { + if (!slot) + continue; + const auto spec = slot->SpecId(); + if ((spec == 3502081615 || spec == 953301906) && slot->HasItem()) + return true; + } + return false; } - // live state, empty when missing - std::optional IsHome() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "IsHome"); - return ManagedRead::Value(getter, this); - } - std::optional IsDeployed() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "IsDeployed"); - return ManagedRead::Value(getter, this); - } - std::optional IsMining() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "IsMining"); - return ManagedRead::Value(getter, this); - } - std::optional IsRepairing() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "IsRepairing"); - return ManagedRead::Value(getter, this); - } - std::optional IsDamaged() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "IsDamaged"); - return ManagedRead::Value(getter, this); - } - std::optional IsDestroyed() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "IsDestroyed"); - return ManagedRead::Value(getter, this); - } - std::optional DrydockID() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "DrydockID"); - return ManagedRead::Value(getter, this); - } - std::optional CargoResourceFillLevel() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "CargoResourceFillLevel"); - return ManagedRead::Value(getter, this); - } - CargoHoldData* GetCargoHoldData() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "CargoHoldData"); - return (CargoHoldData*)ManagedRead::Object(getter, this); - } + // Live state, read for fleet sync. + bool IsHome() + { return Get<"IsHome", bool>(); } + bool IsDeployed() + { return Get<"IsDeployed", bool>(); } + bool IsMining() + { return Get<"IsMining", bool>(); } + bool IsRepairing() + { return Get<"IsRepairing", bool>(); } + bool IsDamaged() + { return Get<"IsDamaged", bool>(); } + bool IsDestroyed() + { return Get<"IsDestroyed", bool>(); } + int32_t DrydockID() + { return Get<"DrydockID", int32_t>(); } + float CargoResourceFillLevel() + { return Get<"CargoResourceFillLevel", float>(); } + CargoHoldData* CargoHold() + { return Get<"CargoHoldData", CargoHoldData*>(); } IMiningSlot* MiningData() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "MiningData"); - return (IMiningSlot*)ManagedRead::Object(getter, this); - } + { return Get<"MiningData", IMiningSlot*>(); } ITimerData* Timer() - { - static auto getter = ManagedRead::Getter(get_class_helper().get_cls(), "Timer"); - return (ITimerData*)ManagedRead::Object(getter, this); - } - // no property for the mining timer, so read the private field + { return Get<"Timer", ITimerData*>(); } + // No property for the mining timer, so read the private field. ITimerData* MiningSlotTimer() - { - static auto field = get_class_helper().GetField("_miningSlotTimer"); - return (ITimerData*)ManagedRead::FieldObject(this, field.get_info()); - } - // damage on the fleet's ship (BaseShip), so a docked ship's hull is known without the server + { return Field<"_miningSlotTimer", ITimerData*>(); } + float GetMiningRate() + { return Call<"GetMiningRate", float>(); } + // Damage on the fleet's ship, so a docked ship's hull is known without the server. Empty without a ship. std::optional HullDamage() { - static ShipGetter getter{"HullDamage"}; - return getter.Read(this->Ship); - } - std::optional GetMiningRate() - { - static auto method = get_class_helper().GetMethodInfo("GetMiningRate", 0); - return ManagedRead::Value(method, this); + // IShipModel, read on the ship's runtime class. + struct Ship : il2cpp::Object { + float HullDamage() + { return Get<"HullDamage", float>(); } + }; + auto* ship = Get<"Ship", Ship*>(); + return ship ? std::optional(ship->HullDamage()) : std::nullopt; } -private: - // the ship's concrete class is only known at runtime; look the getter up once per class - struct ShipGetter { - const char* property; - Il2CppClass* cls = nullptr; - const MethodInfo* getter = nullptr; - - std::optional Read(void* ship) - { - if (!ship) { - return std::nullopt; - } - if (auto* klass = ((Il2CppObject*)ship)->klass; klass != cls) { - cls = klass; - getter = ManagedRead::Getter(klass, property); - } - return ManagedRead::Value(getter, ship); - } - }; - -public: int64_t GetLocaId() - { - static auto GetLocaIdWarn = true; - static auto GetLocaIdMethod = get_class_helper().GetMethod("GetLocaId"); - - if (GetLocaIdMethod) { - return GetLocaIdMethod(this); - } - if (GetLocaIdWarn) { - GetLocaIdWarn = false; - ErrorMsg::MissingMethod("FleetPlayerData", "GetLocaId"); - } - - return 0; - } + { return Call<"GetLocaId", int64_t>(); } }; diff --git a/mods/src/prime/FleetsManager.h b/mods/src/prime/FleetsManager.h index b10d324fc..8a00a1c90 100644 --- a/mods/src/prime/FleetsManager.h +++ b/mods/src/prime/FleetsManager.h @@ -1,156 +1,51 @@ #pragma once -#include "CallbackContainer.h" #include "FleetDeployedData.h" #include "FleetPlayerData.h" -#include "HullSpec.h" -#include "IEnumerator.h" #include "MonoSingleton.h" -#include "Vector3.h" -#include +#include -struct FleetsManager : MonoSingleton { - friend struct MonoSingleton; - -public: - __declspec(property(get = __get_TargetFleetData)) FleetDeployedData* targetFleetData; - -public: - class IEnumerator_Tow - { - public: - bool MoveNext() - { - static auto MoveNext = get_class_helper().GetMethodSpecial("MoveNext"); - static auto MoveWarn = true; - - if (MoveNext) { - return MoveNext(this); - } else if (MoveWarn) { - MoveWarn = false; - ErrorMsg::MissingMethod("IEnumerator_Tow", "MoveNext"); - } +#include - return false; - } - - private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "", "FleetsManager.d__192"); - return class_helper; - } - }; +struct FleetsManager : MonoSingleton { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.FleetManagement", "FleetsManager">; void RequestViewFleet(FleetPlayerData* fleetData, bool showSystemInfo = false) - { - static auto RequestViewFleet = - get_class_helper().GetMethod("RequestViewFleet"); - static auto RequestViewWarn = true; - if (RequestViewFleet) { - RequestViewFleet(this, fleetData, showSystemInfo); - } else if (RequestViewWarn) { - RequestViewWarn = false; - ErrorMsg::MissingMethod("FleetsManager", "RequestViewFleet"); - } - } - - void RecallFleet(long fleetId) - { - static auto RecallFleet = get_class_helper().GetMethod("RecallFleet"); - static auto RecallWarn = true; - - if (RecallFleet) { - auto ptr = CallbackContainer::Create(); - RecallFleet(this, fleetId, ptr); - } else if (RecallWarn) { - RecallWarn = true; - ErrorMsg::MissingMethod("FleetsManager", "RecallFleet"); - } - } + { Call<"RequestViewFleet">(fleetData, showSystemInfo); } - IEnumerator_Tow* Tow(long towedFleetId, long towingFleetId, Vector3* targetPosition) - { - static auto TowMethod = - get_class_helper().GetMethod("Tow"); - static auto TowWarn = true; + // The int overload takes a fleet slot; the long overload takes a fleet id. + FleetPlayerData* GetFleetPlayerData(int32_t idx) + { return Call<"GetFleetPlayerData", FleetPlayerData*>(idx); } - if (TowMethod) { - auto ptr = CallbackContainer::Create(); - return TowMethod(this, towedFleetId, towingFleetId, nullptr, targetPosition, ptr); - } else if (TowWarn) { - TowWarn = false; - ErrorMsg::MissingMethod("FleetsManager", "Tow"); - } + FleetPlayerData* GetSelectedFleetData() + { return Call<"GetSelectedFleetData", FleetPlayerData*>(); } - return nullptr; - } - - FleetPlayerData* GetFleetPlayerData(int idx) - { - static auto GetFleetPlayerDataMethod = - get_class_helper().GetMethod("GetFleetPlayerData"); - static auto GetFleetPlayerDataWarn = true; - - if (GetFleetPlayerDataMethod) { - return GetFleetPlayerDataMethod(this, idx); - } else if (GetFleetPlayerDataWarn) { - GetFleetPlayerDataWarn = false; - ErrorMsg::MissingMethod("FleetPlayerData", "GetFleetPlayerData"); - } - - return nullptr; - } + FleetDeployedData* targetFleetData() + { return Field<"_targetFleetData", FleetDeployedData*>(); } + // _fleetServiceCache is a CachedService struct whose HasService getter resolves and + // stores the service lazily. It has to run on the field in place, not on a boxed copy, so this + // stays on the raw runtime API. bool HasFleetService() { - static auto* fleet_service_cache = - il2cpp_class_get_field_from_name(get_class_helper().get_cls(), "_fleetServiceCache"); - static auto FleetServiceCacheWarn = true; - if (!fleet_service_cache) { - if (FleetServiceCacheWarn) { - FleetServiceCacheWarn = false; - spdlog::error("Unable to find field 'FleetsManager->_fleetServiceCache'"); - } + static FieldInfo* const field = Class::Raw() ? il2cpp_class_get_field_from_name(Class::Raw(), "_fleetServiceCache") + : nullptr; + static const MethodInfo* const has_service = + field ? il2cpp_class_get_method_from_name(il2cpp_class_from_type(il2cpp_field_get_type(field)), + "get_HasService", 0) + : nullptr; + if (!has_service) { return false; } - auto* cache = reinterpret_cast(reinterpret_cast(this) + fleet_service_cache->offset); - if (*cache) { + auto* cache = reinterpret_cast(this) + il2cpp_field_get_offset(field); + if (*reinterpret_cast(cache)) { return true; } - static auto* HasServiceMethod = [] { - auto* cache_class = il2cpp_class_from_type(il2cpp_field_get_type(fleet_service_cache)); - return cache_class ? il2cpp_class_get_method_from_name(cache_class, "get_HasService", 0) : nullptr; - }(); - static auto HasServiceWarn = true; - if (!HasServiceMethod) { - if (HasServiceWarn) { - HasServiceWarn = false; - ErrorMsg::MissingMethod("CachedService", "get_HasService"); - } - return false; - } - Il2CppException* exception = nullptr; - auto* result = il2cpp_runtime_invoke(HasServiceMethod, cache, nullptr, &exception); + auto* result = il2cpp_runtime_invoke(has_service, cache, nullptr, &exception); return !exception && result && *static_cast(il2cpp_object_unbox(result)); } - - FleetDeployedData* __get_TargetFleetData() - { - static auto field = get_class_helper().GetField("_targetFleetData").offset(); - return *(FleetDeployedData**)((char*)this + field); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.FleetManagement", "FleetsManager"); - return class_helper; - } }; diff --git a/mods/src/prime/FullScreenChatViewController.h b/mods/src/prime/FullScreenChatViewController.h index 3542da2ec..9a1687400 100644 --- a/mods/src/prime/FullScreenChatViewController.h +++ b/mods/src/prime/FullScreenChatViewController.h @@ -3,33 +3,13 @@ #include "ChatMessageListLocalViewController.h" #include "TabBarViewController.h" -class FullScreenChatViewController -{ -public: - __declspec(property(get = __get__messageList)) ChatMessageListLocalViewController* _messageList; - __declspec(property(get = __get__categoriesTabBarViewController)) - TabBarViewController* _categoriesTabBarViewController; +#include -private: - friend class ObjectFinder; +struct FullScreenChatViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Chat", "FullScreenChatViewController">; -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Chat", "FullScreenChatViewController"); - return class_helper; - } - - ChatMessageListLocalViewController* __get__messageList() - { - static auto field = get_class_helper().GetField("_messageList").offset(); - return *(ChatMessageListLocalViewController**)((uintptr_t)this + field); - } - - TabBarViewController* __get__categoriesTabBarViewController() - { - static auto field = get_class_helper().GetField("_categoriesTabBarViewController").offset(); - return *(TabBarViewController**)((uintptr_t)this + field); - } -}; \ No newline at end of file + ChatMessageListLocalViewController* messageList() + { return Field<"_messageList", ChatMessageListLocalViewController*>(); } + TabBarViewController* categoriesTabBarViewController() + { return Field<"_categoriesTabBarViewController", TabBarViewController*>(); } +}; diff --git a/mods/src/prime/GameObject.h b/mods/src/prime/GameObject.h index c0bda987b..0d6febde7 100644 --- a/mods/src/prime/GameObject.h +++ b/mods/src/prime/GameObject.h @@ -2,91 +2,33 @@ #include "str_utils.h" -#include +#include #include -struct GameObject { -public: - __declspec(property(get = __get_activeInHierarchy)) bool activeInHierarchy; - __declspec(property(get = __get_scene)) void* scene; +struct GameObject : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "GameObject">; std::string Name() { - static auto field = get_class_helper().GetParent("Object").GetProperty("name"); - auto str = field.GetRaw(this); - return str != nullptr ? to_string(str) : std::string{}; + auto* name = Get<"name", Il2CppString*>(); + return name ? to_string(name) : std::string{}; } - + bool activeInHierarchy() + { return Get<"activeInHierarchy", bool>(); } void SetActive(bool active) - { - static auto method = get_class_helper().GetMethod("SetActive"); - if (method != nullptr) { - method(this, active); - } - } - - template T* GetComponentFastPath2() - { - static auto get_component = get_class_helper().GetMethodInfoSpecial("GetComponent", [](auto count, auto params) { - if (count != 1) { - return false; - } - auto p1 = params[0]->type; - if (p1 == IL2CPP_TYPE_CLASS) { - return true; - } - return false; - }); - - static auto type = T::get_class_helper().GetType(); - - Il2CppException* exception = nullptr; - void* params[1] = {type}; - auto result = il2cpp_runtime_invoke(get_component, this, params, &exception); - - if (exception) { - return nullptr; - } else { - return (T*)result; - } - } - - template T* GetComponentFastPath() - { - static auto get_component = - get_class_helper().GetMethodSpecial("GetComponent", [](auto count, auto params) { - if (count != 1) { - return false; - } - auto p1 = params[0].parameter_type->type; - if (p1 == IL2CPP_TYPE_CLASS) { - return true; - } - return false; - }); - - static auto type = T::get_class_helper().GetType(); - - return get_component(this, type); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "GameObject"); - return class_helper; - } + { Call<"SetActive">(active); } -public: - bool __get_activeInHierarchy() + // Passing System.Type selects the (Type) overloads over the string and generic ones. A null + // Type would throw in managed code, so a missing class yields null instead. + template T* GetComponent() { - static auto field = get_class_helper().GetProperty("activeInHierarchy"); - return field.Get(this); + auto* type = T::Class::TypeObject(); + return type ? Call<"GetComponent", T*>(type) : nullptr; } - void* __get_scene() + template T* AddComponent() { - static auto field = get_class_helper().GetProperty("scene"); - return *field.Get(this); + auto* type = T::Class::TypeObject(); + return type ? Call<"AddComponent", T*>(type) : nullptr; } -}; \ No newline at end of file +}; diff --git a/mods/src/prime/GenericButtonContext.h b/mods/src/prime/GenericButtonContext.h index 68562703c..b003b9b46 100644 --- a/mods/src/prime/GenericButtonContext.h +++ b/mods/src/prime/GenericButtonContext.h @@ -1,34 +1,12 @@ #pragma once -#include +#include -class GenericButtonContext -{ -public: - __declspec(property(get = __get_Interactable, put = __set_Interactable)) bool Interactable; +struct GenericButtonContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "GenericButtonContext">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "GenericButtonContext"); - return class_helper; - } - -public: - bool __get_Interactable() - { - auto field = get_class_helper().GetProperty("Interactable"); - if (field.isValidHelper()) { - return *field.Get(this); - } - - return false; - } - void __set_Interactable(bool v) - { - auto field = get_class_helper().GetProperty("Interactable"); - if (field.isValidHelper()) { - field.SetRaw(this, v); - } - } + bool Interactable() + { return Get<"Interactable", bool>(); } + void SetInteractable(bool value) + { Set<"Interactable">(value); } }; diff --git a/mods/src/prime/GenericButtonWidget.h b/mods/src/prime/GenericButtonWidget.h index 93b4cece0..87ade7771 100644 --- a/mods/src/prime/GenericButtonWidget.h +++ b/mods/src/prime/GenericButtonWidget.h @@ -1,29 +1,17 @@ #pragma once -#include - #include "GenericButtonContext.h" #include "SemaphoreButtonListener.h" +#include "TextLocalizer.h" #include "Widget.h" -struct GenericButtonWidget : public Widget { -public: - __declspec(property(get = __get_SemaphoreButtonListener)) SemaphoreButtonListener* SemaphoreListener; - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "GenericButtonWidget"); - return class_helper; - } - -private: - friend class ObjectFinder; - friend struct Widget; +struct GenericButtonWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "GenericButtonWidget">; -public: - SemaphoreButtonListener* __get_SemaphoreButtonListener() - { - static auto field = get_class_helper().GetField("_semaphoreButtonListener").offset(); - return *(SemaphoreButtonListener**)((char*)this + field); - } + SemaphoreButtonListener* SemaphoreListener() + { return Field<"_semaphoreButtonListener", SemaphoreButtonListener*>(); } + TextLocalizer* mainText() + { return Field<"_mainText", TextLocalizer*>(); } + bool Interactable() + { return Get<"Interactable", bool>(); } }; diff --git a/mods/src/prime/GenericRewardsScreenViewController.h b/mods/src/prime/GenericRewardsScreenViewController.h index 9209d7a23..b3a33ed0a 100644 --- a/mods/src/prime/GenericRewardsScreenViewController.h +++ b/mods/src/prime/GenericRewardsScreenViewController.h @@ -1,40 +1,12 @@ #pragma once -#include "errormsg.h" -#include +#include -struct GenericRewardsScreenViewController { -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.SharedFeatures", "GenericRewardsScreenViewController"); - return class_helper; - } +struct GenericRewardsScreenViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.SharedFeatures", "GenericRewardsScreenViewController">; void OnCollectClicked() - { - static auto OnCollectClickedWarn = true; - static auto OnCollectClickedMethod = - get_class_helper().GetMethod("OnCollectClicked"); - - if (OnCollectClickedMethod) { - OnCollectClickedMethod(this); - } else if (OnCollectClickedWarn) { - OnCollectClickedWarn = false; - ErrorMsg::MissingMethod("GenericRewardsScreenViewController", "OnCollectClicked"); - } - } - + { Call<"OnCollectClicked">(); } bool IsActive() - { - auto IsActiveMethod = get_class_helper().GetMethod("IsActive"); - auto IsActiveWarn = true; - - if (IsActiveMethod) { - return IsActiveMethod(this); - } else if (IsActiveWarn) { - IsActiveWarn = false; - ErrorMsg::MissingMethod("GenericRewardsScreenViewController", "IsActive"); - } - } + { return Call<"IsActive", bool>(); } }; diff --git a/mods/src/prime/HousingObjectViewerWidget.h b/mods/src/prime/HousingObjectViewerWidget.h index acca22c2f..1e21655f0 100644 --- a/mods/src/prime/HousingObjectViewerWidget.h +++ b/mods/src/prime/HousingObjectViewerWidget.h @@ -1,22 +1,7 @@ #pragma once -#include - -#include "Widget.h" - -#include "NavigationInteractionUIContext.h" #include "ObjectViewerBaseWidget.h" -#include "ScanEngageButtonsWidget.h" - -struct HousingObjectViewerWidget : public ObjectViewerBaseWidget { -public: - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "HousingObjectViewerWidget"); - return class_helper; - } +struct HousingObjectViewerWidget : ObjectViewerBaseWidget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "HousingObjectViewerWidget">; }; diff --git a/mods/src/prime/HttpJob.h b/mods/src/prime/HttpJob.h index 9aa3c82bf..efe7e2e27 100644 --- a/mods/src/prime/HttpJob.h +++ b/mods/src/prime/HttpJob.h @@ -1,11 +1,13 @@ #pragma once -#include - #include "HttpRequest.h" #include "UnityWebRequest.h" -enum class HttpJobState { +#include + +#include + +enum class HttpJobState : int32_t { None = 0, Queued = 1, RequestSending = 2, @@ -16,42 +18,16 @@ enum class HttpJobState { Complete = 7 }; -struct HttpJob { -public: - __declspec(property(get = __get_Request)) HttpRequest* Request; - __declspec(property(get = __get_State, put = __set_State)) HttpJobState State; - __declspec(property(get = __get_UnityRequest)) UnityWebRequest* UnityRequest; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Engine.HttpClient.Runtime", "Digit.Engine.HttpClient", "HttpJob"); - return class_helper; - } - -public: - HttpRequest* __get_Request() - { - static auto field = get_class_helper().GetField("Request"); - return *(HttpRequest**)((ptrdiff_t)this + field.offset()); - } - - void __set_State(HttpJobState state) - { - static auto field = get_class_helper().GetField("State"); - *(HttpJobState*)((ptrdiff_t)this + field.offset()) = state; - } - - HttpJobState __get_State() - { - static auto field = get_class_helper().GetField("State"); - return *(HttpJobState*)((ptrdiff_t)this + field.offset()); - } - - UnityWebRequest* __get_UnityRequest() - { - static auto field = get_class_helper().GetField("UnityRequest"); - return *(UnityWebRequest**)((ptrdiff_t)this + field.offset()); - } +struct HttpJob : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Engine.HttpClient.Runtime", "Digit.Engine.HttpClient", "HttpJob">; + + HttpRequest* Request() + { return Field<"Request", HttpRequest*>(); } + UnityWebRequest* UnityRequest() + { return Field<"UnityRequest", UnityWebRequest*>(); } + HttpJobState State() + { return Field<"State", HttpJobState>(); } + // Writes the field directly and bypasses the managed SetState(HttpJobState). + void SetState(HttpJobState state) + { SetField<"State">(state); } }; diff --git a/mods/src/prime/HttpRequest.h b/mods/src/prime/HttpRequest.h index eed2c77f5..207afa2c5 100644 --- a/mods/src/prime/HttpRequest.h +++ b/mods/src/prime/HttpRequest.h @@ -1,21 +1,13 @@ #pragma once -#include +#include -struct HttpRequest { -public: - const Il2CppChar* get_URL() - { - static auto prop = get_class_helper().GetProperty("URL"); - auto s = prop.Get((void*)this); - return il2cpp_string_chars(s); - } +struct HttpRequest : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Engine.HttpClient.Runtime", "Digit.Engine.HttpClient", "HttpRequest">; -private: - static IL2CppClassHelper& get_class_helper() + const Il2CppChar* get_URL() { - static auto class_helper = - il2cpp_get_class_helper("Digit.Engine.HttpClient.Runtime", "Digit.Engine.HttpClient", "HttpRequest"); - return class_helper; + auto* url = Get<"URL", Il2CppString*>(); + return url ? il2cpp_string_chars(url) : nullptr; } }; diff --git a/mods/src/prime/HttpResponse.h b/mods/src/prime/HttpResponse.h index 819ac9cec..c0515c04f 100644 --- a/mods/src/prime/HttpResponse.h +++ b/mods/src/prime/HttpResponse.h @@ -1,39 +1,14 @@ #pragma once -#include - #include "HttpRequest.h" -struct HttpResponse { -public: - struct System_Byte_array { - Il2CppObject obj; - Il2CppArrayBounds* bounds; - il2cpp_array_size_t max_length; - uint8_t m_Items[65535]; - }; +#include - __declspec(property(get = __get_Body)) Il2CppString* Body; +struct HttpResponse : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Engine.HttpClient.Runtime", "Digit.Engine.HttpClient", "HttpResponse">; HttpRequest* get_Request() - { - static auto prop = get_class_helper().GetProperty("Request"); - auto s = prop.GetRaw((void*)this); - return s; - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Engine.HttpClient.Runtime", "Digit.Engine.HttpClient", "HttpResponse"); - return class_helper; - } - -public: - Il2CppString* __get_Body() - { - static auto prop = get_class_helper().GetProperty("Body"); - return prop.GetRaw(this); - } + { return Get<"Request", HttpRequest*>(); } + Il2CppString* Body() + { return Get<"Body", Il2CppString*>(); } }; diff --git a/mods/src/prime/Hub.h b/mods/src/prime/Hub.h index 3747f88b2..d4ab5612a 100644 --- a/mods/src/prime/Hub.h +++ b/mods/src/prime/Hub.h @@ -1,8 +1,10 @@ #pragma once -#include +#include -enum class SectionID { +#include + +enum class SectionID : int32_t { ArtifactHall_Inventory = -2058246328, Alliance_Contribution = -2035860712, Alliance_Diplomacy_List = -1998741427, @@ -143,195 +145,84 @@ enum class SectionID { Tournament_Group_Selection = 2138751318, }; -struct SectionStorage { -public: - void* GetState(SectionID section) - { - static auto GetStateMethod = get_class_helper().GetMethod("GetState"); - static auto GetStateWarn = true; - - if (GetStateMethod) { - return GetStateMethod(this, section); - } else if (GetStateWarn) { - GetStateWarn = false; - ErrorMsg::MissingMethod("SectionStorage", "GetState"); - } +struct SectionStorage : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Sections", "SectionStorage">; - return nullptr; - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Sections", "SectionStorage"); - return class_helper; - } + void* GetState(SectionID section) + { return Call<"GetState", void*>(section); } }; -struct SectionNavHistory { -public: - bool Contains(SectionID section) - { - static auto ContainsMethod = get_class_helper().GetMethod("ContainsSectionID"); - static auto ContainsWarn = true; - - if (ContainsMethod) { - return ContainsMethod(this, section); - } else if (ContainsWarn) { - ContainsWarn = false; - ErrorMsg::MissingMethod("SectionNavHistory", "Contains"); - } +struct SectionNavHistory : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Sections", "SectionNavHistory">; - return false; - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Sections", "SectionNavHistory"); - return class_helper; - } + bool Contains(SectionID section) + { return Call<"ContainsSectionID", bool>(section); } }; -struct SectionManager { -public: - __declspec(property(get = __get_CurrentSection)) SectionID CurrentSection; - __declspec(property(get = __get__sectionStorage)) SectionStorage* _sectionStorage; - __declspec(property(get = __get__history)) SectionNavHistory* _history; - - void TriggerSectionChange(SectionID nextSectionID, void* args, bool forcedSectionChange = false, - bool isGoBackStep = false, bool allowSameSection = false) - { - static auto triggerWarn = true; - static auto triggerMethod = - get_class_helper().GetMethod( - "TriggerSectionChange"); - - if (triggerMethod) { - triggerMethod(this, nextSectionID, args, forcedSectionChange, isGoBackStep, allowSameSection); - } else if (triggerWarn) { - triggerWarn = false; - ErrorMsg::MissingMethod("SectionManager", "TriggerSectionChange"); - } - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Sections", "SectionManager"); - return class_helper; - } +struct SectionManager : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Sections", "SectionManager">; -public: - SectionID __get_CurrentSection() - { - static auto field = get_class_helper().GetProperty("CurrentSection"); - return *field.Get(this); - } + SectionID CurrentSection() + { return Get<"CurrentSection", SectionID>(); } + SectionStorage* sectionStorage() + { return Field<"_sectionStorage", SectionStorage*>(); } + SectionNavHistory* history() + { return Field<"_history", SectionNavHistory*>(); } - SectionStorage* __get__sectionStorage() + // The (SectionData) overload is not used here; args is any managed object or null. + bool TriggerSectionChange(SectionID nextSectionID, void* args, bool forcedSectionChange = false, + bool isGoBackStep = false, bool allowSameSection = false) { - static auto field = get_class_helper().GetField("_sectionStorage"); - return *(SectionStorage**)((ptrdiff_t)this + field.offset()); + return Call<"TriggerSectionChange", bool>(nextSectionID, args, forcedSectionChange, isGoBackStep, + allowSameSection); } +}; - SectionNavHistory* __get__history() - { - static auto field = get_class_helper().GetField("_history"); - return *(SectionNavHistory**)((ptrdiff_t)this + field.offset()); - } +struct GSServiceRegistry : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.Networking.Core", "GSServiceRegistry">; }; -struct GSServiceRegistry { -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.Networking.Core", "GSServiceRegistry"); - return class_helper; - } +// Digit.Client.Core.CustomReloadArgs and Nullable, laid out as the runtime does. +struct CustomReloadArgs { + Il2CppObject* SectionArgs; + bool ForceFakeReload; +}; +struct NullableCustomReloadArgs { + bool hasValue; + CustomReloadArgs value; }; -struct PrimeApp { - void Quit() - { - static auto quit = get_class_helper().GetMethod("Quit"); - static auto warn = true; - if (quit) { - quit(this); - } else if (warn) { - warn = false; - ErrorMsg::MissingMethod("PrimeApp", "Quit"); - } - } +struct PrimeApp : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Core", "PrimeApp">; + void Quit() + { Call<"Quit">(); } void Reload() - { - struct CustomReloadArgs { - void* SectionArgs; - bool ForceFakeReload; - }; - struct NullableCustomReloadArgs { - bool hasValue; - CustomReloadArgs value; - }; - - static auto reload = get_class_helper().GetMethod("Reload"); - static auto warn = true; - if (reload) { - reload(this, false, {}, false); - } else if (warn) { - warn = false; - ErrorMsg::MissingMethod("PrimeApp", "Reload"); - } - } - + { Call<"Reload">(false, NullableCustomReloadArgs{}, false); } GSServiceRegistry* get_Services() - { - static auto field = get_class_helper().GetProperty("Services"); - return field.GetRaw(this); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Core", "PrimeApp"); - return class_helper; - } + { return Get<"Services", GSServiceRegistry*>(); } }; +// Digit.Client.Core.Hub is a static class. struct Hub { - static SectionManager* get_SectionManager() - { - static auto field = get_class_helper().GetProperty("SectionManager"); - return field.GetRaw(nullptr); - } + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Core", "Hub">; + static SectionManager* get_SectionManager() + { return Class::Get<"SectionManager", SectionManager*>(); } static PrimeApp* get_App() - { - static auto field = get_class_helper().GetProperty("App"); - return field.GetRaw(nullptr); - } + { return Class::Get<"App", PrimeApp*>(); } static bool IsInChat() { - auto current_section = Hub::get_SectionManager()->CurrentSection; + auto current_section = get_SectionManager()->CurrentSection(); return current_section == SectionID::Chat_Private_Message || current_section == SectionID::Chat_Alliance || current_section == SectionID::Chat_Main || current_section == SectionID::Chat_Private_List; } static bool IsInSystemOrGalaxyOrStarbase() { - auto current_section = Hub::get_SectionManager()->CurrentSection; + auto current_section = get_SectionManager()->CurrentSection(); return current_section == SectionID::Navigation_Galaxy || current_section == SectionID::Navigation_System || current_section == SectionID::Starbase_Interior || current_section == SectionID::Starbase_Exterior; } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Core", "Hub"); - return class_helper; - } - -public: }; diff --git a/mods/src/prime/HullSpec.h b/mods/src/prime/HullSpec.h index 3da8975c2..dae14687c 100644 --- a/mods/src/prime/HullSpec.h +++ b/mods/src/prime/HullSpec.h @@ -1,8 +1,10 @@ #pragma once -#include +#include -enum class HullType { +#include + +enum class HullType : int32_t { Any = -1, Destroyer = 0, Survey = 1, @@ -12,43 +14,26 @@ enum class HullType { ArmadaTarget = 5 }; -struct HullSpec { -public: - __declspec(property(get = __get_Id)) long Id; - __declspec(property(get = __get_Name)) Il2CppString* Name; - __declspec(property(get = __get_IdStr)) Il2CppString* IdStr; - __declspec(property(get = __get_Type)) HullType Type; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "HullSpec"); - return class_helper; - } - -public: - long __get_Id() - { - static auto field = get_class_helper().GetField("id_").offset(); - return *(long*)((char*)this + field); - } +struct IdRefs : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "IdRefs">; - Il2CppString* __get_Name() - { - static auto field = get_class_helper().GetField("name_").offset(); - return *(Il2CppString**)((char*)this + field); - } - - Il2CppString* __get_IdStr() - { - static auto field = get_class_helper().GetField("idStr_").offset(); - return *(Il2CppString**)((char*)this + field); - } + Il2CppString* locaStringId() + { return Field<"_locaStringId", Il2CppString*>(); } + int64_t locaId() + { return Field<"locaId_", int64_t>(); } +}; - HullType __get_Type() - { - static auto prop = get_class_helper().GetProperty("Type"); - return *prop.Get(this); - } +struct HullSpec : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "HullSpec">; + + int64_t Id() + { return Field<"id_", int64_t>(); } + Il2CppString* Name() + { return Field<"name_", Il2CppString*>(); } + Il2CppString* IdStr() + { return Field<"idStr_", Il2CppString*>(); } + HullType Type() + { return Get<"Type", HullType>(); } + IdRefs* idRefs() + { return Field<"idRefs_", IdRefs*>(); } }; diff --git a/mods/src/prime/IBuffComparer.h b/mods/src/prime/IBuffComparer.h index 5374ffae3..46a81981a 100644 --- a/mods/src/prime/IBuffComparer.h +++ b/mods/src/prime/IBuffComparer.h @@ -1,27 +1,15 @@ #pragma once -#include +#include -class IBuffComparer -{ -public: - long get_HullId() - { - static auto prop = get_class_helper().GetProperty("HullID"); - return *prop.Get((void*)this); - } +#include - long get_FactionId() - { - static auto prop = get_class_helper().GetProperty("FactionID"); - return *prop.Get((void*)this); - } +// Interface binding; the getters dispatch to the receiver's implementation. +struct IBuffComparer : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "IBuffComparer">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "IBuffComparer"); - return class_helper; - } -}; \ No newline at end of file + int64_t get_HullId() + { return Get<"HullID", int64_t>(); } + int64_t get_FactionId() + { return Get<"FactionID", int64_t>(); } +}; diff --git a/mods/src/prime/IBuffData.h b/mods/src/prime/IBuffData.h index e62c37b84..3c1c64212 100644 --- a/mods/src/prime/IBuffData.h +++ b/mods/src/prime/IBuffData.h @@ -1,25 +1,12 @@ #pragma once -#include - #include "ClientModifierType.h" -class IBuffData -{ -public: - __declspec(property(get = __get_modifierType)) ClientModifierType ModifierType; +#include -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit", "IBuffData"); - return class_helper; - } +struct IBuffData : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit", "IBuffData">; -public: - ClientModifierType __get_modifierType() - { - static auto prop = get_class_helper().GetProperty("ModifierType"); - return *prop.Get((void*)this); - } -}; \ No newline at end of file + ClientModifierType ModifierType() + { return Get<"ModifierType", ClientModifierType>(); } +}; diff --git a/mods/src/prime/IEnumerator.h b/mods/src/prime/IEnumerator.h index a75821786..fc2c54dda 100644 --- a/mods/src/prime/IEnumerator.h +++ b/mods/src/prime/IEnumerator.h @@ -1,7 +1,6 @@ #pragma once -#include +#include -class IEnumerator -{ -}; \ No newline at end of file +struct IEnumerator : il2cpp::Object { +}; diff --git a/mods/src/prime/IList.h b/mods/src/prime/IList.h deleted file mode 100644 index db71b55c6..000000000 --- a/mods/src/prime/IList.h +++ /dev/null @@ -1,39 +0,0 @@ -#pragma once - -#include - -class IList : Il2CppObject -{ -public: - __declspec(property(get = __get_Count)) int Count; - - Il2CppObject* Get(int32_t index) - { - static auto GetImpl = - get_class_helper().GetMethodSpecial2((Il2CppObject*)(this), "get_Item"); - return GetImpl(this, index); - } - - void RemoveAt(int32_t index) - { - static auto RemoveAtImpl = - get_class_helper().GetMethodSpecial2((Il2CppObject*)(this), "RemoveAt"); - if (RemoveAtImpl != nullptr) { - RemoveAtImpl(this, index); - } - } - -private: - IL2CppClassHelper& get_class_helper() - { - static auto class_helper = IL2CppClassHelper{this->klass}; - return class_helper; - } - -public: - int __get_Count() - { - static auto field = get_class_helper().GetField("_size").offset(); - return *(int*)((char*)this + field); - } -}; \ No newline at end of file diff --git a/mods/src/prime/Image.h b/mods/src/prime/Image.h new file mode 100644 index 000000000..fba0a1c9a --- /dev/null +++ b/mods/src/prime/Image.h @@ -0,0 +1,48 @@ +#pragma once + +#include "Color.h" +#include "Component.h" + +#include + +#include + +struct Sprite : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Sprite">; + + float pixelsPerUnit() + { return Get<"pixelsPerUnit", float>(); } +}; + +// UnityEngine.UI.Image; color and raycastTarget come from Graphic. +struct Image : Component { + using Class = il2cpp::Class<"UnityEngine.UI", "UnityEngine.UI", "Image">; + + enum class Type : int32_t { + Simple = 0, + Sliced = 1, + Tiled = 2, + Filled = 3, + }; + + Sprite* sprite() + { return Get<"sprite", Sprite*>(); } + void SetSprite(Sprite* value) + { Set<"sprite">(value); } + Sprite* overrideSprite() + { return Get<"overrideSprite", Sprite*>(); } + void SetOverrideSprite(Sprite* value) + { Set<"overrideSprite">(value); } + void SetType(Type value) + { Set<"type">(value); } + void SetPreserveAspect(bool value) + { Set<"preserveAspect">(value); } + void SetPixelsPerUnitMultiplier(float value) + { Set<"pixelsPerUnitMultiplier">(value); } + void SetColor(color value) + { Set<"color">(value); } + void SetRaycastTarget(bool value) + { Set<"raycastTarget">(value); } + void SetVerticesDirty() + { Call<"SetVerticesDirty">(); } +}; diff --git a/mods/src/prime/InputFieldWidget.h b/mods/src/prime/InputFieldWidget.h index 1efed6161..d8d16e554 100644 --- a/mods/src/prime/InputFieldWidget.h +++ b/mods/src/prime/InputFieldWidget.h @@ -1,44 +1,17 @@ #pragma once -#include - #include "TMP_InputField.h" -struct InputFieldWidget { -public: - __declspec(property(get = __get__input)) TMP_InputField* _input; - __declspec(property(get = __get_isActiveAndEnabled)) bool isActiveAndEnabled; - - void Focus() - { - static auto focusMethod = get_class_helper().GetMethod("Focus"); - if (focusMethod) { - focusMethod(this); - } +#include - // Fallback in case Focus method isn't present or doesn't activate properly - if (auto input = this->_input; input) { - input->ActivateInputField(); - } - } +struct InputFieldWidget : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "InputFieldWidget">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "InputFieldWidget"); - return class_helper; - } - -public: - TMP_InputField* __get__input() - { - static auto field = get_class_helper().GetField("_input").offset(); - return *(TMP_InputField**)((uintptr_t)this + field); - } + void Focus() + { Call<"Focus">(); } - bool __get_isActiveAndEnabled() - { - static auto field = get_class_helper().GetProperty("isActiveAndEnabled"); - return field.Get(this); - } + TMP_InputField* input() + { return Field<"_input", TMP_InputField*>(); } + bool isActiveAndEnabled() + { return Get<"isActiveAndEnabled", bool>(); } }; diff --git a/mods/src/prime/InterstitialViewController.h b/mods/src/prime/InterstitialViewController.h index d335e1d4d..b6f17e474 100644 --- a/mods/src/prime/InterstitialViewController.h +++ b/mods/src/prime/InterstitialViewController.h @@ -1,21 +1,10 @@ #pragma once -#include +#include -#include "TMP_InputField.h" - -struct InterstitialViewController { -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Interstitial", "InterstitialViewController"); - return class_helper; - } +struct InterstitialViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Interstitial", "InterstitialViewController">; void CloseWhenReady() - { - auto CloseWhenReady = get_class_helper().GetMethod("CloseWhenReady"); - return CloseWhenReady(this); - } + { Call<"CloseWhenReady">(); } }; diff --git a/mods/src/prime/InventoryForPopup.h b/mods/src/prime/InventoryForPopup.h index 2ef371fd7..e1258d63e 100644 --- a/mods/src/prime/InventoryForPopup.h +++ b/mods/src/prime/InventoryForPopup.h @@ -1,53 +1,20 @@ #pragma once -#include +#include #include -struct InventoryForPopup { -public: - __declspec(property(get = __get_MaxItemsToUse, put = __set_MaxItemsToUse)) int64_t MaxItemsToUse; - __declspec(property(get = __get_IsDonationUse, put = __set_IsDonationUse)) bool IsDonationUse; - __declspec(property(get = __get_IsChestPurchase)) bool IsChestPurchase; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Inventories", "InventoryForPopup"); - return class_helper; - } - -public: - int64_t __get_MaxItemsToUse() - { - static auto property = get_class_helper().GetProperty("MaxItemsToUse"); - const auto* value = property.Get(this); - return value ? *value : 0; - } - - void __set_MaxItemsToUse(int64_t value) - { - static auto property = get_class_helper().GetProperty("MaxItemsToUse"); - property.SetRaw(this, value); - } - - bool __get_IsDonationUse() - { - static auto field = get_class_helper().GetProperty("IsDonationUse"); - return field.GetRaw(this); - } - - void __set_IsDonationUse(float v) - { - static auto field = get_class_helper().GetProperty("IsDonationUse"); - return field.SetRaw(this, v); - } - - bool __get_IsChestPurchase() - { - static auto property = get_class_helper().GetProperty("IsChestPurchase"); - const auto* value = property.Get(this); - return value ? *value : false; - } +struct InventoryForPopup : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Inventories", "InventoryForPopup">; + + int64_t MaxItemsToUse() + { return Get<"MaxItemsToUse", int64_t>(); } + void SetMaxItemsToUse(int64_t value) + { Set<"MaxItemsToUse">(value); } + bool IsDonationUse() + { return Get<"IsDonationUse", bool>(); } + void SetIsDonationUse(bool value) + { Set<"IsDonationUse">(value); } + bool IsChestPurchase() + { return Get<"IsChestPurchase", bool>(); } }; diff --git a/mods/src/prime/InventoryListViewController.h b/mods/src/prime/InventoryListViewController.h index 624608dce..fdd353cd2 100644 --- a/mods/src/prime/InventoryListViewController.h +++ b/mods/src/prime/InventoryListViewController.h @@ -1,49 +1,20 @@ #pragma once -#include - #include "CanvasController.h" #include "Hub.h" #include "InputFieldWidget.h" -struct InventoryListViewController { -public: - __declspec(property(get = __get__inputField)) InputFieldWidget* _inputField; - __declspec(property(get = __get__targetSection)) SectionID _targetSection; - __declspec(property(get = __get_isActiveAndEnabled)) bool isActiveAndEnabled; - __declspec(property(get = __get_canvasController)) CanvasController* canvasController; - -private: - friend class ObjectFinder; - -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Inventories", "InventoryListViewController"); - return class_helper; - } - - InputFieldWidget* __get__inputField() - { - static auto field = get_class_helper().GetField("_inputField").offset(); - return *(InputFieldWidget**)((uintptr_t)this + field); - } - - SectionID __get__targetSection() - { - static auto field = get_class_helper().GetField("_targetSection").offset(); - return *(SectionID*)((uintptr_t)this + field); - } +#include - bool __get_isActiveAndEnabled() - { - static auto field = get_class_helper().GetProperty("isActiveAndEnabled"); - return field.Get(this); - } +struct InventoryListViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Inventories", "InventoryListViewController">; - CanvasController* __get_canvasController() - { - return GetCanvasControllerFromComponent(this); - } + InputFieldWidget* inputField() + { return Field<"_inputField", InputFieldWidget*>(); } + SectionID targetSection() + { return Field<"_targetSection", SectionID>(); } + bool isActiveAndEnabled() + { return Get<"isActiveAndEnabled", bool>(); } + CanvasController* canvasController() + { return GetCanvasControllerFromComponent(this); } }; diff --git a/mods/src/prime/InventoryUseRowWidget.h b/mods/src/prime/InventoryUseRowWidget.h index e214d9946..5e9db5d2e 100644 --- a/mods/src/prime/InventoryUseRowWidget.h +++ b/mods/src/prime/InventoryUseRowWidget.h @@ -3,15 +3,6 @@ #include "InventoryForPopup.h" #include "Widget.h" -struct InventoryUseRowWidget : public Widget { -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Inventories", "InventoryUseRowWidget"); - return class_helper; - } - -private: - friend struct Widget; +struct InventoryUseRowWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Inventories", "InventoryUseRowWidget">; }; diff --git a/mods/src/prime/LanguageManager.h b/mods/src/prime/LanguageManager.h index 98e1000c1..339c2df18 100644 --- a/mods/src/prime/LanguageManager.h +++ b/mods/src/prime/LanguageManager.h @@ -1,39 +1,25 @@ #pragma once -#include - -#include "errormsg.h" +#include "LocaleTextContext.h" #include "MonoSingleton.h" -class LanguageManager : public MonoSingleton -{ -public: - bool TryGetTranslation(Il2CppString *category, Il2CppString *key, Il2CppString **translatedText) - { - static auto TryGetTranslation = - get_class_helper().GetMethod( - "TryGetTranslation"); - return TryGetTranslation(this, category, key, translatedText); - } +#include - static void ClearCache() - { - static auto clearCache = get_class_helper().GetMethod("ClearCache"); - static auto warn = true; - if (clearCache) { - clearCache(); - } else if (warn) { - warn = false; - ErrorMsg::MissingMethod("LanguageManager", "ClearCache"); - } - } +#include + +struct LanguageManager : MonoSingleton { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Localization", "LanguageManager">; -private: - friend struct MonoSingleton; - static IL2CppClassHelper &get_class_helper() + bool TryGetTranslation(Il2CppString* category, int32_t key, Il2CppString** translatedText) { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Localization", "LanguageManager"); - return class_helper; + if (!translatedText) + return false; + return Call<"TryGetTranslation", bool(Il2CppString*, int32_t, Il2CppString*&)>(category, key, *translatedText); } + + bool Localize(Il2CppString*& translation, LocaleTextContext* context) + { return Call<"Localize", bool(Il2CppString*&, LocaleTextContext*)>(translation, context); } + + static void ClearCache() + { Class::Call<"ClearCache">(); } }; diff --git a/mods/src/prime/LocaleTextContext.h b/mods/src/prime/LocaleTextContext.h new file mode 100644 index 000000000..7d7fef4cc --- /dev/null +++ b/mods/src/prime/LocaleTextContext.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +#include + +// Identifier + category pair that the localizer turns into display text. +// Construct with il2cpp::New(identifier, category). +struct LocaleTextContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "LocaleTextContext">; + + // Fills the {N} slots of the identifier itself, e.g. "ship_name_{0}". + void ApplyIdentifierParameters(il2cpp::Array* parameters) + { Call<"ApplyIdentifierParameters">(parameters); } +}; + +struct LocaleUtilities { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Localization", "LocaleUtilities">; + + static Il2CppString* Localize(LocaleTextContext* context, bool localizeTextParams = false, + bool invariantCulture = false) + { return Class::Call<"Localize", Il2CppString*>(context, localizeTextParams, invariantCulture); } +}; + +struct Localizer { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.Localization", "Localizer">; + + // textCase is TextLocalizer.TextCase; 0 leaves the text as translated. + static Il2CppString* Localise(LocaleTextContext* context, bool forceCommas = false, int32_t textCase = 0) + { return Class::Call<"Localise", Il2CppString*>(context, forceCommas, textCase); } +}; diff --git a/mods/src/prime/ManagedRead.h b/mods/src/prime/ManagedRead.h deleted file mode 100644 index ae4da9a6f..000000000 --- a/mods/src/prime/ManagedRead.h +++ /dev/null @@ -1,84 +0,0 @@ -#pragma once - -#include - -#include -#include - -// Null-safe IL2CPP reads: an empty optional or nullptr instead of 0 when anything is missing. -namespace ManagedRead -{ -// property getter on a class or interface -inline const MethodInfo* Getter(Il2CppClass* cls, const char* property) -{ - if (!cls) { - return nullptr; - } - const PropertyInfo* prop = il2cpp_class_get_property_from_name(cls, property); - return prop ? il2cpp_property_get_get_method(const_cast(prop)) : nullptr; -} - -// getter on the object's runtime class, for members only some implementations have -inline const MethodInfo* RuntimeGetter(void* obj, const char* property) -{ - return obj ? Getter(((Il2CppObject*)obj)->klass, property) : nullptr; -} - -// instance call, virtual and interface dispatch included -inline Il2CppObject* Invoke(const MethodInfo* method, void* obj) -{ - if (!method || !obj) { - return nullptr; - } - auto* object = (Il2CppObject*)obj; - if (method->klass && !il2cpp_class_is_assignable_from(method->klass, object->klass)) { - return nullptr; - } - const MethodInfo* target = il2cpp_object_get_virtual_method(object, method); - if (!target) { - return nullptr; - } - Il2CppException* exception = nullptr; - Il2CppObject* result = il2cpp_runtime_invoke(target, obj, nullptr, &exception); - return exception ? nullptr : result; -} - -// reference result -inline void* Object(const MethodInfo* getter, void* obj) -{ - return Invoke(getter, obj); -} - -// boxed value result; T must match the managed size (uint64_t for DateTime) -template std::optional Value(const MethodInfo* getter, void* obj) -{ - Il2CppObject* boxed = Invoke(getter, obj); - if (!boxed) { - return std::nullopt; - } - auto* data = static_cast(il2cpp_object_unbox(boxed)); - return data ? std::optional(*data) : std::nullopt; -} - -// object stored in an instance field -inline void* FieldObject(void* obj, FieldInfo* field) -{ - if (!obj || !field) { - return nullptr; - } - return *reinterpret_cast(reinterpret_cast(obj) + field->offset); -} - -// DateTime -> unix seconds. The top two bits are the Kind; MinValue (unset) gives nullopt. -inline std::optional DateTimeToUnix(std::optional date_data) -{ - if (!date_data) { - return std::nullopt; - } - const int64_t ticks = static_cast(*date_data & 0x3FFFFFFFFFFFFFFFull); - if (ticks <= 0) { - return std::nullopt; - } - return (ticks - 621355968000000000LL) / 10000000LL; -} -} // namespace ManagedRead diff --git a/mods/src/prime/MiningObjectViewerWidget.h b/mods/src/prime/MiningObjectViewerWidget.h index 59d5a2b8c..c9a9cd89f 100644 --- a/mods/src/prime/MiningObjectViewerWidget.h +++ b/mods/src/prime/MiningObjectViewerWidget.h @@ -1,51 +1,27 @@ #pragma once -#include - -#include "Widget.h" - -#include "NavigationInteractionUIContext.h" #include "ObjectViewerBaseWidget.h" #include "ScanEngageButtonsWidget.h" -enum OccupiedState // TypeDefIndex: 12900 -{ +#include + +#include + +// MiningObjectViewerWidget.OccupiedState, a private nested enum. +enum class OccupiedState : int32_t { NotOccupied = 0, LocalPlayerOccupied = 1, OtherPlayerOccupied = 2 }; -struct MiningObjectViewerWidget : public ObjectViewerBaseWidget { -public: - __declspec(property(get = __get__occupiedState)) OccupiedState _occupiedState; - __declspec(property(get = __get__scanEngageButtonsWidget)) ScanEngageButtonsWidget* _scanEngageButtonsWidget; +struct MiningObjectViewerWidget : ObjectViewerBaseWidget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "MiningObjectViewerWidget">; void MineClicked() - { - static auto MineClicked = get_class_helper().GetMethod("MineClicked"); - MineClicked(this); - } - -public: - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "MiningObjectViewerWidget"); - return class_helper; - } - -public: - OccupiedState __get__occupiedState() - { - static auto field = get_class_helper().GetField("_occupiedState"); - return *(OccupiedState*)((ptrdiff_t)this + field.offset()); - } + { Call<"MineClicked">(); } - ScanEngageButtonsWidget* __get__scanEngageButtonsWidget() - { - static auto field = get_class_helper().GetField("_scanEngageButtonsWidget").offset(); - return *(ScanEngageButtonsWidget**)((char*)this + field); - } + OccupiedState occupiedState() + { return Field<"_occupiedState", OccupiedState>(); } + ScanEngageButtonsWidget* scanEngageButtonsWidget() + { return Field<"_scanEngageButtonsWidget", ScanEngageButtonsWidget*>(); } }; diff --git a/mods/src/prime/MissionsObjectViewerWidget.h b/mods/src/prime/MissionsObjectViewerWidget.h index b93f89ed7..f1c0b34e5 100644 --- a/mods/src/prime/MissionsObjectViewerWidget.h +++ b/mods/src/prime/MissionsObjectViewerWidget.h @@ -1,22 +1,7 @@ #pragma once -#include - -#include "Widget.h" - -#include "NavigationInteractionUIContext.h" #include "ObjectViewerBaseWidget.h" -#include "ScanEngageButtonsWidget.h" - -struct MissionsObjectViewerWidget : public ObjectViewerBaseWidget { -public: - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "MissionsObjectViewerWidget"); - return class_helper; - } +struct MissionsObjectViewerWidget : ObjectViewerBaseWidget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "MissionsObjectViewerWidget">; }; diff --git a/mods/src/prime/MonoSingleton.h b/mods/src/prime/MonoSingleton.h index 64788d113..0a592e4c1 100644 --- a/mods/src/prime/MonoSingleton.h +++ b/mods/src/prime/MonoSingleton.h @@ -1,24 +1,12 @@ #pragma once -#include +#include -template struct MonoSingleton { -public: +// Unity singletons expose a static Instance on their MonoSingleton`1 base. +template struct MonoSingleton : il2cpp::Object { static T* Instance() - { - IL2CppClassHelper& helper = T::get_class_helper(); - static auto parent = helper.GetParent("MonoSingleton`1"); - auto p = parent.GetProperty("Instance"); - auto p2 = p.GetRaw(nullptr); - - // Il2CppClassPointerStore>.NativeClassPtr = - // (__Null)IL2CPP.il2cpp_class_from_type(Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type(IL2CPP.GetIl2CppClass("Assembly-CSharp.dll", - // "", "MonoSingleton`1"))).MakeGenericType(new Il2CppReferenceArray(new Il2CppSystem.Type[1] - // { - // Il2CppSystem.Type.internal_from_handle(IL2CPP.il2cpp_class_get_type((System.IntPtr) - // Il2CppClassPointerStore.NativeClassPtr)) - // })).TypeHandle.value); - - return p2; - } + { return T::Class::template Get<"Instance", T*>(); } + // The singleton s_instance already holds, or null. Instance can search the scene or create a replacement. + static T* Existing() + { return T::Class::template Field<"s_instance", T*>(); } }; diff --git a/mods/src/prime/NavigationDirector.h b/mods/src/prime/NavigationDirector.h deleted file mode 100644 index 4a6779569..000000000 --- a/mods/src/prime/NavigationDirector.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -class NavigationDirector -{ -public: - void HideInteraction() - { - static auto HideInteraction = get_class_helper().GetMethod("HideInteraction"); - HideInteraction(this); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationManager"); - return class_helper; - } -}; \ No newline at end of file diff --git a/mods/src/prime/NavigationFleetWidget.h b/mods/src/prime/NavigationFleetWidget.h index 03151b042..b1c73cc16 100644 --- a/mods/src/prime/NavigationFleetWidget.h +++ b/mods/src/prime/NavigationFleetWidget.h @@ -1,54 +1,12 @@ #pragma once -#include - -#include - #include "FleetDeployedData.h" #include "NavigationLOD.h" +#include "Widget.h" -struct NavigationFleetWidget { - __declspec(property(get = __get__lod)) NavigationLOD* _lod; - __declspec(property(get = __get_Context)) FleetDeployedData* Context; - - static FieldInfo* ContextField() - { - static auto* field = [] { - for (auto* cls = get_class_helper().get_cls(); cls != nullptr; cls = il2cpp_class_get_parent(cls)) { - void* iterator = nullptr; - while (auto* candidate = il2cpp_class_get_fields(cls, &iterator)) { - const auto* name = il2cpp_field_get_name(candidate); - if (name != nullptr && std::strcmp(name, "m_context") == 0) { - return candidate; - } - } - } - return static_cast(nullptr); - }(); - return field; - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationFleetWidget"); - return class_helper; - } - -public: - NavigationLOD* __get__lod() - { - static auto* field = [] { - auto* cls = get_class_helper().get_cls(); - return cls != nullptr ? il2cpp_class_get_field_from_name(cls, "_lod") : nullptr; - }(); - return field != nullptr ? *(NavigationLOD**)((ptrdiff_t)this + field->offset) : nullptr; - } +struct NavigationFleetWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "NavigationFleetWidget">; - FleetDeployedData* __get_Context() - { - auto* field = ContextField(); - return field != nullptr ? *(FleetDeployedData**)((ptrdiff_t)this + field->offset) : nullptr; - } + NavigationLOD* lod() + { return Field<"_lod", NavigationLOD*>(); } }; diff --git a/mods/src/prime/NavigationInteractionUIContext.h b/mods/src/prime/NavigationInteractionUIContext.h index 96a5c7d6c..3a1b7c309 100644 --- a/mods/src/prime/NavigationInteractionUIContext.h +++ b/mods/src/prime/NavigationInteractionUIContext.h @@ -1,14 +1,7 @@ #pragma once -#include +#include -struct NavigationInteractionUIContext { -public: -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationInteractionUIContext"); - return class_helper; - } +struct NavigationInteractionUIContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "NavigationInteractionUIContext">; }; diff --git a/mods/src/prime/NavigationInteractionUIViewController.h b/mods/src/prime/NavigationInteractionUIViewController.h index 2099af7fc..7ea98bc27 100644 --- a/mods/src/prime/NavigationInteractionUIViewController.h +++ b/mods/src/prime/NavigationInteractionUIViewController.h @@ -1,34 +1,34 @@ #pragma once -#include +#include #include "VisibilityController.h" #include "SemaphoreButtonListener.h" -struct NavigationInteractionUIViewController { -public: +struct SetCourseWidget : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "SetCourseWidget">; + VisibilityController* visibilityController() + { return Field<"_visibilityController", VisibilityController*>(); } + bool isActiveAndEnabled() + { return Get<"isActiveAndEnabled", bool>(); } + SemaphoreButtonListener* joinWaveDefenseButton() + { return Field<"_joinWaveDefenseButton", SemaphoreButtonListener*>(); } +}; + +struct NavigationInteractionUIViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "NavigationInteractionUIViewController">; + bool TryPressJoinWaveDefense() { if (!IsSetCourseVisible()) { return false; } - static auto widget_class = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "SetCourseWidget"); - static auto widget_field = get_class_helper().GetField("_setCourseWidget"); - static auto join_field = widget_class.GetField("_joinWaveDefenseButton"); - static auto active_property = SemaphoreButtonListener::get_class_helper().GetProperty("isActiveAndEnabled"); - if (!widget_field.get_info() || !join_field.get_info()) { - return false; - } - auto* widget = *reinterpret_cast(reinterpret_cast(this) + widget_field.offset()); - if (!widget) { - return false; - } - auto* listener = *reinterpret_cast(widget + join_field.offset()); - const auto* active = listener ? active_property.Get(listener) : nullptr; - if (!active || !*active) { + auto* widget = Field<"_setCourseWidget", SetCourseWidget*>(); + auto* listener = widget ? widget->joinWaveDefenseButton() : nullptr; + if (!listener || !listener->isActiveAndEnabled()) { return false; } // Button.Press preserves Unity's active/interactable checks and the native join callback. - if (auto* button = listener->TheButton) { + if (auto* button = listener->TheButton()) { button->Press(); } return true; @@ -36,45 +36,11 @@ struct NavigationInteractionUIViewController { bool IsSetCourseVisible() { - static auto widget_class = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "SetCourseWidget"); - if (!get_class_helper().get_cls() || !widget_class.get_cls()) { - return false; - } - static auto widget_field = get_class_helper().GetField("_setCourseWidget"); - static auto visibility_field = widget_class.GetField("_visibilityController"); - static auto active_property = widget_class.GetProperty("isActiveAndEnabled"); - if (!widget_field.get_info() || !visibility_field.get_info()) { - return false; - } - - auto* widget = *reinterpret_cast(reinterpret_cast(this) + widget_field.offset()); - if (!widget) { - return false; - } - auto* visibility = - *reinterpret_cast(reinterpret_cast(widget) + visibility_field.offset()); - if (!visibility - || (visibility->_state != VisibilityState::Visible && visibility->_state != VisibilityState::Show)) { - return false; - } - const auto* active = active_property.Get(widget); - return active && *active; + auto* widget = Field<"_setCourseWidget", SetCourseWidget*>(); + auto* visibility = widget ? widget->visibilityController() : nullptr; + return visibility && (visibility->state() == VisibilityState::Visible || visibility->state() == VisibilityState::Show) + && widget->isActiveAndEnabled(); } - void OnSetCourseButtonClick() - { - static auto OnSetCourseButtonClick = - get_class_helper().GetMethod("OnSetCourseButtonClick"); - OnSetCourseButtonClick(this); - } - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationInteractionUIViewController"); - return class_helper; - } - -private: - friend class ObjectFinder; + { Call<"OnSetCourseButtonClick">(); } }; diff --git a/mods/src/prime/NavigationLOD.h b/mods/src/prime/NavigationLOD.h index 581922b47..5ce0f5a73 100644 --- a/mods/src/prime/NavigationLOD.h +++ b/mods/src/prime/NavigationLOD.h @@ -1,28 +1,16 @@ #pragma once -#include - #include "ZoomLevels.h" -struct NavigationLOD { +#include + +struct NavigationLOD : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "NavigationLOD">; + void UpdateLOD(ZoomLevels level) - { - static auto update_lod = get_class_helper().GetMethod("UpdateLOD"); - if (update_lod != nullptr) { - update_lod(this, level); - } - } + { Call<"UpdateLOD">(level); } + // Writes the backing field directly; the managed setter is private. void SetTargetLevel(ZoomLevels level) - { - static auto field = get_class_helper().GetField("k__BackingField"); - *(ZoomLevels*)((ptrdiff_t)this + field.offset()) = level; - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationLOD"); - return class_helper; - } + { SetField<"k__BackingField">(level); } }; diff --git a/mods/src/prime/NavigationManager.h b/mods/src/prime/NavigationManager.h index c6b1047b5..96ac794f5 100644 --- a/mods/src/prime/NavigationManager.h +++ b/mods/src/prime/NavigationManager.h @@ -1,18 +1,10 @@ #pragma once -struct NavigationManager { -public: - void HideInteraction() - { - static auto HideInteraction = get_class_helper().GetMethod("HideInteraction"); - HideInteraction(this); - } +#include + +struct NavigationManager : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "NavigationManager">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationManager"); - return class_helper; - } -}; \ No newline at end of file + void HideInteraction() + { Call<"HideInteraction">(); } +}; diff --git a/mods/src/prime/NavigationPan.h b/mods/src/prime/NavigationPan.h index eeff4082a..de5a1f119 100644 --- a/mods/src/prime/NavigationPan.h +++ b/mods/src/prime/NavigationPan.h @@ -1,118 +1,48 @@ #pragma once -#include - #include "Vector2.h" +#include + #pragma pack(push, 1) struct vec3 { float x, y, z; }; #pragma pack(pop) -struct NavigationPan { -public: - // An inline value has no Il2CppObject header. Vector2's reflected accessors - // expect a boxed value and would read/write the adjacent pixel drag delta. - __declspec(property(get = __get__lastDelta)) vec2* _lastDelta; - __declspec(property(get = __get__panningStarted)) bool _panningStarted; - __declspec(property(get = __get__deltaScaler)) float _deltaScaler; - __declspec(property(get = __get__keyboardDeltaScaler)) float _keyboardDeltaScaler; - __declspec(property(get = __get__trackingPOI, put = __set__trackingPOI)) uintptr_t* _trackingPOI; - - __declspec(property(get = __get__farMagRadiusRatioSystemNormal, - put = __set__farMagRadiusRatioSystemNormal)) float _farMagRadiusRatioSystemNormal; - __declspec(property(get = __get__farMagRadiusRatioSystemExtended, - put = __set__farMagRadiusRatioSystemExtended)) float _farMagRadiusRatioSystemExtended; - __declspec(property(get = __get__nearMagRadiusRatioSystem)) float _nearMagRadiusRatioSystem; +struct NavigationPan : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "NavigationPan">; void MoveCamera(vec2 delta, bool isMomentum = false) - { - static auto MoveCamera = get_class_helper().GetMethod("MoveCamera"); - MoveCamera(this, delta, isMomentum); - } + { Call<"MoveCamera">(delta, isMomentum); } static bool BlockPan() - { - return get_class_helper().GetStaticField("BlockPan").Get(); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationPan"); - return class_helper; - } - -public: - uintptr_t* __get__trackingPOI() - { - static auto field = get_class_helper().GetField("_trackingPOI"); - return *(uintptr_t**)((ptrdiff_t)this + field.offset()); - } - - void __set__trackingPOI(uintptr_t* value) - { - static auto field = get_class_helper().GetField("_trackingPOI"); - *(uintptr_t**)((ptrdiff_t)this + field.offset()) = value; - } - - vec2* __get__lastDelta() - { - static auto field = get_class_helper().GetField("_lastDelta"); - return (vec2*)((ptrdiff_t)this + field.offset()); - } - - bool __get__panningStarted() - { - static auto field = get_class_helper().GetField("_panningStarted"); - return *(bool*)((ptrdiff_t)this + field.offset()); - } - - float __get__keyboardDeltaScaler() - { - static auto field = get_class_helper().GetField("_keyboardDeltaScaler"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - float __get__deltaScaler() - { - static auto field = get_class_helper().GetField("_deltaScaler"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - float __get__farMagRadiusRatioSystemNormal() - { - static auto field = get_class_helper().GetField("_farMagRadiusRatioSystemNormal").offset(); - return *(float*)((ptrdiff_t)this + field); - } - - void __set__farMagRadiusRatioSystemNormal(float v) - { - static auto field = get_class_helper().GetField("_farMagRadiusRatioSystemNormal").offset(); - *(float*)((ptrdiff_t)this + field) = v; - } - - float __get__farMagRadiusRatioSystemExtended() - { - static auto field = get_class_helper().GetField("_farMagRadiusRatioSystemExtended").offset(); - return *(float*)((ptrdiff_t)this + field); - } - - void __set__farMagRadiusRatioSystemExtended(float v) - { - static auto field = get_class_helper().GetField("_farMagRadiusRatioSystemExtended").offset(); - *(float*)((ptrdiff_t)this + field) = v; - } - - float __get__nearMagRadiusRatioSystem() - { - static auto field = get_class_helper().GetField("_nearMagRadiusRatioSystemNormal"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - // void __set__lastDelta(int depth) { - // static auto field = get_class_helper().GetField("_depth"); - // *(int*)((ptrdiff_t)this + field.offset()) = depth; - // } + { return Class::Field<"BlockPan", bool>(); } + + Vector2 lastDelta() + { return Field<"_lastDelta", Vector2>(); } + void SetLastDelta(Vector2 value) + { SetField<"_lastDelta">(value); } + bool panningStarted() + { return Field<"_panningStarted", bool>(); } + float deltaScaler() + { return Field<"_deltaScaler", float>(); } + float keyboardDeltaScaler() + { return Field<"_keyboardDeltaScaler", float>(); } + // Managed POI; only checked for null or cleared. + void* trackingPOI() + { return Field<"_trackingPOI", void*>(); } + void SetTrackingPOI(void* value) + { SetField<"_trackingPOI">(value); } + + float farMagRadiusRatioSystemNormal() + { return Field<"_farMagRadiusRatioSystemNormal", float>(); } + void SetFarMagRadiusRatioSystemNormal(float value) + { SetField<"_farMagRadiusRatioSystemNormal">(value); } + float farMagRadiusRatioSystemExtended() + { return Field<"_farMagRadiusRatioSystemExtended", float>(); } + void SetFarMagRadiusRatioSystemExtended(float value) + { SetField<"_farMagRadiusRatioSystemExtended">(value); } + float nearMagRadiusRatioSystem() + { return Field<"_nearMagRadiusRatioSystemNormal", float>(); } }; diff --git a/mods/src/prime/NavigationSectionManager.h b/mods/src/prime/NavigationSectionManager.h index 03cf7d80e..d2b7dd4ba 100644 --- a/mods/src/prime/NavigationSectionManager.h +++ b/mods/src/prime/NavigationSectionManager.h @@ -1,39 +1,22 @@ #pragma once -#include - -#include "NavigationManager.h" #include "Hub.h" +#include "NavigationManager.h" + +#include #include -struct NavigationSectionManager { -public: - __declspec(property(get = __get_NavigationManager)) NavigationManager* SNavigationManager; +struct NavigationSectionManager : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "NavigationSectionManager">; - static void ChangeNavigationSection(SectionID sectionId, int64_t systemId = -1) { - auto ChangeNavigationSection = get_class_helper().GetMethod("ChangeNavigationSection", 2); - ChangeNavigationSection(sectionId, systemId); - } + static void ChangeNavigationSection(SectionID sectionId, int64_t systemId = -1) + { Class::Call<"ChangeNavigationSection">(sectionId, systemId); } static NavigationSectionManager* Instance() - { - static auto p = get_class_helper().GetProperty("Instance"); - return p.GetRaw(nullptr); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationSectionManager"); - return class_helper; - } + { return Class::Get<"Instance", NavigationSectionManager*>(); } -public: - NavigationManager* __get_NavigationManager() - { - static auto field = get_class_helper().GetProperty("NavigationManager"); - return field.GetRaw(this); - } -}; \ No newline at end of file + // Not NavigationManager(): that would hide the type inside this struct. + NavigationManager* SNavigationManager() + { return Get<"NavigationManager", NavigationManager*>(); } +}; diff --git a/mods/src/prime/NavigationZoom.h b/mods/src/prime/NavigationZoom.h index 22cf2fcd7..ed03a6d6f 100644 --- a/mods/src/prime/NavigationZoom.h +++ b/mods/src/prime/NavigationZoom.h @@ -1,265 +1,95 @@ #pragma once -#include - -#include "Vector2.h" -#include "Vector3.h" - #include "Camera.h" #include "NavigationPan.h" +#include "Vector2.h" #include "ZoomLevels.h" -enum class NodeDepth { +#include + +#include + +enum class NodeDepth : int32_t { Galaxy = 1, SolarSystem = 2, PlanetSystem = 4, Starbase = 8, }; -struct NavigationZoom { -public: - void SetViewParameters(float radius, NodeDepth depth) - { - static auto SetViewParameters = - get_class_helper().GetMethod("SetViewParameters"); - SetViewParameters(this, radius, depth); - } +struct NavigationZoom : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "NavigationZoom">; + void SetViewParameters(float radius, NodeDepth depth) + { Call<"SetViewParameters">(radius, depth); } void ZoomCameraAtWorldPoint() - { - static auto ZoomCameraAtWorldPoint = get_class_helper().GetMethod("ZoomCameraAtWorldPoint"); - ZoomCameraAtWorldPoint(this); - } - - __declspec(property(get = __get_Distance, put = __set_Distance)) float Distance; - - __declspec(property(get = __get__depth, put = __set_depth)) NodeDepth _depth; - // - __declspec(property(get = __get__storedZoomDistanceSystem, - put = __set__storedZoomDistanceSystem)) float _storedZoomDistanceSystem; - __declspec(property(get = __get__systemDefaultZoomRatio, - put = __set__systemDefaultZoomRatio)) float _systemDefaultZoomRatio; - __declspec(property(get = __get__actualDistance, put = __set__actualDistance)) float _actualDistance; - __declspec(property(get = __get__zoomtotal, put = __set__zoomtotal)) float _zoomtotal; - __declspec(property(get = __get__minimum, put = __set__minimum)) float _minimum; - __declspec(property(get = __get__maximum, put = __set__maximum)) float _maximum; - __declspec(property(get = __get__zoomLevel)) ZoomLevels _zoomLevel; - __declspec(property(get = __get_NormalizedZoom)) float NormalizedZoom; - __declspec(property(get = __get__zoomDelta, put = __set__zoomDelta)) float _zoomDelta; - __declspec(property(get = __get__viewRadius, put = __set__viewRadius)) float _viewRadius; - __declspec(property(get = __get__lastZoomDelta, put = __set__lastZoomDelta)) float _lastZoomDelta; - __declspec(property(get = __get__zoomLocation, put = __set__zoomLocation)) vec2 _zoomLocation; - __declspec(property(get = __get__worldPoint, put = __set__worldPoint)) vec3 _worldPoint; - __declspec(property(get = __get__farRatioSystemNormal, - put = __set__farRatioSystemNormal)) float _farRatioSystemNormal; - __declspec(property(get = __get__farRatioSystemExtended, - put = __set__farRatioSystemExtended)) float _farRatioSystemExtended; - - __declspec(property(get = __get__sceneCamera)) Camera* _sceneCamera; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "NavigationZoom"); - return class_helper; - } - -public: - float __get_Distance() - { - static auto field = get_class_helper().GetProperty("Distance"); - return *field.Get(this); - } - - void __set_Distance(float depth) - { - static auto field = get_class_helper().GetProperty("Distance"); - field.SetRaw(this, depth); - } - - NodeDepth __get__depth() - { - static auto field = get_class_helper().GetField("_depth"); - return *(NodeDepth*)((ptrdiff_t)this + field.offset()); - } - - void __set_depth(NodeDepth depth) - { - static auto field = get_class_helper().GetField("_depth"); - *(NodeDepth*)((ptrdiff_t)this + field.offset()) = depth; - } - - float __get__viewRadius() - { - static auto field = get_class_helper().GetField("_viewRadius"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set__viewRadius(float delta) - { - static auto field = get_class_helper().GetField("_viewRadius"); - *(float*)((ptrdiff_t)this + field.offset()) = delta; - } - - float __get__zoomDelta() - { - static auto field = get_class_helper().GetField("_zoomDelta"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set__zoomDelta(float delta) - { - static auto field = get_class_helper().GetField("_zoomDelta"); - *(float*)((ptrdiff_t)this + field.offset()) = delta; - } - - float __get__minimum() - { - static auto field = get_class_helper().GetField("_minimum"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set__minimum(float delta) - { - static auto field = get_class_helper().GetField("_minimum"); - *(float*)((ptrdiff_t)this + field.offset()) = delta; - } - - float __get__maximum() - { - static auto field = get_class_helper().GetField("_maximum"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set__maximum(float delta) - { - static auto field = get_class_helper().GetField("_maximum"); - *(float*)((ptrdiff_t)this + field.offset()) = delta; - } - - ZoomLevels __get__zoomLevel() - { - static auto field = get_class_helper().GetField("_zoomLevel"); - return *(ZoomLevels*)((ptrdiff_t)this + field.offset()); - } - - float __get_NormalizedZoom() - { - static auto field = get_class_helper().GetProperty("NormalizedZoom"); - return *field.Get(this); - } - - float __get__actualDistance() - { - static auto field = get_class_helper().GetField("_actualDistance"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set__actualDistance(float delta) - { - static auto field = get_class_helper().GetField("_actualDistance"); - *(float*)((ptrdiff_t)this + field.offset()) = delta; - } - - float __get__storedZoomDistanceSystem() - { - static auto field = get_class_helper().GetField("_storedZoomDistanceSystem"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set__storedZoomDistanceSystem(float delta) - { - static auto field = get_class_helper().GetField("_storedZoomDistanceSystem"); - *(float*)((ptrdiff_t)this + field.offset()) = delta; - } - - float __get__systemDefaultZoomRatio() - { - static auto field = get_class_helper().GetField("_systemDefaultZoomRatio"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set__systemDefaultZoomRatio(float delta) - { - static auto field = get_class_helper().GetField("_systemDefaultZoomRatio"); - *(float*)((ptrdiff_t)this + field.offset()) = delta; - } - - float __get__zoomtotal() - { - static auto field = get_class_helper().GetField("_zoomtotal"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set__zoomtotal(float delta) - { - static auto field = get_class_helper().GetField("_zoomtotal"); - *(float*)((ptrdiff_t)this + field.offset()) = delta; - } - - float __get__lastZoomDelta() - { - static auto field = get_class_helper().GetField("_lastZoomDelta"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set__lastZoomDelta(float delta) - { - static auto field = get_class_helper().GetField("_lastZoomDelta"); - *(float*)((ptrdiff_t)this + field.offset()) = delta; - } - - vec2 __get__zoomLocation() - { - static auto field = get_class_helper().GetField("_zoomLocation"); - return *(vec2*)((ptrdiff_t)this + field.offset()); - } - - void __set__zoomLocation(vec2 v) - { - static auto field = get_class_helper().GetField("_zoomLocation"); - *(vec2*)((ptrdiff_t)this + field.offset()) = v; - } - - vec3 __get__worldPoint() - { - static auto field = get_class_helper().GetField("_worldPoint"); - return *(vec3*)((ptrdiff_t)this + field.offset()); - } - - void __set__worldPoint(vec3 v) - { - static auto field = get_class_helper().GetField("_worldPoint"); - *(vec3*)((ptrdiff_t)this + field.offset()) = v; - } - - Camera* __get__sceneCamera() - { - static auto field = get_class_helper().GetField("_sceneCamera"); - return *(Camera**)((ptrdiff_t)this + field.offset()); - } - - float __get__farRatioSystemNormal() - { - static auto field = get_class_helper().GetField("_farRatioSystemNormal").offset(); - return *(float*)((ptrdiff_t)this + field); - } - - void __set__farRatioSystemNormal(float v) - { - static auto field = get_class_helper().GetField("_farRatioSystemNormal").offset(); - *(float*)((ptrdiff_t)this + field) = v; - } - - float __get__farRatioSystemExtended() - { - static auto field = get_class_helper().GetField("_farRatioSystemExtended").offset(); - return *(float*)((ptrdiff_t)this + field); - } - - void __set__farRatioSystemExtended(float v) - { - static auto field = get_class_helper().GetField("_farRatioSystemExtended").offset(); - *(float*)((ptrdiff_t)this + field) = v; - } + { Call<"ZoomCameraAtWorldPoint">(); } + + float Distance() + { return Get<"Distance", float>(); } + void SetDistance(float value) + { Set<"Distance">(value); } + float NormalizedZoom() + { return Get<"NormalizedZoom", float>(); } + + NodeDepth depth() + { return Field<"_depth", NodeDepth>(); } + void SetDepth(NodeDepth value) + { SetField<"_depth">(value); } + ZoomLevels zoomLevel() + { return Field<"_zoomLevel", ZoomLevels>(); } + Camera* sceneCamera() + { return Field<"_sceneCamera", Camera*>(); } + + float storedZoomDistanceSystem() + { return Field<"_storedZoomDistanceSystem", float>(); } + void SetStoredZoomDistanceSystem(float value) + { SetField<"_storedZoomDistanceSystem">(value); } + float systemDefaultZoomRatio() + { return Field<"_systemDefaultZoomRatio", float>(); } + void SetSystemDefaultZoomRatio(float value) + { SetField<"_systemDefaultZoomRatio">(value); } + float actualDistance() + { return Field<"_actualDistance", float>(); } + void SetActualDistance(float value) + { SetField<"_actualDistance">(value); } + float zoomtotal() + { return Field<"_zoomtotal", float>(); } + void SetZoomtotal(float value) + { SetField<"_zoomtotal">(value); } + float minimum() + { return Field<"_minimum", float>(); } + void SetMinimum(float value) + { SetField<"_minimum">(value); } + float maximum() + { return Field<"_maximum", float>(); } + void SetMaximum(float value) + { SetField<"_maximum">(value); } + float zoomDelta() + { return Field<"_zoomDelta", float>(); } + void SetZoomDelta(float value) + { SetField<"_zoomDelta">(value); } + float viewRadius() + { return Field<"_viewRadius", float>(); } + void SetViewRadius(float value) + { SetField<"_viewRadius">(value); } + float lastZoomDelta() + { return Field<"_lastZoomDelta", float>(); } + void SetLastZoomDelta(float value) + { SetField<"_lastZoomDelta">(value); } + vec2 zoomLocation() + { return Field<"_zoomLocation", vec2>(); } + void SetZoomLocation(vec2 value) + { SetField<"_zoomLocation">(value); } + vec3 worldPoint() + { return Field<"_worldPoint", vec3>(); } + void SetWorldPoint(vec3 value) + { SetField<"_worldPoint">(value); } + float farRatioSystemNormal() + { return Field<"_farRatioSystemNormal", float>(); } + void SetFarRatioSystemNormal(float value) + { SetField<"_farRatioSystemNormal">(value); } + float farRatioSystemExtended() + { return Field<"_farRatioSystemExtended", float>(); } + void SetFarRatioSystemExtended(float value) + { SetField<"_farRatioSystemExtended">(value); } }; diff --git a/mods/src/prime/ObjectViewerBaseWidget.h b/mods/src/prime/ObjectViewerBaseWidget.h index 8af6a75bc..35be10c32 100644 --- a/mods/src/prime/ObjectViewerBaseWidget.h +++ b/mods/src/prime/ObjectViewerBaseWidget.h @@ -5,52 +5,18 @@ #include "VisibilityController.h" #include "Widget.h" -template -class ObjectViewerBaseWidget : public Widget> -{ -public: - __declspec(property(get = __get__visibilityController)) VisibilityController* _visibilityController; - __declspec(property(get = __get_IsInfoShown, put = __set_IsInfoShown)) bool IsInfoShown; - __declspec(property(get = __get_Parent)) ParentObjectViewerViewController* Parent; - -private: - friend struct Widget; - static IL2CppClassHelper& get_class_helper() - { - static IL2CppClassHelper class_helper = Y::get_class_helper().GetParent("ObjectViewerBaseWidget"); - return class_helper; - } - -public: - void HideAllViewers() { - static auto HideAllViewersMethod = get_class_helper().GetMethod("HideAllViewers"); - - if (HideAllViewersMethod) { - HideAllViewersMethod(); - } - } - - VisibilityController* __get__visibilityController() - { - static auto field = get_class_helper().GetField("_visibilityController"); - return *(VisibilityController**)((ptrdiff_t)this + field.offset()); - } - - ParentObjectViewerViewController* __get_Parent() - { - static auto field = get_class_helper().GetProperty("Parent"); - return field.GetRaw(this); - } - - bool __get_IsInfoShown() - { - static auto field = get_class_helper().GetProperty("IsInfoShown"); - return *field.GetRaw(this); - } - - void __set_IsInfoShown(bool v) - { - static auto field = get_class_helper().GetProperty("IsInfoShown"); - field.SetRaw(this, v); - } -}; \ No newline at end of file +struct ObjectViewerBaseWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "ObjectViewerBaseWidget">; + + void HideAllViewers() + { Call<"HideAllViewers">(); } + + VisibilityController* visibilityController() + { return Field<"_visibilityController", VisibilityController*>(); } + ParentObjectViewerViewController* Parent() + { return Get<"Parent", ParentObjectViewerViewController*>(); } + bool IsInfoShown() + { return Get<"IsInfoShown", bool>(); } + void SetIsInfoShown(bool value) + { Set<"IsInfoShown">(value); } +}; diff --git a/mods/src/prime/OfficerAssignmentViewController.h b/mods/src/prime/OfficerAssignmentViewController.h index cd1cbb684..af5d3f875 100644 --- a/mods/src/prime/OfficerAssignmentViewController.h +++ b/mods/src/prime/OfficerAssignmentViewController.h @@ -1,49 +1,20 @@ #pragma once -#include - #include "CanvasController.h" #include "GenericButtonWidget.h" #include "InputFieldWidget.h" -struct OfficerAssignmentViewController { -public: - __declspec(property(get = __get__inputField)) InputFieldWidget* _inputField; - __declspec(property(get = __get__officerPresetsButton)) GenericButtonWidget* _officerPresetsButton; - __declspec(property(get = __get_isActiveAndEnabled)) bool isActiveAndEnabled; - __declspec(property(get = __get_canvasController)) CanvasController* canvasController; - -private: - friend class ObjectFinder; - -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.OfficerAssignment", "OfficerAssignmentViewController"); - return class_helper; - } - - InputFieldWidget* __get__inputField() - { - static auto field = get_class_helper().GetField("_inputField").offset(); - return *(InputFieldWidget**)((uintptr_t)this + field); - } - - GenericButtonWidget* __get__officerPresetsButton() - { - static auto field = get_class_helper().GetField("_officerPresetsButton").offset(); - return *(GenericButtonWidget**)((uintptr_t)this + field); - } +#include - bool __get_isActiveAndEnabled() - { - static auto field = get_class_helper().GetProperty("isActiveAndEnabled"); - return field.Get(this); - } +struct OfficerAssignmentViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.OfficerAssignment", "OfficerAssignmentViewController">; - CanvasController* __get_canvasController() - { - return GetCanvasControllerFromComponent(this); - } + InputFieldWidget* inputField() + { return Field<"_inputField", InputFieldWidget*>(); } + GenericButtonWidget* officerPresetsButton() + { return Field<"_officerPresetsButton", GenericButtonWidget*>(); } + bool isActiveAndEnabled() + { return Get<"isActiveAndEnabled", bool>(); } + CanvasController* canvasController() + { return GetCanvasControllerFromComponent(this); } }; diff --git a/mods/src/prime/OrbitFrameProvider.h b/mods/src/prime/OrbitFrameProvider.h index 2ba6e8945..d12d4975f 100644 --- a/mods/src/prime/OrbitFrameProvider.h +++ b/mods/src/prime/OrbitFrameProvider.h @@ -1,20 +1,13 @@ #pragma once -#include +#include -#include "Camera.h" +struct OrbitFrameProvider : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.CameraController", "OrbitFrameProvider">; -struct OrbitFrameProvider { - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.CameraController", "OrbitFrameProvider"); - return class_helper; - } - - float& RotationAngleDelta() - { - static auto field = get_class_helper().GetField("_rotationAngleDelta"); - return *(float*)((ptrdiff_t)this + field.offset()); - } + // Declared on AbstractOrbitFrameProvider. + float RotationAngleDelta() + { return Field<"_rotationAngleDelta", float>(); } + void SetRotationAngleDelta(float value) + { SetField<"_rotationAngleDelta">(value); } }; diff --git a/mods/src/prime/ParentObjectViewerViewController.h b/mods/src/prime/ParentObjectViewerViewController.h index 030985b0a..867303b67 100644 --- a/mods/src/prime/ParentObjectViewerViewController.h +++ b/mods/src/prime/ParentObjectViewerViewController.h @@ -1,60 +1,21 @@ #pragma once #include "NavigationInteractionUIContext.h" -#include "VisibilityController.h" #include "ViewController.h" -#include "Widget.h" - -#include "spdlog/spdlog.h" - -class ParentObjectViewerViewController : public ViewController -{ -public: - __declspec(property(get = __get__visibilityController)) VisibilityController* _visibilityController; - __declspec(property(get = __get_IsShowing)) bool IsShowing; +#include "VisibilityController.h" -private: - friend struct ViewController; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "ParentObjectViewerViewController"); - spdlog::debug("ParentObjectViewerViewController :: Returned Class Helper"); - return class_helper; - } +struct ParentObjectViewerViewController : ViewController { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "ParentObjectViewerViewController">; -public: void ActivateViewerHUDState(bool isHUDOpen = false) - { - static auto ActivateViewerHUDStateMethod = get_class_helper().GetMethod("ActivateViewerHUDState"); - if (ActivateViewerHUDStateMethod) { - spdlog::debug("ParentObjectViewerViewController :: Before ActivateViewerHUDStateMethod"); - ActivateViewerHUDStateMethod(this, isHUDOpen); - spdlog::debug("ParentObjectViewerViewController :: After ActivateViewerHUDStateMethod"); - } - } - + { Call<"ActivateViewerHUDState">(isHUDOpen); } void TriggerInstantHide() - { - static auto TriggerInstantHideMethod = get_class_helper().GetMethod("TriggerInstantHide"); - TriggerInstantHideMethod(this); - } - + { Call<"TriggerInstantHide">(); } void TriggerShow() - { - static auto TriggerShowMethod = get_class_helper().GetMethod("TriggerInstantHide"); - TriggerShowMethod(this); - } - - VisibilityController* __get__visibilityController() - { - static auto field = get_class_helper().GetField("_visibilityController"); - return *(VisibilityController**)((ptrdiff_t)this + field.offset()); - } + { Call<"TriggerShow">(); } - bool __get_IsShowing() - { - static auto field = get_class_helper().GetProperty("IsShowing"); - const auto* showing = field.Get(this); - return showing && *showing; - } + VisibilityController* visibilityController() + { return Field<"_visibilityController", VisibilityController*>(); } + bool IsShowing() + { return Get<"IsShowing", bool>(); } }; diff --git a/mods/src/prime/PlanetViewUtils.h b/mods/src/prime/PlanetViewUtils.h index b697640a0..bccde2892 100644 --- a/mods/src/prime/PlanetViewUtils.h +++ b/mods/src/prime/PlanetViewUtils.h @@ -1,38 +1,19 @@ #pragma once -#include - #include "PopulatedSystemData.h" +#include "Transform.h" -struct PlanetViewUtils { -public: - __declspec(property(get = __get__popData)) PopulatedSystemData* _popData; - __declspec(property(get = __get__activeSystemParent)) void* _activeSystemParent; - - void* GetFlatRenderable() - { - static auto fn = get_class_helper().GetMethod("get_FlatRenderable"); - return fn(this); - } +#include -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "PlanetViewUtils"); - return class_helper; - } +struct PlanetViewUtils : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "PlanetViewUtils">; -public: - PopulatedSystemData* __get__popData() - { - static auto field = get_class_helper().GetField("_popData"); - return *(PopulatedSystemData**)((ptrdiff_t)this + field.offset()); - } + // Managed FlatRenderable. Calling this also runs any detour on get_FlatRenderable. + void* GetFlatRenderable() + { return Get<"FlatRenderable", void*>(); } - void* __get__activeSystemParent() - { - static auto field = get_class_helper().GetField("_activeSystemParent"); - return *(void**)((ptrdiff_t)this + field.offset()); - } + PopulatedSystemData* popData() + { return Field<"_popData", PopulatedSystemData*>(); } + Transform* activeSystemParent() + { return Field<"_activeSystemParent", Transform*>(); } }; diff --git a/mods/src/prime/PlanetaryBaseManager.h b/mods/src/prime/PlanetaryBaseManager.h index 0c802dd69..464c2fdd3 100644 --- a/mods/src/prime/PlanetaryBaseManager.h +++ b/mods/src/prime/PlanetaryBaseManager.h @@ -2,46 +2,25 @@ #include "Hub.h" #include "MonoSingleton.h" -#include +#include + +// Older clients lack this class; there the binding logs it once and ViewOwnHaven does nothing. struct PlanetaryBaseManager : MonoSingleton { - friend struct MonoSingleton; + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.PlanetaryBase", "PlanetaryBaseManager">; -public: static void ViewOwnHaven() { - static auto& manager_class = get_class_helper(); - static auto is_ready = - manager_class.GetMethod("IsPlanetaryBaseCenterReady", 1); - if (!manager_class.get_cls() || !is_ready) { - static bool warned = false; - if (!warned) { - warned = true; - spdlog::warn("[ShowHaven] Planetary navigation is unavailable in this client"); - } - return; - } - auto* manager = Instance(); auto* sections = Hub::get_SectionManager(); - if (!manager || !sections) { + if (!manager || !sections) return; - } // Preserve the game's Iconian Gateway prerequisite and its locked-building message. - if (!is_ready(manager, true)) { + if (!manager->Call<"IsPlanetaryBaseCenterReady", bool>(true)) return; - } // PlanetarySectionContext is reserved for visiting another player's Haven. Passing no context makes the // PlanetarySectionDirector initialize the complete local Haven state from the service cache. sections->TriggerSectionChange(SectionID::Starbase_Planetary, nullptr, false, false, true); } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.PlanetaryBase", "PlanetaryBaseManager"); - return helper; - } }; diff --git a/mods/src/prime/PopulatedSystemData.h b/mods/src/prime/PopulatedSystemData.h index f361bf9af..01a1bcedb 100644 --- a/mods/src/prime/PopulatedSystemData.h +++ b/mods/src/prime/PopulatedSystemData.h @@ -1,37 +1,14 @@ #pragma once -#include +#include -struct PopulatedSystemData { -public: - __declspec(property(get = __get__backdrop)) void* _backdrop; - __declspec(property(get = __get__systemId)) int64_t _systemId; - __declspec(property(get = __get__VariationCollection)) void* VariationCollection; +#include -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "PopulatedSystemData"); - return class_helper; - } +struct PopulatedSystemData : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "PopulatedSystemData">; -public: - void* __get__VariationCollection() - { - static auto field = get_class_helper().GetField("k__BackingField"); - return field.get_info() ? *(void**)((ptrdiff_t)this + field.offset()) : nullptr; - } - - void* __get__backdrop() - { - static auto field = get_class_helper().GetField("_backdrop"); - return *(void**)((ptrdiff_t)this + field.offset()); - } - - int64_t __get__systemId() - { - static auto field = get_class_helper().GetField("_systemId"); - return *(int64_t*)((ptrdiff_t)this + field.offset()); - } + void* backdrop() + { return Field<"_backdrop", void*>(); } + int64_t systemId() + { return Field<"_systemId", int64_t>(); } }; diff --git a/mods/src/prime/PreScanStationTargetWidget.h b/mods/src/prime/PreScanStationTargetWidget.h index 4f8d72931..25991e3e9 100644 --- a/mods/src/prime/PreScanStationTargetWidget.h +++ b/mods/src/prime/PreScanStationTargetWidget.h @@ -1,20 +1,7 @@ #pragma once -#include - #include "PreScanTargetWidget.h" struct PreScanStationTargetWidget : PreScanTargetWidget { -private: - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; - - public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Combat", "PreScanStationTargetWidget"); - return class_helper; - } + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Combat", "PreScanStationTargetWidget">; }; diff --git a/mods/src/prime/PreScanTargetWidget.h b/mods/src/prime/PreScanTargetWidget.h index 2d53af69e..fbd30c253 100644 --- a/mods/src/prime/PreScanTargetWidget.h +++ b/mods/src/prime/PreScanTargetWidget.h @@ -1,79 +1,30 @@ #pragma once -#include - #include "BattleTargetData.h" #include "GenericButtonWidget.h" -#include "NavigationInteractionUIContext.h" +#include "ObjectViewerBaseWidget.h" #include "RewardsButtonWidget.h" -#include "Widget.h" +#include "ScanEngageButtonsWidget.h" -struct PreScanTargetWidget : public ObjectViewerBaseWidget { -public: - __declspec(property(get = __get__battleTargetData)) BattleTargetData* _battleTargetData; - __declspec(property(get = __get__scanEngageButtonsWidget)) ScanEngageButtonsWidget* _scanEngageButtonsWidget; - __declspec(property(get = __get__rewardsButtonWidget)) RewardsButtonWidget* _rewardsButtonWidget; - __declspec(property(get = __get__addToQueueButtonWidget)) GenericButtonWidget* _addToQueueButtonWidget; - __declspec(property(get = __get__armadaAttackButton)) GenericButtonWidget* _armadaAttackButton; +struct PreScanTargetWidget : ObjectViewerBaseWidget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Combat", "PreScanTargetWidget">; void ValidateThenCreateArmada() - { - static auto method = get_class_helper().GetMethod("ValidateThenCreateArmada"); - method(this); - } - + { Call<"ValidateThenCreateArmada">(); } void OnAddToQueueClickedEventHandler() - { - static auto OnAddToQueueClickedEventHandlerMethod = - get_class_helper().GetMethod("OnAddToQueueClickedEventHandler"); - OnAddToQueueClickedEventHandlerMethod(this); - } - + { Call<"OnAddToQueueClickedEventHandler">(); } + // Declared on ObjectViewerBaseWidget. void OnInfoClick() - { - static auto OnInfoClickMethod = get_class_helper().GetMethod("OnInfoClick"); - return OnInfoClickMethod(this); - } - -private: - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; - -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Combat", "PreScanTargetWidget"); - return class_helper; - } - -public: - BattleTargetData* __get__battleTargetData() - { - static auto field = get_class_helper().GetField("_battleTargetData").offset(); - return *(BattleTargetData**)((char*)this + field); - } - - GenericButtonWidget* __get__addToQueueButtonWidget() - { - static auto field = get_class_helper().GetField("_addToQueueButtonWidget").offset(); - return *(GenericButtonWidget**)((char*)this + field); - } - - GenericButtonWidget* __get__armadaAttackButton() - { - static auto field = get_class_helper().GetField("_armadaAttackButton").offset(); - return *(GenericButtonWidget**)((char*)this + field); - } - - ScanEngageButtonsWidget* __get__scanEngageButtonsWidget() - { - static auto field = get_class_helper().GetField("_scanEngageButtonsWidget").offset(); - return *(ScanEngageButtonsWidget**)((char*)this + field); - } - - RewardsButtonWidget* __get__rewardsButtonWidget() - { - static auto field = get_class_helper().GetField("_rewardsButtonWidget").offset(); - return *(RewardsButtonWidget**)((char*)this + field); - } + { Call<"OnInfoClick">(); } + + BattleTargetData* battleTargetData() + { return Field<"_battleTargetData", BattleTargetData*>(); } + ScanEngageButtonsWidget* scanEngageButtonsWidget() + { return Field<"_scanEngageButtonsWidget", ScanEngageButtonsWidget*>(); } + RewardsButtonWidget* rewardsButtonWidget() + { return Field<"_rewardsButtonWidget", RewardsButtonWidget*>(); } + GenericButtonWidget* addToQueueButtonWidget() + { return Field<"_addToQueueButtonWidget", GenericButtonWidget*>(); } + GenericButtonWidget* armadaAttackButton() + { return Field<"_armadaAttackButton", GenericButtonWidget*>(); } }; diff --git a/mods/src/prime/RealtimeDataPayload.h b/mods/src/prime/RealtimeDataPayload.h index 3cd675418..e28e6ed13 100644 --- a/mods/src/prime/RealtimeDataPayload.h +++ b/mods/src/prime/RealtimeDataPayload.h @@ -1,6 +1,8 @@ #pragma once -#include +#include + +#include enum DataType : int32_t { @@ -11,58 +13,20 @@ enum DataType : int32_t UrlEncoded = 3, }; -struct RealtimeDataPayload -{ -public: - __declspec(property(get = __get_dataType)) Il2CppString* DataType; - __declspec(property(get = __get_channelId)) Il2CppString* ChannelId; - __declspec(property(get = __get_instanceIdJson)) int32_t InstanceIdJson; - __declspec(property(get = __get_target)) Il2CppString* Target; - __declspec(property(get = __get_source)) Il2CppString* Source; - __declspec(property(get = __get_data)) Il2CppString* Data; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.Networking.RTC", "RealtimeDataPayload"); - return class_helper; - } - -public: - Il2CppString* __get_dataType() - { - static auto prop = get_class_helper().GetProperty("DataType"); - return prop.GetRaw(this); - } - - Il2CppString* __get_channelId() - { - static auto prop = get_class_helper().GetProperty("ChannelId"); - return prop.GetRaw(this); - } - - int32_t __get_instanceIdJson() - { - static auto prop = get_class_helper().GetProperty("InstanceIdJson"); - return *prop.Get(this); - } - - Il2CppString* __get_target() - { - static auto prop = get_class_helper().GetProperty("Target"); - return prop.GetRaw(this); - } - - Il2CppString* __get_source() - { - static auto prop = get_class_helper().GetProperty("Source"); - return prop.GetRaw(this); - } - - Il2CppString* __get_data() - { - static auto prop = get_class_helper().GetProperty("Data"); - return prop.GetRaw(this); - } +struct RealtimeDataPayload : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.Networking.RTC", "RealtimeDataPayload">; + + // Managed DataType is a string, not the DataType enum above. + Il2CppString* DataType() + { return Get<"DataType", Il2CppString*>(); } + Il2CppString* ChannelId() + { return Get<"ChannelId", Il2CppString*>(); } + int32_t InstanceIdJson() + { return Get<"InstanceIdJson", int32_t>(); } + Il2CppString* Target() + { return Get<"Target", Il2CppString*>(); } + Il2CppString* Source() + { return Get<"Source", Il2CppString*>(); } + Il2CppString* Data() + { return Get<"Data", Il2CppString*>(); } }; diff --git a/mods/src/prime/RecallRequirement.h b/mods/src/prime/RecallRequirement.h index d65c77bc0..f3afff55d 100644 --- a/mods/src/prime/RecallRequirement.h +++ b/mods/src/prime/RecallRequirement.h @@ -1,18 +1,8 @@ #pragma once -#include - #include "ActionRequirement.h" -struct RecallRequirement : public ActionRequirement { - -public: - friend struct ActionRequirement; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "", "FleetPlayerData.RecallRequirement"); - return class_helper; - } -}; \ No newline at end of file +struct RecallRequirement : ActionRequirement { + using Class = + il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "FleetPlayerData/RecallRequirement">; +}; diff --git a/mods/src/prime/RectTransform.h b/mods/src/prime/RectTransform.h new file mode 100644 index 000000000..36a6f72c4 --- /dev/null +++ b/mods/src/prime/RectTransform.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Transform.h" +#include "Vector2.h" + +#include + +struct RectTransform : Transform { + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "RectTransform">; + + Vector2 anchorMin() + { return Get<"anchorMin", Vector2>(); } + Vector2 anchorMax() + { return Get<"anchorMax", Vector2>(); } + Vector2 offsetMin() + { return Get<"offsetMin", Vector2>(); } + Vector2 offsetMax() + { return Get<"offsetMax", Vector2>(); } + void SetOffsetMin(Vector2 value) + { Set<"offsetMin">(value); } + void SetOffsetMax(Vector2 value) + { Set<"offsetMax">(value); } + void SetAnchorMin(Vector2 value) + { Set<"anchorMin">(value); } + void SetAnchorMax(Vector2 value) + { Set<"anchorMax">(value); } + void SetPivot(Vector2 value) + { Set<"pivot">(value); } + void SetSizeDelta(Vector2 value) + { Set<"sizeDelta">(value); } + void SetAnchoredPosition(Vector2 value) + { Set<"anchoredPosition">(value); } +}; diff --git a/mods/src/prime/RequestDispatcherBase.h b/mods/src/prime/RequestDispatcherBase.h deleted file mode 100644 index c971f89ec..000000000 --- a/mods/src/prime/RequestDispatcherBase.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include - -struct RequestDispatcherBase { -public: -private: - friend class ObjectFinder; - friend struct Widget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Combat", "ScanEngageButtonsWidget"); - return class_helper; - } - -public: - void* __get__armadaButton() - { - static auto field = get_class_helper().GetField("_armadaButton"); - return *(void**)((ptrdiff_t)this + field.offset()); - } -}; diff --git a/mods/src/prime/RewardsButtonWidget.h b/mods/src/prime/RewardsButtonWidget.h index 32d8bb319..982ef3d82 100644 --- a/mods/src/prime/RewardsButtonWidget.h +++ b/mods/src/prime/RewardsButtonWidget.h @@ -1,34 +1,15 @@ #pragma once -#include - #include "BattleTargetData.h" #include "VisibilityController.h" #include "Widget.h" -class RewardsButtonWidget : public Widget -{ -public: - __declspec(property(get = __get__rewardsController)) VisibilityController* _rewardsController; +struct RewardsButtonWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Combat", "RewardsButtonWidget">; void RewardsClicked() - { - auto RewardsClicked = get_class_helper().GetMethod("RewardsClicked"); - return RewardsClicked(this); - } - -private: - friend struct Widget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Combat", "RewardsButtonWidget"); - return class_helper; - } + { Call<"RewardsClicked">(); } -public: - VisibilityController* __get__rewardsController() - { - static auto field = get_class_helper().GetField("_rewardsController"); - return *(VisibilityController**)((ptrdiff_t)this + field.offset()); - } -}; \ No newline at end of file + VisibilityController* rewardsController() + { return Field<"_rewardsController", VisibilityController*>(); } +}; diff --git a/mods/src/prime/ScanEngageButtonsWidget.h b/mods/src/prime/ScanEngageButtonsWidget.h index 00b6abc82..7efd829c5 100644 --- a/mods/src/prime/ScanEngageButtonsWidget.h +++ b/mods/src/prime/ScanEngageButtonsWidget.h @@ -1,48 +1,19 @@ #pragma once -#include - #include "BattleTargetData.h" #include "GenericButtonWidget.h" #include "Widget.h" -struct ScanEngageButtonsWidget : public Widget { -public: - __declspec(property(get = __get__armadaButton)) GenericButtonWidget* _armadaButton; +struct ScanEngageButtonsWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Combat", "ScanEngageButtonsWidget">; void OnArmadaButtonClicked() - { - static auto OnArmadaButtonClicked = - get_class_helper().GetMethod("OnArmadaButtonClicked"); - OnArmadaButtonClicked(this); - } + { Call<"OnArmadaButtonClicked">(); } void OnEngageButtonClicked() - { - static auto OnEngageButtonClicked = - get_class_helper().GetMethod("OnEngageButtonClicked"); - OnEngageButtonClicked(this); - } + { Call<"OnEngageButtonClicked">(); } void OnScanButtonClicked() - { - static auto OnScanButtonClicked = - get_class_helper().GetMethod("OnScanButtonClicked"); - OnScanButtonClicked(this); - } - -private: - friend class ObjectFinder; - friend struct Widget; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Combat", "ScanEngageButtonsWidget"); - return class_helper; - } + { Call<"OnScanButtonClicked">(); } -public: - GenericButtonWidget* __get__armadaButton() - { - static auto field = get_class_helper().GetField("_armadaButton").offset(); - return *(GenericButtonWidget**)((char*)this + field); - } + GenericButtonWidget* armadaButton() + { return Field<"_armadaButton", GenericButtonWidget*>(); } }; diff --git a/mods/src/prime/ScanTargetViewController.h b/mods/src/prime/ScanTargetViewController.h index 62947a823..e635482e6 100644 --- a/mods/src/prime/ScanTargetViewController.h +++ b/mods/src/prime/ScanTargetViewController.h @@ -1,30 +1,12 @@ #pragma once -#include - #include "BattleTargetData.h" #include "ScanEngageButtonsWidget.h" #include "ViewController.h" -class ScanTargetViewController : public ViewController -{ -public: - __declspec(property(get = __get__scanEngageButtonsWidget)) ScanEngageButtonsWidget* _scanEngageButtonsWidget; - -private: - friend class ObjectFinder; - friend struct ViewController; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Combat", "ScanTargetViewController"); - return class_helper; - } +struct ScanTargetViewController : ViewController { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Combat", "ScanTargetViewController">; -public: - ScanEngageButtonsWidget* __get__scanEngageButtonsWidget() - { - static auto field = get_class_helper().GetField("_scanEngageButtonsWidget").offset(); - return *(ScanEngageButtonsWidget**)((char*)this + field); - } + ScanEngageButtonsWidget* scanEngageButtonsWidget() + { return Field<"_scanEngageButtonsWidget", ScanEngageButtonsWidget*>(); } }; diff --git a/mods/src/prime/SceneManager.h b/mods/src/prime/SceneManager.h index c80559bb2..7c8dafaca 100644 --- a/mods/src/prime/SceneManager.h +++ b/mods/src/prime/SceneManager.h @@ -1,31 +1,25 @@ #pragma once -#include +#include +// Scene is a struct wrapping an int handle; the internal calls below take and fill it directly. struct SceneManager { -public: + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine.SceneManagement", "SceneManager">; + static const Il2CppChar* GetSceneName(void* scene) { - static auto GetSceneName = il2cpp_resolve_icall_typed( - "UnityEngine.SceneManagement.Scene::GetNameInternal(System.Int32)"); + static auto GetSceneName = + il2cpp::ResolveICall("UnityEngine.SceneManagement.Scene::GetNameInternal(System.Int32)"); auto name = GetSceneName(scene); return il2cpp_string_chars(name); } static void* GetActiveScene() { - static auto GetActiveScene_Injected = il2cpp_resolve_icall_typed( + static auto GetActiveScene_Injected = il2cpp::ResolveICall( "UnityEngine.SceneManagement.SceneManager::GetActiveScene_Injected(UnityEngine.SceneManagement.Scene&)"); void* scene = nullptr; GetActiveScene_Injected(&scene); return scene; } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine.SceneManagement", "SceneManager"); - return class_helper; - } }; diff --git a/mods/src/prime/ScreenManager.h b/mods/src/prime/ScreenManager.h index 7eee497ab..6a5132fc9 100644 --- a/mods/src/prime/ScreenManager.h +++ b/mods/src/prime/ScreenManager.h @@ -4,41 +4,29 @@ #include "CanvasScaler.h" #include "MonoSingleton.h" -struct ScreenManager : MonoSingleton { -public: - __declspec(property(get = __get_m_canvasRootScaler)) CanvasScaler* m_canvasRootScaler; - __declspec(property(get = __get__minimumCanvasScaleFactor)) float _minimumCanvasScaleFactor; - __declspec(property(get = __get__maximumCanvasScaleFactor)) float _maximumCanvasScaleFactor; - - static CanvasController* GetTopCanvas(bool visibleOnly = false) - { - static auto GetTopCanvas = get_class_helper().GetMethod("GetTopCanvas"); - return GetTopCanvas(visibleOnly); - } +struct UIFrameManager : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "UIFrameManager">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "ScreenManager"); - return class_helper; - } + // The game clamps the width to the available space and the side frame's minimum. False when the call failed. + bool ResizeSideFrame(float width) + { return TryCall<"ResizeSideFrame", void(float)>(width); } +}; -public: - CanvasScaler* __get_m_canvasRootScaler() - { - static auto field = get_class_helper().GetField("m_canvasRootScaler"); - return *(CanvasScaler**)((ptrdiff_t)this + field.offset()); - } +struct ScreenManager : MonoSingleton { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "ScreenManager">; - float __get__minimumCanvasScaleFactor() - { - static auto field = get_class_helper().GetField("_minimumCanvasScaleFactor").offset(); - return *(float*)((ptrdiff_t)this + field); - } + static CanvasController* GetTopCanvas(bool visibleOnly = false) + { return Class::Call<"GetTopCanvas", CanvasController*>(visibleOnly); } - float __get__maximumCanvasScaleFactor() - { - static auto field = get_class_helper().GetField("_maximumCanvasScaleFactor").offset(); - return *(float*)((ptrdiff_t)this + field); - } -}; \ No newline at end of file + // Every registered canvas, visible or not. + il2cpp::List* screens() + { return Field<"_screens", il2cpp::List*>(); } + CanvasScaler* canvasRootScaler() + { return Field<"m_canvasRootScaler", CanvasScaler*>(); } + float minimumCanvasScaleFactor() + { return Field<"_minimumCanvasScaleFactor", float>(); } + float maximumCanvasScaleFactor() + { return Field<"_maximumCanvasScaleFactor", float>(); } + UIFrameManager* frameManager() + { return Field<"_frameManager", UIFrameManager*>(); } +}; diff --git a/mods/src/prime/SectionManager.h b/mods/src/prime/SectionManager.h deleted file mode 100644 index eeea17574..000000000 --- a/mods/src/prime/SectionManager.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once - -class SectionManager -{ -}; \ No newline at end of file diff --git a/mods/src/prime/SelectableList.h b/mods/src/prime/SelectableList.h index eb81a86ee..7de77238f 100644 --- a/mods/src/prime/SelectableList.h +++ b/mods/src/prime/SelectableList.h @@ -1,87 +1,33 @@ #pragma once -#include -#include +#include -#include "errormsg.h" +#include -struct SelectableList { -public: - __declspec(property(get = __get_SelectedIndex)) int32_t SelectedIndex; - __declspec(property(get = __get_Count)) int32_t Count; +// Digit.Client.UI list hierarchy: BaseListContainer <- SelectableListBase <- SelectableList <- TabBar. +// Data is a System.Collections.IList; elements are whatever the list was bound to. +struct BaseListContainer : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "BaseListContainer">; - bool RequestSelect(int32_t index, bool simulated = false) - { - static auto RequestSelectWarn = true; - static auto RequestSelectMethod = - get_selectable_list_base_helper().GetMethodSpecial( - "RequestSelect", [](auto count, auto params) { - if (count != 2) { - return false; - } - return params[0]->type == IL2CPP_TYPE_I4; - }); - - if (RequestSelectMethod) { - RequestSelectMethod(this, index, simulated); - return true; - } else if (RequestSelectWarn) { - RequestSelectWarn = false; - ErrorMsg::MissingMethod("SelectableListBase", "RequestSelect"); - } - - return false; - } - - void* DataItem(int32_t index) - { - static auto dataProp = get_selectable_list_base_helper().GetProperty("Data"); - auto* list = dataProp.GetRaw(this); - if (!list) return nullptr; - - auto* listClass = il2cpp_object_get_class(list); - if (!listClass) return nullptr; - - auto* getItem = il2cpp_class_get_method_from_name(listClass, "get_Item", 1); - if (!getItem) return nullptr; - - Il2CppException* exception = nullptr; - void* args[1] = {&index}; - auto* item = il2cpp_runtime_invoke(getItem, list, args, &exception); - if (exception) return nullptr; - - return item; - } - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "SelectableList"); - return class_helper; - } + il2cpp::List* data() + { return Field<"_data", il2cpp::List*>(); } + int32_t Count() + { return Get<"Count", int32_t>(); } +}; -private: - static IL2CppClassHelper& get_selectable_list_base_helper() - { - static IL2CppClassHelper class_helper = get_class_helper().GetParent("SelectableListBase"); - return class_helper; - } +struct SelectableList : BaseListContainer { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "SelectableList">; - static IL2CppClassHelper& get_base_list_container_helper() - { - static IL2CppClassHelper class_helper = get_class_helper().GetParent("BaseListContainer"); - return class_helper; - } + int32_t SelectedIndex() + { return Get<"SelectedIndex", int32_t>(); } -public: - int32_t __get_SelectedIndex() - { - static auto field = get_class_helper().GetProperty("SelectedIndex"); - return *field.Get(this); - } + // Declared on SelectableListBase. The int overload is picked over RequestSelect(Component, bool). + void RequestSelect(int32_t index, bool simulated = false) + { Call<"RequestSelect">(index, simulated); } - int32_t __get_Count() + il2cpp::Object* DataItem(int32_t index) { - static auto field = get_base_list_container_helper().GetProperty("Count"); - return *field.Get(this); + auto* list = Get<"Data", il2cpp::List*>(); + return list ? (*list)[index] : nullptr; } }; diff --git a/mods/src/prime/SemaphoreButtonListener.h b/mods/src/prime/SemaphoreButtonListener.h index ce980c811..8b29eb344 100644 --- a/mods/src/prime/SemaphoreButtonListener.h +++ b/mods/src/prime/SemaphoreButtonListener.h @@ -2,42 +2,16 @@ #include "Button.h" #include "SemaphoreListenerBase.h" -#include -class SemaphoreButtonListener : SemaphoreListenerBase -{ -public: - __declspec(property(get = __get_Button)) Button* TheButton; - __declspec(property(get = __get_Interactable, put = __set_Interactable)) bool Interactable; +struct SemaphoreButtonListener : SemaphoreListenerBase { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "SemaphoreButtonListener">; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "SemaphoreButtonListener"); - return class_helper; - } - - Button* __get_Button() - { - static auto field = get_class_helper().GetField("_button").offset(); - return *(Button**)((char*)this + field); - } - - bool __get_Interactable() - { - static auto field = get_class_helper().GetProperty("Interactable"); - if (field.isValidHelper()) { - return *field.Get(this); - } - - return false; - } - - void __set_Interactable(bool v) - { - static auto field = get_class_helper().GetProperty("Interactable"); - if (field.isValidHelper()) { - field.SetRaw(this, v); - } - } + Button* TheButton() + { return Field<"_button", Button*>(); } + bool Interactable() + { return Get<"Interactable", bool>(); } + bool isActiveAndEnabled() + { return Get<"isActiveAndEnabled", bool>(); } + void SetInteractable(bool value) + { Set<"Interactable">(value); } }; diff --git a/mods/src/prime/SemaphoreListenerBase.h b/mods/src/prime/SemaphoreListenerBase.h index 638f3113a..13fc27052 100644 --- a/mods/src/prime/SemaphoreListenerBase.h +++ b/mods/src/prime/SemaphoreListenerBase.h @@ -1,16 +1,7 @@ #pragma once -#include +#include -class SemaphoreListenerBase -{ -public: - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "SemaphoreListenerBase"); - return class_helper; - } +struct SemaphoreListenerBase : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "SemaphoreListenerBase">; }; diff --git a/mods/src/prime/ServiceResponse.h b/mods/src/prime/ServiceResponse.h index 2f38bfde0..8a9c289cd 100644 --- a/mods/src/prime/ServiceResponse.h +++ b/mods/src/prime/ServiceResponse.h @@ -2,51 +2,62 @@ #include "EntityGroup.h" -#include - -struct RepeatedField_EntityGroup { -public: - __declspec(property(get = __get_Count)) int Count; - - EntityGroup* get_Item(int index) - { - static auto helper = IL2CppClassHelper{((Il2CppObject*)(this))->klass}; - static auto get_Item = helper.GetMethod("get_Item"); - return get_Item(this, index); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Google.Protobuf", "Google.Protobuf.Collections", "RepeatedField`1"); - return class_helper; - } - -public: - int __get_Count() - { - static auto helper = IL2CppClassHelper{((Il2CppObject*)(this))->klass}; - static auto prop = helper.GetField("count"); - return *(int*)((ptrdiff_t)this + prop.offset()); +#include + +struct ServiceResponse : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "ServiceResponse">; + + // Google.Protobuf.Collections.RepeatedField; List resolves Count/get_Item on it. + il2cpp::List* EntityGroups() + { return Get<"EntityGroups", il2cpp::List*>(); } +}; + +// The platform service's responses, Digit.Platform.Models. They pass through the same model registry code as game +// server responses, but their entity groups have their own type enum. +struct PlatformEntityGroup : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.Platform.Models", "EntityGroup">; + + enum Type : int32_t { + StaticTranslations = 0, + DynamicTranslations = 1, + StoreOffers = 2, + OrderVirtualGood = 3, + Json = 4, + TournamentBundles = 5, + EventRanking = 6, + EventsModel = 7, + LeaderboardRanking = 8, + LeaderboardsModel = 9, + EventNotification = 10, + PlayerStats = 11, + GameNews = 12, + FileSignedUrls = 13, + GameInfo = 14, + PlayerProfiles = 15, + PlayerlifecycleNotification = 16, + MinigamesList = 17, + GameNewsVideo = 18, + PromoCodeRedeem = 19, + StoreGroups = 20, + AllianceGameState = 21, + AllianceGameChangeNotification = 22, + ApmFiltersStatus = 23, + ApmFiltersUpdated = 24, + ListReferralConnections = 25, + RewardRedeem = 26, + LoginStreakRecalculate = 27, + OrdersVirtualGood = 28, }; + + Type Type_() + { return Get<"Type", Type>(); } + ByteString* Group() + { return Get<"Group", ByteString*>(); } }; -struct ServiceResponse { - __declspec(property(get = __get_EntityGroups)) RepeatedField_EntityGroup* EntityGroups; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "ServiceResponse"); - return class_helper; - } - -public: - RepeatedField_EntityGroup* __get_EntityGroups() - { - static auto prop = get_class_helper().GetProperty("EntityGroups"); - return prop.GetRaw(this); - } -}; \ No newline at end of file +struct PlatformServiceResponse : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.Platform.Models", "ServiceResponse">; + + il2cpp::List* EntityGroups() + { return Get<"EntityGroups", il2cpp::List*>(); } +}; diff --git a/mods/src/prime/ShipActionPanelLocalViewController.h b/mods/src/prime/ShipActionPanelLocalViewController.h new file mode 100644 index 000000000..df6edc12a --- /dev/null +++ b/mods/src/prime/ShipActionPanelLocalViewController.h @@ -0,0 +1,20 @@ +#pragma once +#include "GenericButtonWidget.h" + +struct AbilityTimeSpan { int64_t ticks; }; +struct AbilityTimerDataContext : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "TimerDataContext">; + AbilityTimeSpan RemainingTime() + { return Get<"RemainingTime", AbilityTimeSpan>(); } +}; +struct ShipActionPanelLocalViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Ships", "ShipActionPanelLocalViewController">; + il2cpp::Array* abilityIDs() + { return Field<"_abilityIDs", il2cpp::Array*>(); } + il2cpp::Array* buttons() + { return Field<"_buttons", il2cpp::Array*>(); } + AbilityTimerDataContext* GetRemainingCooldown(int64_t abilityId) + { return Call<"GetRemainingCooldown", AbilityTimerDataContext*>(abilityId); } + bool HasShipAnActiveAbilityWithActiveEffect() + { return Call<"HasShipAnActiveAbilityWithActiveEffect", bool>(); } +}; diff --git a/mods/src/prime/ShipBarItemLocalViewController.h b/mods/src/prime/ShipBarItemLocalViewController.h index fa5f63e27..5862bec1d 100644 --- a/mods/src/prime/ShipBarItemLocalViewController.h +++ b/mods/src/prime/ShipBarItemLocalViewController.h @@ -1,8 +1,7 @@ #pragma once -#include +#include -class ShipBarItemLocalViewController -{ - // -}; \ No newline at end of file +struct ShipBarItemLocalViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Ships", "ShipBarItemLocalViewController">; +}; diff --git a/mods/src/prime/ShipManagementScreenStateViewController.h b/mods/src/prime/ShipManagementScreenStateViewController.h index 32cc4e465..2719deb61 100644 --- a/mods/src/prime/ShipManagementScreenStateViewController.h +++ b/mods/src/prime/ShipManagementScreenStateViewController.h @@ -1,40 +1,16 @@ #pragma once -#include - #include "Button.h" - -struct ShipManagementScreenStateViewController { -public: - __declspec(property(get = __get_isActiveAndEnabled)) bool isActiveAndEnabled; - __declspec(property(get = __get__officerIconButton)) Button* _officerIconButton; - __declspec(property(get = __get__swapShipButton)) Button* _swapShipButton; - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Ships", "ShipManagementScreenStateViewController"); - return class_helper; - } - - bool __get_isActiveAndEnabled() - { - static auto property = get_class_helper().GetProperty("isActiveAndEnabled"); - return property.Get(this); - } - - Button* __get__officerIconButton() - { - static auto field = get_class_helper().GetField("_officerIconButton").offset(); - return *(Button**)((char*)this + field); - } - - Button* __get__swapShipButton() - { - static auto field = get_class_helper().GetField("_swapShipButton").offset(); - return *(Button**)((char*)this + field); - } - -private: - friend class ObjectFinder; +#include "ShipManagementViewContext.h" +#include "ViewController.h" + +struct ShipManagementScreenStateViewController : ViewController { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Ships", "ShipManagementScreenStateViewController">; + + bool isActiveAndEnabled() + { return Get<"isActiveAndEnabled", bool>(); } + Button* officerIconButton() + { return Field<"_officerIconButton", Button*>(); } + Button* swapShipButton() + { return Field<"_swapShipButton", Button*>(); } }; diff --git a/mods/src/prime/ShipManagementViewContext.h b/mods/src/prime/ShipManagementViewContext.h index da1215da1..3a5e3c180 100644 --- a/mods/src/prime/ShipManagementViewContext.h +++ b/mods/src/prime/ShipManagementViewContext.h @@ -1,96 +1,26 @@ #pragma once -#include - -#include "errormsg.h" #include "FleetPlayerData.h" -struct ShipManagementViewContext { -public: - FleetPlayerData* CurrentFleet() - { - static auto method = get_class_helper().GetMethod("get_CurrentFleet"); - return method ? method(this) : nullptr; - } +#include - int32_t CurrentIndex() - { - static auto CurrentIndexWarn = true; - static auto CurrentIndexMethod = - get_class_helper().GetMethod("get_CurrentIndex"); +#include - if (CurrentIndexMethod) { - return CurrentIndexMethod(this); - } else if (CurrentIndexWarn) { - CurrentIndexWarn = false; - ErrorMsg::MissingMethod("ShipManagementViewContext", "get_CurrentIndex"); - } - - return -1; - } +struct ShipManagementViewContext : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Ships", "ShipManagementViewContext">; + FleetPlayerData* CurrentFleet() + { return Get<"CurrentFleet", FleetPlayerData*>(); } + void Sort() + { Call<"Sort">(); } + int32_t CurrentIndex() + { return Get<"CurrentIndex", int32_t>(); } void SetCurrentIndex(int32_t index) - { - static auto SetCurrentIndexWarn = true; - static auto SetCurrentIndexMethod = - get_class_helper().GetMethod("set_CurrentIndex"); - - if (SetCurrentIndexMethod) { - SetCurrentIndexMethod(this, index); - } else if (SetCurrentIndexWarn) { - SetCurrentIndexWarn = false; - ErrorMsg::MissingMethod("ShipManagementViewContext", "set_CurrentIndex"); - } - } - + { Set<"CurrentIndex">(index); } void SetLastMovementWasToIncrement(bool increment) - { - static auto Warn = true; - static auto SetLast = get_class_helper().GetMethod( - "set_LastMovementWasToIncrement"); - - if (SetLast) { - SetLast(this, increment); - } else if (Warn) { - Warn = false; - ErrorMsg::MissingMethod("ShipManagementViewContext", "set_LastMovementWasToIncrement"); - } - } - + { Set<"LastMovementWasToIncrement">(increment); } int32_t ListCount() - { - static auto ListCountWarn = true; - static auto ListCountMethod = - get_class_helper().GetMethod("get_ListCount"); - - if (ListCountMethod) { - return ListCountMethod(this); - } else if (ListCountWarn) { - ListCountWarn = false; - ErrorMsg::MissingMethod("ShipManagementViewContext", "get_ListCount"); - } - - return -1; - } - - void Sort() - { - static auto SortWarn = true; - static auto SortMethod = get_class_helper().GetMethod("Sort"); - - if (SortMethod) { - SortMethod(this); - } else if (SortWarn) { - SortWarn = false; - ErrorMsg::MissingMethod("ShipManagementViewContext", "Sort"); - } - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Ships", "ShipManagementViewContext"); - return class_helper; - } + { return Get<"ListCount", int32_t>(); } + il2cpp::List* SortedIdleShips() + { return Field<"SortedIdleShips", il2cpp::List*>(); } }; diff --git a/mods/src/prime/ShipManagementViewController.h b/mods/src/prime/ShipManagementViewController.h index 388b9c275..148f99dc1 100644 --- a/mods/src/prime/ShipManagementViewController.h +++ b/mods/src/prime/ShipManagementViewController.h @@ -1,47 +1,14 @@ #pragma once -#include - #include "ShipManagementViewContext.h" +#include "ViewController.h" -#include - -struct ShipManagementViewController { -public: - __declspec(property(get = __get_isActiveAndEnabled)) bool isActiveAndEnabled; +struct ShipManagementViewController : ViewController { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Ships", "ShipManagementViewController">; - // m_context lives on the generic ViewController base, so its offset is only - // discoverable by walking the class hierarchy. + // m_context lives on the generic ViewController base; field lookup walks the parents. ShipManagementViewContext* Context() - { - static auto* field = [] { - for (auto* cls = get_class_helper().get_cls(); cls != nullptr; cls = il2cpp_class_get_parent(cls)) { - void* iterator = nullptr; - while (auto* candidate = il2cpp_class_get_fields(cls, &iterator)) { - const auto* name = il2cpp_field_get_name(candidate); - if (name != nullptr && std::strcmp(name, "m_context") == 0) { - return candidate; - } - } - } - return static_cast(nullptr); - }(); - return field != nullptr ? *(ShipManagementViewContext**)((ptrdiff_t)this + field->offset) : nullptr; - } - - bool __get_isActiveAndEnabled() - { - static auto property = get_class_helper().GetProperty("isActiveAndEnabled"); - return property.Get(this); - } - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Ships", "ShipManagementViewController"); - return class_helper; - } - -private: - friend class ObjectFinder; + { return Field<"m_context", ShipManagementViewContext*>(); } + bool isActiveAndEnabled() + { return Get<"isActiveAndEnabled", bool>(); } }; diff --git a/mods/src/prime/ShipTileWidget.h b/mods/src/prime/ShipTileWidget.h index 0960f3d1d..a9cd16e57 100644 --- a/mods/src/prime/ShipTileWidget.h +++ b/mods/src/prime/ShipTileWidget.h @@ -1,18 +1,8 @@ #pragma once -#include - #include "FleetPlayerData.h" #include "Widget.h" -struct ShipTileWidget : public Widget { -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Ships", "ShipTileWidget"); - return class_helper; - } - -private: - friend struct Widget; +struct ShipTileWidget : Widget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Ships", "ShipTileWidget">; }; diff --git a/mods/src/prime/ShopSummaryDirector.h b/mods/src/prime/ShopSummaryDirector.h index a81933a2e..2765c0388 100644 --- a/mods/src/prime/ShopSummaryDirector.h +++ b/mods/src/prime/ShopSummaryDirector.h @@ -1,28 +1,13 @@ #pragma once -#include +#include -#include "BattleTargetData.h" -#include "ScanEngageButtonsWidget.h" -#include "ViewController.h" +#include -class ShopSummaryDirector -{ -public: - __declspec(property(get = __get__backLogicSkipSectionIds)) Il2CppArraySize* _backLogicSkipSectionIds; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Shop", "ShopSummaryDirector"); - return class_helper; - } - -public: - Il2CppArraySize* __get__backLogicSkipSectionIds() - { - static auto field = get_class_helper().GetField("_backLogicSkipSectionIds").offset(); - return *(Il2CppArraySize**)((char*)this + field); - } +struct ShopSummaryDirector : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Shop", "ShopSummaryDirector">; + // SectionID[] declared on SectionDirectorBase; elements are the SectionID enum (Hub.h). + il2cpp::Array* backLogicSkipSectionIds() + { return Field<"_backLogicSkipSectionIds", il2cpp::Array*>(); } }; diff --git a/mods/src/prime/ShortcutsManager.h b/mods/src/prime/ShortcutsManager.h index 5529506f2..e1f814a2c 100644 --- a/mods/src/prime/ShortcutsManager.h +++ b/mods/src/prime/ShortcutsManager.h @@ -3,12 +3,13 @@ #include "MonoSingleton.h" struct ShortcutsManager : MonoSingleton { - friend struct MonoSingleton; + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.GameInput", "ShortcutsManager">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.GameInput", "ShortcutsManager"); - return class_helper; - } + bool ShowKeybindings() + { return Get<"ShowKeybindings", bool>(); } + void SetShowKeybindings(bool value) + { Set<"ShowKeybindings">(value); } + // Private static gate the native shortcut actions check (tutorial, modals, text input). + static bool CanUseShortcuts() + { return Class::Get<"CanUseShortcuts", bool>(); } }; diff --git a/mods/src/prime/SpecService.h b/mods/src/prime/SpecService.h index cc71b775c..16f75dde7 100644 --- a/mods/src/prime/SpecService.h +++ b/mods/src/prime/SpecService.h @@ -1,22 +1,14 @@ #pragma once -#include #include "HullSpec.h" -struct SpecService { -public: - HullSpec* GetHull(long hullId) - { - static auto method = - get_class_helper().GetMethod("GetHull"); - return method(this, hullId); - } +#include -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "SpecService"); - return class_helper; - } +#include + +struct SpecService : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Services", "SpecService">; + + HullSpec* GetHull(int64_t hullId) + { return Call<"GetHull", HullSpec*>(hullId); } }; diff --git a/mods/src/prime/StarNodeObjectViewerWidget.h b/mods/src/prime/StarNodeObjectViewerWidget.h index 43f2ea507..7eaf95ca1 100644 --- a/mods/src/prime/StarNodeObjectViewerWidget.h +++ b/mods/src/prime/StarNodeObjectViewerWidget.h @@ -1,31 +1,15 @@ #pragma once -#include - +#include "GenericButtonWidget.h" #include "ObjectViewerBaseWidget.h" -struct StarNodeObjectViewerWidget : public ObjectViewerBaseWidget { -public: +struct StarNodeObjectViewerWidget : ObjectViewerBaseWidget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "StarNodeObjectViewerWidget">; + void OnViewButtonActivation() - { - static auto OnViewButtonActivation = - get_class_helper().GetMethod("OnViewButtonActivation"); - OnViewButtonActivation(this); - } + { Call<"OnViewButtonActivation">(); } void InitiateWarp() - { - static auto InitiateWarp = get_class_helper().GetMethod("InitiateWarp"); - InitiateWarp(this); - } - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "StarNodeObjectViewerWidget"); - return class_helper; - } - -private: - friend class ObjectFinder; - friend class ObjectViewerBaseWidget; + { Call<"InitiateWarp">(); } + GenericButtonWidget* visitButton() + { return Field<"_visitButton", GenericButtonWidget*>(); } }; diff --git a/mods/src/prime/StateContainer.h b/mods/src/prime/StateContainer.h index 609907098..a6b3bbac4 100644 --- a/mods/src/prime/StateContainer.h +++ b/mods/src/prime/StateContainer.h @@ -1,32 +1,14 @@ #pragma once -#include - +// Digit.PrimeServer.Models.StateContainer, a managed struct. Layout matches T values of at most +// pointer alignment (the game uses it with int enums). template struct StateContainer { -public: - __declspec(property(get = __get_CurrentState)) T CurrentState; - __declspec(property(get = __get_PreviousState)) T PreviousState; - -private - T _currentState; -private - T _previousState; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "StateContainer`1"); - return class_helper; - } - T __get_CurrentState() - { - static auto field = get_class_helper().GetProperty("CurrentState"); - return *field.Get(this); - } - T __get_PreviousState() - { - static auto field = get_class_helper().GetProperty("PreviousState"); - return *field.Get(this); - } -}; \ No newline at end of file + T currentState; + T previousState; + void* equalityComparer; + + T CurrentState() const + { return currentState; } + T PreviousState() const + { return previousState; } +}; diff --git a/mods/src/prime/SystemViewScaler.h b/mods/src/prime/SystemViewScaler.h index c31682d93..2a82b2b74 100644 --- a/mods/src/prime/SystemViewScaler.h +++ b/mods/src/prime/SystemViewScaler.h @@ -1,28 +1,12 @@ #pragma once -#include +#include -struct SystemViewScaler { -public: - __declspec(property(get = __get__baseSystemRadius, put = __set__baseSystemRadius)) float _baseSystemRadius; +struct SystemViewScaler : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.Navigation", "SystemViewScaler">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.Navigation", "SystemViewScaler"); - return class_helper; - } - -public: - float __get__baseSystemRadius() - { - static auto field = get_class_helper().GetField("_baseSystemRadius"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set__baseSystemRadius(float v) - { - static auto field = get_class_helper().GetField("_baseSystemRadius"); - *(float*)((ptrdiff_t)this + field.offset()) = v; - } + float baseSystemRadius() + { return Field<"_baseSystemRadius", float>(); } + void SetBaseSystemRadius(float value) + { SetField<"_baseSystemRadius">(value); } }; diff --git a/mods/src/prime/TKTouch.h b/mods/src/prime/TKTouch.h index cf52983b5..739087db6 100644 --- a/mods/src/prime/TKTouch.h +++ b/mods/src/prime/TKTouch.h @@ -1,6 +1,8 @@ #pragma once -#include +#include + +#include enum class TouchPhase : int32_t { Began = 0, @@ -10,27 +12,11 @@ enum class TouchPhase : int32_t { Canceled = 4, }; -struct TKTouch { -public: - __declspec(property(get = __get_phase, put = __set_phase)) TouchPhase phase; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("TouchKit", "", "TKTouch"); - return class_helper; - } - -public: - TouchPhase __get_phase() - { - static auto field = get_class_helper().GetField("phase"); - return *reinterpret_cast(reinterpret_cast(this) + field.offset()); - } +struct TKTouch : il2cpp::Object { + using Class = il2cpp::Class<"TouchKit", "", "TKTouch">; - void __set_phase(TouchPhase phase) - { - static auto field = get_class_helper().GetField("phase"); - *reinterpret_cast(reinterpret_cast(this) + field.offset()) = phase; - } + TouchPhase phase() + { return Field<"phase", TouchPhase>(); } + void SetPhase(TouchPhase phase) + { SetField<"phase">(phase); } }; diff --git a/mods/src/prime/TMP_InputField.h b/mods/src/prime/TMP_InputField.h index e1a50fdc2..b92afd234 100644 --- a/mods/src/prime/TMP_InputField.h +++ b/mods/src/prime/TMP_InputField.h @@ -1,50 +1,19 @@ #pragma once -#include "GameObject.h" +#include -class TMP_InputField -{ -public: - __declspec(property(get = __get_isFocused)) bool isFocused; +struct TMP_InputField : il2cpp::Object { + using Class = il2cpp::Class<"Unity.TextMeshPro", "TMPro", "TMP_InputField">; - void ActivateInputField() - { - static auto ActivateInputField = get_class_helper().GetMethod("ActivateInputField"); - ActivateInputField(this); - } - - void DeactivateInputField() - { - static auto DeactivateInputField = get_class_helper().GetMethod("DeactivateInputField"); - DeactivateInputField(this); - } + bool isFocused() + { return Get<"isFocused", bool>(); } + void ActivateInputField() + { Call<"ActivateInputField">(); } + void DeactivateInputField(bool clearSelection = false) + { Call<"DeactivateInputField">(clearSelection); } void SendOnFocusLost() - { - static auto SendOnFocusLost = get_class_helper().GetMethod("SendOnFocusLost"); - SendOnFocusLost(this); - } - + { Call<"SendOnFocusLost">(); } void SendOnFocus() - { - static auto SendOnFocus = get_class_helper().GetMethod("SendOnFocus"); - SendOnFocus(this); - } - -private: - friend class ObjectFinder; - friend struct GameObject; - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Unity.TextMeshPro", "TMPro", "TMP_InputField"); - return class_helper; - } - -public: - bool __get_isFocused() - { - static auto field = get_class_helper().GetProperty("isFocused"); - const auto* focused = field.Get(this); - return focused && *focused; - } + { Call<"SendOnFocus">(); } }; diff --git a/mods/src/prime/TabBarViewController.h b/mods/src/prime/TabBarViewController.h index f4b0dc76e..b0259c980 100644 --- a/mods/src/prime/TabBarViewController.h +++ b/mods/src/prime/TabBarViewController.h @@ -1,79 +1,20 @@ #pragma once -#include +#include "SelectableList.h" -#include "IList.h" +#include -class BaseListContainer -{ -public: - __declspec(property(get = __get__data)) IList* _data; +struct TabBar : SelectableList { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "TabBar">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "BaseListContainer"); - return class_helper; - } - -public: - IList* __get__data() - { - static auto field = get_class_helper().GetField("_data").offset(); - return *(IList**)((uintptr_t)this + field); - } + // GenericButtonContext[] + il2cpp::Array* defaultOptions() + { return Field<"_defaultOptions", il2cpp::Array*>(); } }; -class TabBar -{ -public: - __declspec(property(get = __get__defaultOptions)) Il2CppArray* _defaultOptions; - __declspec(property(get = __get__listContainer)) BaseListContainer* _listContainer; - __declspec(property(get = __get__data)) IList* _data; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "TabBar"); - return class_helper; - } - -public: - Il2CppArray* __get__defaultOptions() - { - static auto field = get_class_helper().GetField("_defaultOptions").offset(); - return *(Il2CppArray**)((uintptr_t)this + field); - } - - BaseListContainer* __get__listContainer() - { - static auto field = get_class_helper().GetField("_listContainer").offset(); - return *(BaseListContainer**)((uintptr_t)this + field); - } +struct TabBarViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "TabBarViewController">; - IList* __get__data() - { - static auto field = get_class_helper().GetField("_data").offset(); - return *(IList**)((uintptr_t)this + field); - } + TabBar* tabBar() + { return Field<"_tabBar", TabBar*>(); } }; - -class TabBarViewController -{ -public: - __declspec(property(get = __get__tabBar)) TabBar* _tabBar; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "TabBarViewController"); - return class_helper; - } - -public: - TabBar* __get__tabBar() - { - static auto field = get_class_helper().GetField("_tabBar").offset(); - return *(TabBar**)((uintptr_t)this + field); - } -}; \ No newline at end of file diff --git a/mods/src/prime/TextLocalizer.h b/mods/src/prime/TextLocalizer.h new file mode 100644 index 000000000..ee225d23b --- /dev/null +++ b/mods/src/prime/TextLocalizer.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +#include + +// Digit.Client.UI.TextLocalizer: the label component that renders localized, parameterized text. +struct TextLocalizer : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "TextLocalizer">; + + // Declared on GraphicLocalizer<,>. + Il2CppString* identifier() + { return Field<"m_identifier", Il2CppString*>(); } + // The backing field, not the SignificantDecimals property. + int32_t significantDecimals() + { return Field<"m_significantDecimals", int32_t>(); } + void SetSignificantDecimals(int32_t value) + { SetField<"m_significantDecimals">(value); } + bool textOverride() + { return Field<"_textOverride", bool>(); } + // Declared on GraphicLocalizer<,>: the rendered graphic, resolved lazily even on inactive labels. + il2cpp::Object* Target() + { return Get<"Target", il2cpp::Object*>(); } + + Il2CppString* CurrentText() + { return Call<"CurrentText", Il2CppString*>(); } + void ClearTextOverride() + { Call<"ClearTextOverride">(); } + void OverrideLocalizedText(Il2CppString* text) + { Call<"OverrideLocalizedText">(text); } +}; + +struct ColourTextLocalizer : TextLocalizer { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "ColourTextLocalizer">; +}; diff --git a/mods/src/prime/Toast.h b/mods/src/prime/Toast.h index b88192684..0db5a0bc9 100644 --- a/mods/src/prime/Toast.h +++ b/mods/src/prime/Toast.h @@ -1,6 +1,6 @@ #pragma once -#include +#include enum ToastState { All = -1, @@ -64,35 +64,16 @@ enum ToastState { GalacticAnomalySystemEntered = 58, }; -struct Toast { -public: - Il2CppArray* get_TextParameters() - { - static auto prop = get_class_helper().GetProperty("TextParameters"); - return prop.GetRaw(this); - } +struct Toast : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.HUD", "Toast">; + il2cpp::Array* get_TextParameters() + { return Get<"TextParameters", il2cpp::Array*>(); } + // LocaleTextContext void* get_TextLocaleTextContext() - { - static auto prop = get_class_helper().GetProperty("TextLocaleTextContext"); - return prop.GetRaw(this); - } - + { return Get<"TextLocaleTextContext", void*>(); } Il2CppObject* get_Data() - { - return *reinterpret_cast(reinterpret_cast(this) + 0x38); - } - + { return Get<"Data", Il2CppObject*>(); } int get_State() - { - static auto prop = get_class_helper().GetProperty("State"); - return *prop.Get((void *)this); - } - -private: - static IL2CppClassHelper &get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.HUD", "Toast"); - return class_helper; - } + { return Get<"State", ToastState>(); } }; diff --git a/mods/src/prime/Transform.h b/mods/src/prime/Transform.h index a97609f90..496db99a7 100644 --- a/mods/src/prime/Transform.h +++ b/mods/src/prime/Transform.h @@ -1,56 +1,32 @@ #pragma once -#include - #include "GameObject.h" #include "Vector3.h" -struct Transform { - __declspec(property(get = __get_LocalScale, put = __set_LocalScale)) Vector3* localScale; - __declspec(property(get = __get_ChildCount)) int32_t childCount; - __declspec(property(get = __get_GameObject)) GameObject* gameObject; - __declspec(property(get = __get_Parent)) Transform* parent; - - Vector3* __get_LocalScale() - { - static auto field = get_class_helper().GetProperty("localScale"); - return field.Get(this); - } - - void __set_LocalScale(Vector3* v) - { - static auto prop = get_class_helper().GetProperty("localScale"); - return prop.SetRaw((void*)this, *v); - } +#include - int32_t __get_ChildCount() - { - static auto field = get_class_helper().GetProperty("childCount"); - return *field.Get(this); - } - - GameObject* __get_GameObject() - { - static auto field = get_class_helper().GetParent("Component").GetProperty("gameObject"); - return field.GetRaw(this); - } +struct Transform : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Transform">; + int32_t childCount() + { return Get<"childCount", int32_t>(); } Transform* GetChild(int32_t index) - { - static auto method = get_class_helper().GetMethod("GetChild"); - return method != nullptr ? method(this, index) : nullptr; - } - - Transform* __get_Parent() - { - static auto field = get_class_helper().GetProperty("parent"); - return field.GetRaw(this); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Transform"); - return class_helper; - } + { return Call<"GetChild", Transform*>(index); } + Transform* parent() + { return Get<"parent", Transform*>(); } + GameObject* gameObject() + { return Get<"gameObject", GameObject*>(); } + Vector3 localScale() + { return Get<"localScale", Vector3>(); } + void SetLocalScale(Vector3 value) + { Set<"localScale">(value); } + void SetLocalEulerAngles(Vector3 value) + { Set<"localEulerAngles">(value); } + void SetParent(Transform* parent, bool worldPositionStays) + { Call<"SetParent">(parent, worldPositionStays); } + void SetAsFirstSibling() + { Call<"SetAsFirstSibling">(); } + // Finds a descendant by slash-separated path, e.g. "Hint/Background". + Transform* Find(Il2CppString* path) + { return Call<"Find", Transform*>(path); } }; diff --git a/mods/src/prime/TransitionManager.h b/mods/src/prime/TransitionManager.h index 6a84dc94b..73210d33a 100644 --- a/mods/src/prime/TransitionManager.h +++ b/mods/src/prime/TransitionManager.h @@ -2,34 +2,18 @@ #include "BlurController.h" -struct TransitionManager { -public: - __declspec(property(get = __get_BlurController)) BlurController* SBlurController; - __declspec(property(get = __get__waitFrames, put = __set__waitFrames)) int _waitFrames; +#include -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.LoadingScreen", "TransitionManager"); - return class_helper; - } +#include -public: - BlurController* __get_BlurController() - { - static auto field = get_class_helper().GetField("BlurController"); - return *(BlurController**)((ptrdiff_t)this + field.offset()); - } +struct TransitionManager : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.LoadingScreen", "TransitionManager">; - int __get__waitFrames() - { - static auto field = get_class_helper().GetField("_waitFrames"); - return *(int*)((ptrdiff_t)this + field.offset()); - } - void __set__waitFrames(int v) - { - static auto field = get_class_helper().GetField("_waitFrames"); - *(int*)((ptrdiff_t)this + field.offset()) = v; - } -}; \ No newline at end of file + // Public field named BlurController; the S prefix keeps the type name visible in this scope. + BlurController* SBlurController() + { return Field<"BlurController", BlurController*>(); } + int32_t waitFrames() + { return Field<"_waitFrames", int32_t>(); } + void SetWaitFrames(int32_t value) + { SetField<"_waitFrames">(value); } +}; diff --git a/mods/src/prime/TransitionViewController.h b/mods/src/prime/TransitionViewController.h index 05865d5e1..c23b3a838 100644 --- a/mods/src/prime/TransitionViewController.h +++ b/mods/src/prime/TransitionViewController.h @@ -1,39 +1,17 @@ #pragma once -#include +#include -struct TransitionViewController { -public: - __declspec(property(get = __get__staticOverride)) void* _staticOverride; - __declspec(property(get = __get__animator)) void* _animator; - __declspec(property(get = __get__scroller)) void* _scroller; +struct TransitionViewController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.LoadingScreen", "TransitionViewController">; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.LoadingScreen", "TransitionViewController"); - return class_helper; - } - -public: - void* __get__staticOverride() - { - if (!this) return nullptr; - static auto field = get_class_helper().GetField("_staticOverride"); - if (!field.isValidHelper()) return nullptr; - return *(void**)((ptrdiff_t)this + field.offset()); - } - - void* __get__animator() - { - static auto field = get_class_helper().GetField("_animator"); - return *(void**)((ptrdiff_t)this + field.offset()); - } - - void* __get__scroller() - { - static auto field = get_class_helper().GetField("_scroller"); - return *(void**)((ptrdiff_t)this + field.offset()); - } + // Image + void* staticOverride() + { return Field<"_staticOverride", void*>(); } + // Animator + void* animator() + { return Field<"_animator", void*>(); } + // SmartScroller + void* scroller() + { return Field<"_scroller", void*>(); } }; diff --git a/mods/src/prime/UIBehaviour.h b/mods/src/prime/UIBehaviour.h index 1f0eda976..07edd67a3 100644 --- a/mods/src/prime/UIBehaviour.h +++ b/mods/src/prime/UIBehaviour.h @@ -1,12 +1,7 @@ #pragma once -#include -struct UIBehaviour { -public: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("UnityEngine.UI", "UnityEngine.EventSystems", "UIBehaviour"); - return class_helper; - } -}; \ No newline at end of file +#include + +struct UIBehaviour : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.UI", "UnityEngine.EventSystems", "UIBehaviour">; +}; diff --git a/mods/src/prime/UnityWebRequest.h b/mods/src/prime/UnityWebRequest.h index b41908311..5f636a94f 100644 --- a/mods/src/prime/UnityWebRequest.h +++ b/mods/src/prime/UnityWebRequest.h @@ -1,21 +1,12 @@ #pragma once -#include +#include -struct UnityWebRequest { -public: - const uintptr_t get_uploadHandler() - { - static auto prop = get_class_helper().GetProperty("uploadHandler"); - auto s = prop.Get((void*)this); - return (uintptr_t)s; - } +#include -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("UnityEngine.UnityWebRequestModule", "UnityEngine.Networking", "UnityWebRequest"); - return class_helper; - } +struct UnityWebRequest : il2cpp::Object { + using Class = il2cpp::Class<"UnityEngine.UnityWebRequestModule", "UnityEngine.Networking", "UnityWebRequest">; + + uintptr_t get_uploadHandler() + { return reinterpret_cast(Get<"uploadHandler", Il2CppObject*>()); } }; diff --git a/mods/src/prime/UserProfile.h b/mods/src/prime/UserProfile.h index 6335c6bb4..62da80e24 100644 --- a/mods/src/prime/UserProfile.h +++ b/mods/src/prime/UserProfile.h @@ -1,44 +1,18 @@ #pragma once -#include +#include -struct UserProfile { -public: - __declspec(property(get = __get_LocaId)) long LocaId; - __declspec(property(get = __get_Name)) Il2CppString* Name; - __declspec(property(get = __get_UserId)) Il2CppString* UserId; - __declspec(property(get = __get_Level)) int32_t Level; +#include -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "UserProfile"); - return class_helper; - } +struct UserProfile : il2cpp::Object { + using Class = il2cpp::Class<"Digit.Client.PrimeLib.Runtime", "Digit.PrimeServer.Models", "UserProfile">; -public: - long __get_LocaId() - { - static auto field = get_class_helper().GetField("_locaId").offset(); - return *(long*)((char*)this + field); - } - - Il2CppString* __get_Name() - { - static auto field = get_class_helper().GetField("name_").offset(); - return *(Il2CppString**)((char*)this + field); - } - - Il2CppString* __get_UserId() - { - static auto prop = get_class_helper().GetProperty("UserId"); - return prop.GetRaw(this); - } - - int32_t __get_Level() - { - static auto prop = get_class_helper().GetProperty("Level"); - return *prop.Get(this); - } + int64_t LocaId() + { return Field<"_locaId", int64_t>(); } + Il2CppString* Name() + { return Field<"name_", Il2CppString*>(); } + Il2CppString* UserId() + { return Get<"UserId", Il2CppString*>(); } + int32_t Level() + { return Get<"Level", int32_t>(); } }; diff --git a/mods/src/prime/UserProfileManager.h b/mods/src/prime/UserProfileManager.h index 90da6b935..e032c778d 100644 --- a/mods/src/prime/UserProfileManager.h +++ b/mods/src/prime/UserProfileManager.h @@ -3,20 +3,9 @@ #include "MonoSingleton.h" #include "UserProfile.h" -#include - struct UserProfileManager : MonoSingleton { - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.PlayerProfile", "UserProfileManager"); - return class_helper; - } + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.PlayerProfile", "UserProfileManager">; [[nodiscard]] UserProfile* GetLocalUserProfile() - { - static auto method = get_class_helper().GetInvokeMethod("GetLocalUserProfile"); - return method.Invoke(this); - } + { return Call<"GetLocalUserProfile", UserProfile*>(); } }; diff --git a/mods/src/prime/UserProfileWidget.h b/mods/src/prime/UserProfileWidget.h index 732c94226..205f08845 100644 --- a/mods/src/prime/UserProfileWidget.h +++ b/mods/src/prime/UserProfileWidget.h @@ -1,22 +1,10 @@ #pragma once - -#include - -struct PlayerTitleWidget; - -struct UserProfileWidget { - __declspec(property(get = __get__playerTitleWidget)) PlayerTitleWidget* _playerTitleWidget; - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.PlayerProfile", "UserProfileWidget"); - return class_helper; - } - - PlayerTitleWidget* __get__playerTitleWidget() - { - static auto field = get_class_helper().GetField("_playerTitleWidget").offset(); - return *(PlayerTitleWidget**)((uintptr_t)this + field); - } +#include +struct PlayerTitleWidget : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.PlayerAvatars", "PlayerTitleWidget">; +}; +struct UserProfileWidget : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.PlayerProfile", "UserProfileWidget">; + PlayerTitleWidget* playerTitleWidget() + { return Field<"_playerTitleWidget", PlayerTitleWidget*>(); } }; diff --git a/mods/src/prime/Vector2.h b/mods/src/prime/Vector2.h index 150131886..329435ede 100644 --- a/mods/src/prime/Vector2.h +++ b/mods/src/prime/Vector2.h @@ -1,45 +1,11 @@ #pragma once -#include +#include #include struct Vector2 { -public: - __declspec(property(get = __get_x, put = __set_x)) float x; - __declspec(property(get = __get_y, put = __set_y)) float y; - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Vector2"); - return class_helper; - } - -public: - float __get_x() - { - static auto field = get_class_helper().GetField("x"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set_x(float v) - { - static auto field = get_class_helper().GetField("x"); - *(float*)((ptrdiff_t)this + field.offset()) = v; - } - - float __get_y() - { - static auto field = get_class_helper().GetField("y"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set_y(float v) - { - static auto field = get_class_helper().GetField("y"); - *(float*)((ptrdiff_t)this + field.offset()) = v; - } + float x, y; }; #pragma pack(push, 1) diff --git a/mods/src/prime/Vector3.h b/mods/src/prime/Vector3.h index a38fecd4e..2f1b0cf32 100644 --- a/mods/src/prime/Vector3.h +++ b/mods/src/prime/Vector3.h @@ -1,53 +1,14 @@ #pragma once -#include +#include struct Vector3 { -public: + using Class = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Vector3">; + float x; float y; float z; -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Vector3"); - return class_helper; - } - -public: static Vector3 zero() - { - return get_class_helper().GetStaticField("zeroVector").Get(); - } - - /* float __get_x() { - static auto field = get_class_helper().GetField("x"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set_x(float v) { - static auto field = get_class_helper().GetField("x"); - *(float*)((ptrdiff_t)this + field.offset()) = v; - } - - float __get_y() { - static auto field = get_class_helper().GetField("y"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set_y(float v) { - static auto field = get_class_helper().GetField("y"); - *(float*)((ptrdiff_t)this + field.offset()) = v; - } - - float __get_z() { - static auto field = get_class_helper().GetField("z"); - return *(float*)((ptrdiff_t)this + field.offset()); - } - - void __set_z(float v) { - static auto field = get_class_helper().GetField("z"); - *(float*)((ptrdiff_t)this + field.offset()) = v; - }*/ -}; \ No newline at end of file + { return Class::Field<"zeroVector", Vector3>(); } +}; diff --git a/mods/src/prime/ViewController.h b/mods/src/prime/ViewController.h index 1a4236450..5ec1584ab 100644 --- a/mods/src/prime/ViewController.h +++ b/mods/src/prime/ViewController.h @@ -1,22 +1,9 @@ #pragma once -#include +#include -template struct ViewController { -public: - __declspec(property(get = __get_CanvasContext)) T* CanvasContext; - -private: - static IL2CppClassHelper& get_class_helper() - { - static IL2CppClassHelper class_helper = Y::get_class_helper().GetParent("ViewController`1"); - return class_helper; - } - -public: - T* __get_CanvasContext() - { - static auto field = get_class_helper().GetProperty("CanvasContext"); - return field.GetRaw(this); - } -}; \ No newline at end of file +// Digit.Client.UI.ViewController`1. Members resolve on each controller's runtime class. +template struct ViewController : il2cpp::Object { + T* CanvasContext() + { return Get<"CanvasContext", T*>(); } +}; diff --git a/mods/src/prime/VisibilityController.h b/mods/src/prime/VisibilityController.h index 262b23a8b..87713329c 100644 --- a/mods/src/prime/VisibilityController.h +++ b/mods/src/prime/VisibilityController.h @@ -1,8 +1,10 @@ #pragma once -#include +#include -enum class VisibilityState // TypeDefIndex: 13743 +#include + +enum class VisibilityState : int32_t // TypeDefIndex: 13743 { Unknown = 0, Show = 1, @@ -11,60 +13,17 @@ enum class VisibilityState // TypeDefIndex: 13743 Visible = 4 }; -struct VisibilityController { -public: - __declspec(property(get = __get__state)) VisibilityState _state; - __declspec(property(get = __get_State)) VisibilityState State; - - void Show(bool instant = false) - { - auto Show = get_class_helper().GetMethodSpecial( - "Show", [](int param_count, const Il2CppType** param) -> bool { - if (param_count != 1) { - return false; - } - return param[0]->type == IL2CPP_TYPE_BOOLEAN; - }); - return Show(this, instant); - } +struct VisibilityController : il2cpp::Object { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Client.UI", "VisibilityController">; + // bool selects Show(bool)/Hide(bool) over the int entry-point overloads. + void Show(bool instant = false) + { Call<"Show">(instant); } void Hide(bool instant = false) - { - auto Hide = get_class_helper().GetMethodSpecial( - "Hide", [](int param_count, const Il2CppType** param) -> bool { - if (param_count != 1) { - return false; - } - return param[0]->type == IL2CPP_TYPE_BOOLEAN; - }); - return Hide(this, instant); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "VisibilityController"); - return class_helper; - } - -public: - VisibilityState __get__state() - { - static auto field = get_class_helper().GetField("_state"); - if (field.isValidHelper()) { - return *(VisibilityState*)((ptrdiff_t)this + field.offset()); - } - - return VisibilityState::Unknown; - } - - VisibilityState __get_State() - { - static auto prop = get_class_helper().GetProperty("VisibilityState"); - if (prop.isValidHelper()) { - return *prop.Get(this); - } + { Call<"Hide">(instant); } - return VisibilityState::Unknown; - } + VisibilityState state() + { return Field<"_state", VisibilityState>(); } + VisibilityState State() + { return Get<"VisibilityState", VisibilityState>(); } }; diff --git a/mods/src/prime/WaveDefenseObjectViewerWidget.h b/mods/src/prime/WaveDefenseObjectViewerWidget.h index 436f675aa..ac8ada8bd 100644 --- a/mods/src/prime/WaveDefenseObjectViewerWidget.h +++ b/mods/src/prime/WaveDefenseObjectViewerWidget.h @@ -3,39 +3,33 @@ #include "GenericButtonWidget.h" #include "ObjectViewerBaseWidget.h" -struct WaveDefenseObjectViewerWidget : public ObjectViewerBaseWidget { -public: - static Il2CppArraySize* FindActiveViewers() +struct WaveDefenseObjectViewerWidget : ObjectViewerBaseWidget { + using Class = il2cpp::Class<"Assembly-CSharp", "Digit.Prime.ObjectViewer", "WaveDefenseObjectViewerWidget">; + + // Every active viewer of this class in the loaded scenes. + static il2cpp::Array* FindActiveViewers() { - static auto object_class = il2cpp_get_class_helper("UnityEngine.CoreModule", "UnityEngine", "Object"); - static auto find = object_class.GetMethod("FindObjectsOfType", 1); - auto& viewer_class = get_class_helper(); - return find && viewer_class.get_cls() ? find(viewer_class.GetType()) : nullptr; + using UnityObject = il2cpp::Class<"UnityEngine.CoreModule", "UnityEngine", "Object">; + auto* type = Class::TypeObject(); + return type ? UnityObject::Call<"FindObjectsOfType", il2cpp::Array*>(type) + : nullptr; } + GenericButtonWidget* primaryButton() + { return Field<"_primaryButton", GenericButtonWidget*>(); } + void PressPrimaryButton() { - static auto field = get_class_helper().GetField("_primaryButton"); - if (!field.get_info()) { - return; - } - auto widget = *(GenericButtonWidget**)((char*)this + field.offset()); - if (!widget || !widget->isActiveAndEnabled) { + auto* widget = primaryButton(); + if (!widget || !widget->isActiveAndEnabled()) { return; } - auto context = widget->Context; - auto listener = widget->SemaphoreListener; - if (context && context->Interactable && listener) { - if (auto button = listener->TheButton) { + auto* context = widget->Context(); + auto* listener = widget->SemaphoreListener(); + if (context && context->Interactable() && listener) { + if (auto* button = listener->TheButton()) { button->Press(); } } } - - static IL2CppClassHelper& get_class_helper() - { - static auto class_helper = - il2cpp_get_class_helper("Assembly-CSharp", "Digit.Prime.ObjectViewer", "WaveDefenseObjectViewerWidget"); - return class_helper; - } }; diff --git a/mods/src/prime/Widget.h b/mods/src/prime/Widget.h index a25fd862c..24768ea59 100644 --- a/mods/src/prime/Widget.h +++ b/mods/src/prime/Widget.h @@ -1,42 +1,15 @@ #pragma once -#include - -template struct Widget { -public: - __declspec(property(get = __get_Context)) T* Context; - __declspec(property(get = __get_enabled)) bool enabled; - __declspec(property(get = __get_isActiveAndEnabled)) bool isActiveAndEnabled; - +#include + +// Digit.Client.UI.Widget`1. Members resolve on each widget's runtime class. +template struct Widget : il2cpp::Object { + T* Context() + { return Get<"Context", T*>(); } + bool enabled() + { return Get<"enabled", bool>(); } + bool isActiveAndEnabled() + { return Get<"isActiveAndEnabled", bool>(); } bool IsActive() - { - auto IsActive = get_class_helper().GetMethod("IsActive"); - return IsActive(this); - } - -private: - static IL2CppClassHelper& get_class_helper() - { - static IL2CppClassHelper class_helper = Y::get_class_helper().GetParent("Widget`1"); - return class_helper; - } - -public: - bool __get_enabled() - { - static auto field = get_class_helper().GetProperty("enabled"); - return field.Get(this); - } - - T* __get_Context() - { - static auto field = get_class_helper().GetProperty("Context"); - return field.GetRaw(this); - } - - bool __get_isActiveAndEnabled() - { - static auto field = get_class_helper().GetProperty("isActiveAndEnabled"); - return *field.Get(this); - } + { return Call<"IsActive", bool>(); } }; diff --git a/mods/src/ship_name_match.h b/mods/src/ship_name_match.h index 1c9d2868a..c02ca65a9 100644 --- a/mods/src/ship_name_match.h +++ b/mods/src/ship_name_match.h @@ -2,10 +2,11 @@ #include "str_utils.h" -#include +#include #include #include +#include #include #include @@ -77,47 +78,12 @@ inline std::string LocalizedShipName(int64_t loca_id) { if (loca_id <= 0) return {}; - struct Resolved { - Il2CppClass* ctx_cls = nullptr; - const MethodInfo* ctor = nullptr; - Il2CppString* (*localise)(void*, bool, int32_t) = nullptr; - }; - - static const Resolved resolved = [] { - Resolved r{}; - - auto ctx_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.UI", "LocaleTextContext"); - if (!ctx_helper.get_cls()) return r; - - auto* ctor = ctx_helper.GetMethodInfo(".ctor", 2); - if (!ctor) return r; - - auto localizer_helper = il2cpp_get_class_helper("Assembly-CSharp", "Digit.Client.Localization", "Localizer"); - auto* localise = localizer_helper.get_cls() - ? localizer_helper.GetMethod("Localise", 3) - : static_cast(nullptr); - if (!localise) return r; - - r.ctx_cls = ctx_helper.get_cls(); - r.ctor = ctor; - r.localise = localise; - return r; - }(); - - if (!resolved.localise) return {}; - auto* id_str = il2cpp_string_new(("ship_name_" + std::to_string(loca_id)).c_str()); auto* cat_str = il2cpp_string_new("ships"); - if (!id_str || !cat_str) return {}; - - void* ctor_args[2] = {id_str, cat_str}; - Il2CppException* exc = nullptr; - auto* ctx = il2cpp_object_new(resolved.ctx_cls); + auto* ctx = il2cpp::New(id_str, cat_str); if (!ctx) return {}; - il2cpp_runtime_invoke(resolved.ctor, ctx, ctor_args, &exc); - if (exc) return {}; - if (const auto* str = resolved.localise(ctx, false, 0); str != nullptr) { + if (const auto* str = Localizer::Localise(ctx); str != nullptr) { return to_string(str); } return {}; @@ -125,7 +91,7 @@ inline std::string LocalizedShipName(int64_t loca_id) inline std::string GameDisplayName(FleetPlayerData* ship) { - return ship && ship->Hull ? LocalizedShipName(ship->GetLocaId()) : std::string{}; + return ship && ship->Hull() ? LocalizedShipName(ship->GetLocaId()) : std::string{}; } inline std::vector DisplayWords(FleetPlayerData* ship) diff --git a/mods/src/str_utils.h b/mods/src/str_utils.h index 2c153891d..774e29d86 100644 --- a/mods/src/str_utils.h +++ b/mods/src/str_utils.h @@ -8,8 +8,8 @@ #include #include +#include #include -#include #if _WIN32 #include diff --git a/scripts/check-il2cpp-dump.py b/scripts/check-il2cpp-dump.py new file mode 100755 index 000000000..8de4c635f --- /dev/null +++ b/scripts/check-il2cpp-dump.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python3 +"""Check the IL2CPP bindings in mods/src against an Il2CppDumper dump.cs. + + check-il2cpp-dump.py check [--old ] + Every binding class, member and hook target used in the source must exist in the dump with + matching static-ness and argument count. With --old, also lists used members whose + declaration changed between the two dumps. + + check-il2cpp-dump.py diff + Full member diff of every class the source binds, to spot drift the bindings do not use yet. + +Produce dump.cs with Il2CppDumper from GameAssembly and global-metadata.dat of the client you +target, and keep the il2cpp.h it writes next to dump.cs: base classes are resolved from it. +This is a static text match: it does not check C++ types the way the runtime does, so +a clean run is necessary but not sufficient. Members reached only through unbound wrappers +(Widget, List, ...) are not checked. +""" +import argparse +import difflib +import os +import re +from collections import defaultdict + +SRC_ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'mods', 'src') + +TYPE_HEAD = re.compile( + r'^(?:\[.*\]\s*)?(?:(?:public|private|protected|internal|static|sealed|abstract|readonly|ref|unsafe|partial)\s+)*' + r'(class|struct|interface|enum)\s+(\S+?)(?:<[^>]*>)?(?:\s*:\s*([^/]+?))?\s*//\s*TypeDefIndex') +FIELD = re.compile(r'^(?:(?:public|private|protected|internal|static|readonly|const|new|volatile)\s+)*(.+?)\s+([\w<>`.$]+);\s*//') +PROPERTY = re.compile( + r'^((?:(?:public|private|protected|internal|static|virtual|override|abstract|sealed|new)\s+)*)(.+?)\s+([\w.<>`]+)' + r'\s*\{\s*(get;)?\s*(set;)?\s*\}') +# Abstract and extern methods end in ';' instead of '{ }'. +METHOD = re.compile( + r'^((?:(?:public|private|protected|internal|static|virtual|override|abstract|sealed|extern|new|async|unsafe)\s+)*)' + r'(.+?)\s+([\w.<>`$|]+?)(<[^(]*>)?\((.*)\)\s*[{;]') + + +def split_params(params): + depth, out, buf = 0, [], '' + for ch in params: + if ch in '<([': + depth += 1 + elif ch in '>)]': + depth -= 1 + if ch == ',' and depth == 0: + out.append(buf) + buf = '' + else: + buf += ch + out.append(buf) + return out + + +class Dump: + """Classes from dump.cs, keyed by (namespace, name). Nested types are named Outer.Inner.""" + + def __init__(self, path): + self.classes = {} + self.by_name = defaultdict(list) + # dump.cs names bases without their namespace. il2cpp.h, written next to it, spells out + # each concrete class's base with namespace (and generic arguments) mangled into one name. + self.bases = {} + header = os.path.join(os.path.dirname(path), 'il2cpp.h') + if os.path.exists(header): + for line in open(header, encoding='utf-8', errors='replace'): + if m := re.match(r'struct (\w+)_Fields : (\w+)_Fields \{', line): + self.bases[m.group(1)] = m.group(2) + namespace, cur = None, None + for line in open(path, encoding='utf-8', errors='replace'): + if line.startswith('// Namespace:'): + namespace = line[len('// Namespace:'):].strip() + continue + m = TYPE_HEAD.match(line) + if m: + raw_base = m.group(3).split(',')[0].strip() if m.group(3) else None + cur = {'ns': namespace, 'name': m.group(2), 'base': raw_base and re.sub(r'<.*', '', raw_base), + 'generic': '<' in line.split('//')[0].split(':')[0], 'generic_base': bool(raw_base and '<' in raw_base), + 'fields': {}, 'props': {}, 'methods': defaultdict(list)} + self.classes.setdefault((namespace, cur['name']), cur) + self.by_name[cur['name']].append(cur) + continue + s = line.strip() + if cur is None or not s or s.startswith(('//', '[')): + continue + decl = s.split('//')[0].strip() + if (fm := FIELD.match(s)) and '(' not in decl: + head = decl[:decl.rfind(fm.group(2))] + cur['fields'][fm.group(2)] = {'static': bool(re.search(r'\b(static|const)\b', head)), 'decl': decl} + elif pm := PROPERTY.match(s): + cur['props'][pm.group(3).split('.')[-1]] = {'static': 'static' in pm.group(1), 'decl': s} + elif mm := METHOD.match(s): + name = mm.group(3) if mm.group(3) in ('.ctor', '.cctor') else mm.group(3).split('.')[-1] + params = mm.group(5).strip() + cur['methods'][name].append({'static': 'static' in mm.group(1), 'generic': bool(mm.group(4)), + 'arity': len(split_params(params)) if params else 0, + 'decl': s.rstrip(' {};')}) + + def find(self, namespace, name): + name = name.replace('/', '.') + if (namespace, name) in self.classes: + return self.classes[(namespace, name)] + # Nested types are listed under their own (often empty) namespace. + if '.' in name and self.by_name.get(name): + return self.by_name[name][0] + return None + + @staticmethod + def mangle(c): + return re.sub(r'\W', '_', f"{c['ns']}.{c['name']}" if c['ns'] else c['name']) + + def base_of(self, c): + if not c['base']: + return None + candidates = self.by_name.get(c['base'].split('.')[-1], []) + self.by_name.get(c['base'], []) + # ViewController and ViewController share a name; the base's own generic-ness picks one. + candidates = [b for b in candidates if b['generic'] == c['generic_base']] or candidates + if mangled := self.bases.get(self.mangle(c)): + exact = [b for b in candidates + if mangled == self.mangle(b) or mangled.startswith(self.mangle(b) + '_')] + if exact: + return max(exact, key=lambda b: len(self.mangle(b))) + # Open generic classes have no il2cpp.h entry; fall back to the same namespace. + same = [b for b in candidates if b['ns'] == c['ns']] + return (same or candidates or [None])[0] + + def lookup(self, cls, kind, member): + table = {'field': 'fields', 'prop': 'props', 'method': 'methods'}[kind] + for _ in range(64): + if cls is None: + return None + if member in cls[table]: + return cls[table][member] + cls = self.base_of(cls) + return None + + +CLASS = r'il2cpp::Class<\s*"([^"]*)"\s*,\s*"([^"]*)"\s*,\s*"([^"]*)"\s*>' +USE = re.compile(r'(?(') + if close == -1: + return None + i, depth, args = close + 2, 1, '' + while i < len(text) and depth: + depth += {'(': 1, ')': -1}.get(text[i], 0) + if depth: + args += text[i] + i += 1 + return len(split_params(args)) if args.strip() else 0 + + +def scan_source(root): + """Returns ({binding name: (asm, ns, class)}, [(file, owner, kind, member, static, arity)]).""" + classes, uses = {}, [] + for dirpath, _, files in os.walk(root): + if os.sep + 'proto' in dirpath: + continue + for f in files: + if not f.endswith(('.h', '.cc', '.mm')): + continue + path = os.path.join(dirpath, f) + text = open(path, encoding='utf-8', errors='replace').read() + for m in re.finditer(r'using\s+(\w+)\s*=\s*' + CLASS, text): + classes.setdefault(m.group(1), m.group(2, 3, 4)) + for m in re.finditer(r'^\s*struct\s+(\w+)[^;{]*\{', text, re.M): + i, depth = m.end(), 1 + while i < len(text) and depth: + depth += {'{': 1, '}': -1}.get(text[i], 0) + i += 1 + # Nested structs name their own Class; keep their members out of the outer binding. + body = re.sub(r'struct\s+\w+[^;{]*\{(?:[^{}]|\{[^{}]*\})*\};', '', text[m.end():i]) + cm = re.search(r'using\s+Class\s*=\s*' + CLASS, body) + if not cm: + continue + classes.setdefault(m.group(1), cm.group(1, 2, 3)) + for u in USE.finditer(body): + kind = KIND[u.group(2)] + arity = call_arity(body[u.end():]) if kind == 'method' else None + uses.append((path, m.group(1), kind, u.group(3), bool(u.group(1)), arity)) + for h in HOOK.finditer(body): + if h.group(1) == 'Class': + uses.append((path, m.group(1), 'hook', h.group(2), None, h.group(3))) + for h in HOOK.finditer(text): + if h.group(1) != 'Class': + uses.append((path, h.group(1), 'hook', h.group(2), None, h.group(3))) + return classes, uses + + +def check(args): + dump = Dump(args.dump) + old = Dump(args.old) if args.old else None + bound, uses = scan_source(SRC_ROOT) + problems, changes = set(), set() + for path, owner, kind, member, static, arity in uses: + if owner not in bound: + continue + asm, ns, name = bound[owner] + where = f"{os.path.relpath(path, SRC_ROOT)}: {owner}" + cls = dump.find(ns, name) + if not cls: + problems.add(f"{where}: class {ns}.{name} ({asm}) not found") + continue + info = dump.lookup(cls, 'method' if kind == 'hook' else kind, member) + if info is None: + problems.add(f"{where}: {kind} {member} not found on {ns}.{name} or its bases") + continue + if kind == 'hook': + if arity and arity.isdigit() and not any(m['arity'] == int(arity) for m in info): + problems.add(f"{where}: hook {member} has no {arity}-argument overload: " + + ' | '.join(m['decl'] for m in info)) + continue + if kind == 'method': + candidates = [m for m in info if not m['generic'] and (arity is None or m['arity'] == arity)] + if not candidates: + problems.add(f"{where}: Call<\"{member}\"> with {arity} arguments matches nothing: " + + ' | '.join(m['decl'] for m in info)) + continue + if not any(m['static'] == static for m in candidates): + problems.add(f"{where}: Call<\"{member}\"> static={static} does not match: " + + ' | '.join(m['decl'] for m in candidates)) + decls = sorted(m['decl'] for m in candidates) + else: + if info['static'] != static: + problems.add(f"{where}: {kind} {member} static={static} does not match: {info['decl']}") + decls = [info['decl']] + if old and (old_cls := old.find(ns, name)): + old_info = old.lookup(old_cls, kind, member) + if old_info is None: + continue + if kind == 'method': + old_decls = sorted(m['decl'] for m in old_info if not m['generic'] and (arity is None or m['arity'] == arity)) + else: + old_decls = [old_info['decl']] + if old_decls != decls: + changes.add(f"{where}: {ns}.{name}.{member}: {' | '.join(old_decls)} -> {' | '.join(decls)}") + + print(f"{len(bound)} bindings, {len(uses)} member uses") + print(f"\n== problems ({len(problems)})") + print('\n'.join(sorted(problems))) + if old: + print(f"\n== used declarations changed since the old dump ({len(changes)})") + print('\n'.join(sorted(changes))) + + +def class_blocks(path): + """Member lines per (namespace, class), without RVA/offset comments that change every build.""" + out, namespace, cur, buf = {}, None, None, [] + for line in open(path, encoding='utf-8', errors='replace'): + if line.startswith('// Namespace:'): + namespace = line[len('// Namespace:'):].strip() + continue + m = TYPE_HEAD.match(line) + if m: + if cur: + out.setdefault(cur, buf) + cur, buf = (namespace, m.group(2)), [] + continue + s = line.strip() + if cur and s and not s.startswith(('//', '/*', '|', '*/', '[')): + buf.append(re.sub(r'\s*//.*', '', s)) + if cur: + out.setdefault(cur, buf) + return out + + +def diff(args): + old, new = class_blocks(args.old), class_blocks(args.new) + bound, _ = scan_source(SRC_ROOT) + changed = 0 + for ns, name in sorted({(ns, name.replace('/', '.')) for _, ns, name in bound.values()}): + key = (ns, name) + if key not in old and key not in new: + key = next((k for k in {**old, **new} if k[1] == name), key) + before, after = old.get(key), new.get(key) + if before is None or after is None: + print(f"!! {ns}.{name}: in old dump={before is not None}, in new dump={after is not None}") + continue + if before != after: + changed += 1 + print(f"== {ns}.{name}") + for line in difflib.unified_diff(before, after, lineterm='', n=0): + if not line.startswith(('---', '+++', '@@')): + print(' ' + line) + print(f"{len(bound)} bindings, {changed} bound classes changed") + + +def main(): + parser = argparse.ArgumentParser(description=__doc__.split('\n')[0]) + sub = parser.add_subparsers(required=True) + p = sub.add_parser('check', help='check bindings against a dump') + p.add_argument('dump') + p.add_argument('--old', help='previous dump.cs, to list used declarations that changed') + p.set_defaults(run=check) + p = sub.add_parser('diff', help='diff bound classes between two dumps') + p.add_argument('old') + p.add_argument('new') + p.set_defaults(run=diff) + args = parser.parse_args() + args.run(args) + + +if __name__ == '__main__': + main() diff --git a/tests/alliance_store_runtime.cc b/tests/alliance_store_runtime.cc deleted file mode 100644 index 6cfab0ce7..000000000 --- a/tests/alliance_store_runtime.cc +++ /dev/null @@ -1,528 +0,0 @@ -// Exercise the production Alliance Games adapter against a controlled IL2CPP boundary. -// This fixture makes no Unity/rendering or native-detour claim. -#include -#if _WIN32 -#undef IL2CPP_IMPORT -#define IL2CPP_IMPORT -#endif -#include "patches/alliance_store_bindings.h" -#include -#include -#include -#include -#include - -namespace -{ -using namespace alliance_store_game; - -void Require(bool condition, const char* message) -{ - if (!condition) - throw std::runtime_error(message); -} - -Il2CppClass reference_class{}, child_class{}, foreign_class{}, boolean_class{}, int32_class{}, int64_class{}; -Il2CppClass void_class{}, vector_class{}, foreign_vector_class{}, anchor_class{}; -struct Instance { - Object object{}; - alignas(8) unsigned char storage[32]{}; -} receiver; -struct Box { - Object object{}; - alignas(8) unsigned char data[16]{}; -} box; -Object argument{}, foreign_object{}; -MethodInfo method{}, override_method{}; -const MethodInfo* methods[2]{}; -const Il2CppType* parameters[3]{}; -FieldInfo field{}; - -Object* returned = nullptr; -bool managed_exception = false, null_unbox = false; -int invoke_calls = 0, virtual_calls = 0, field_reads = 0, field_writes = 0; -const MethodInfo* seen_method = nullptr; -void* seen_target = nullptr; -std::array seen_arguments{}; -int64_t seen_int64 = 0; -bool seen_boolean = false; -Vector2 seen_vector{}; -void* seen_field_argument = nullptr; -bool drop_field_write = false, use_override = false; - -struct HandleSlot { - Object* target = nullptr; - bool live = false, weak = false; -}; -std::array handles{}; -HandleSlot* last_handle = nullptr; -bool fail_handle = false, mismatch_handle = false; -int handle_creations = 0, handle_frees = 0, weak_creations = 0; - -Il2CppGCHandle NewHandle(Object* value, bool weak) -{ - if (fail_handle) - return nullptr; - for (auto& handle : handles) { - if (handle.live) - continue; - handle = {mismatch_handle ? &foreign_object : value, true, weak}; - last_handle = &handle; - ++handle_creations; - weak_creations += weak; - return &handle; - } - throw std::runtime_error("fixture handle capacity exhausted"); -} - -void InitClass(Il2CppClass& cls, const char* space, const char* name, Il2CppTypeEnum type, bool value_type = false, - uint32_t size = 0) -{ - cls.namespaze = space; - cls.name = name; - cls.byval_arg.type = type; - cls.byval_arg.valuetype = value_type; - cls.byval_arg.data.dummy = &cls; - cls.element_size = size; - cls.instance_size = sizeof(Instance); -} - -void InitMethod(const char* name, Il2CppClass& result, std::initializer_list args = {}) -{ - method = {}; - method.name = name; - method.klass = &reference_class; - method.return_type = &result.byval_arg; - method.parameters_count = static_cast(args.size()); - size_t index = 0; - for (auto* type : args) - parameters[index++] = type; - method.parameters = parameters; - // The adapter checks this invoker; the mock runtime owns invocation itself. - method.invoker_method = reinterpret_cast(1); - methods[0] = &method; - reference_class.methods = methods; - reference_class.method_count = 1; - receiver.object.klass = &reference_class; - returned = nullptr; - managed_exception = use_override = null_unbox = false; - seen_arguments = {}; -} - -template void ReturnBox(Il2CppClass& cls, T value) -{ - static_assert(sizeof(T) <= sizeof(box.data)); - box.object.klass = &cls; - std::memcpy(box.data, &value, sizeof(value)); - returned = &box.object; -} - -void InitField(Il2CppClass& type) -{ - field = {}; - field.name = "value"; - field.type = &type.byval_arg; - field.parent = &reference_class; - field.offset = offsetof(Instance, storage); - reference_class.fields = &field; - reference_class.field_count = 1; - drop_field_write = false; - receiver.object.klass = &reference_class; - std::memset(receiver.storage, 0, sizeof(receiver.storage)); -} - -size_t FieldSize(FieldInfo* value) -{ - auto* cls = static_cast(value->type->data.dummy); - return cls->byval_arg.valuetype ? cls->element_size : sizeof(Object*); -} -} // namespace - -// Windows imports API functions; macOS resolves API function pointers. -#if _WIN32 -#define API(ret, name, params) extern "C" ret name params -#define END_API -#else -#define API(ret, name, params) name##_t name = +[] params->ret -#define END_API ; -#endif -API(Il2CppClass*, il2cpp_class_from_type, (const Il2CppType* type)) -{ return type ? static_cast(type->data.dummy) : nullptr; } -END_API -API(const Il2CppType*, il2cpp_class_get_type, (Il2CppClass * cls)) -{ return cls ? &cls->byval_arg : nullptr; } -END_API -API(bool, il2cpp_class_is_valuetype, (const Il2CppClass* cls)) -{ return cls && cls->byval_arg.valuetype; } -END_API -API(bool, il2cpp_class_is_enum, (const Il2CppClass* cls)) -{ return cls && cls->enumtype; } -END_API -API(const Il2CppType*, il2cpp_class_enum_basetype, (Il2CppClass * cls)) -{ return cls == &anchor_class ? &int32_class.byval_arg : nullptr; } -END_API -API(int32_t, il2cpp_class_value_size, (Il2CppClass * cls, uint32_t* alignment)) -{ - *alignment = 4; - return static_cast(cls->element_size); -} -END_API -API(int32_t, il2cpp_class_instance_size, (Il2CppClass * cls)) -{ return static_cast(cls->instance_size); } -END_API -API(Il2CppClass*, il2cpp_class_get_parent, (Il2CppClass * cls)) -{ return cls ? cls->parent : nullptr; } -END_API -API(bool, il2cpp_class_is_assignable_from, (Il2CppClass * expected, Il2CppClass* actual)) -{ - for (; actual; actual = actual->parent) - if (actual == expected) - return true; - return false; -} -END_API -API(const MethodInfo*, il2cpp_class_get_methods, (Il2CppClass * cls, void** iterator)) -{ - const auto index = reinterpret_cast(*iterator); - if (index >= cls->method_count) - return nullptr; - *iterator = reinterpret_cast(index + 1); - return cls->methods[index]; -} -END_API -API(const MethodInfo*, il2cpp_object_get_virtual_method, (Object*, const MethodInfo* selected)) -{ - ++virtual_calls; - return use_override && selected == &method ? &override_method : selected; -} -END_API -API(char*, il2cpp_type_get_name, (const Il2CppType* type)) -{ - auto* cls = type ? static_cast(type->data.dummy) : nullptr; - if (!cls) - return nullptr; - const auto name = std::string(cls->namespaze) + "." + cls->name; - auto* copy = static_cast(std::malloc(name.size() + 1)); - Require(copy != nullptr, "fixture type-name allocation"); - std::memcpy(copy, name.c_str(), name.size() + 1); - return copy; -} -END_API -API(void, il2cpp_free, (void* value)) -{ std::free(value); } -END_API -API(void*, il2cpp_object_unbox, (Object * object)) -{ return null_unbox ? nullptr : reinterpret_cast(object)->data; } -END_API -API(Object*, il2cpp_runtime_invoke, (const MethodInfo* selected, void* target, void** args, Il2CppException** error)) -{ - ++invoke_calls; - seen_method = selected; - seen_target = target; - seen_arguments = {}; - for (size_t i = 0; args && i < selected->parameters_count && i < seen_arguments.size(); ++i) { - seen_arguments[i] = args[i]; - if (selected->parameters[i] == &int64_class.byval_arg) - std::memcpy(&seen_int64, args[i], sizeof(seen_int64)); - else if (selected->parameters[i] == &boolean_class.byval_arg) - std::memcpy(&seen_boolean, args[i], sizeof(seen_boolean)); - else if (selected->parameters[i] == &vector_class.byval_arg) - std::memcpy(&seen_vector, args[i], sizeof(seen_vector)); - } - if (managed_exception) - *error = reinterpret_cast(&foreign_object); - return returned; -} -END_API -API(FieldInfo*, il2cpp_class_get_field_from_name, (Il2CppClass * cls, const char* name)) -{ - for (size_t i = 0; i < cls->field_count; ++i) - if (std::strcmp(cls->fields[i].name, name) == 0) - return &cls->fields[i]; - return nullptr; -} -END_API -API(int, il2cpp_field_get_flags, (FieldInfo * value)) -{ return value->type->attrs; } -END_API -API(void, il2cpp_field_get_value, (Object * object, FieldInfo* value, void* output)) -{ - ++field_reads; - std::memcpy(output, reinterpret_cast(object) + value->offset, FieldSize(value)); -} -END_API -API(void, il2cpp_field_set_value, (Object * object, FieldInfo* value, void* input)) -{ - ++field_writes; - seen_field_argument = input; - if (drop_field_write) - return; - auto* destination = reinterpret_cast(object) + value->offset; - if (static_cast(value->type->data.dummy)->byval_arg.valuetype) - std::memcpy(destination, input, FieldSize(value)); - else - std::memcpy(destination, &input, sizeof(Object*)); // Reference itself, not its address. -} -END_API -API(Il2CppGCHandle, il2cpp_gchandle_new, (Object * object, bool pinned)) -{ - Require(!pinned, "adapter must not pin its short-lived roots"); - return NewHandle(object, false); -} -END_API -API(Il2CppGCHandle, il2cpp_gchandle_new_weakref, (Object * object, bool resurrection)) -{ - Require(!resurrection, "weak Unity identities must not track resurrection"); - return NewHandle(object, true); -} -END_API -API(Object*, il2cpp_gchandle_get_target, (Il2CppGCHandle value)) -{ - auto* handle = static_cast(value); - return handle && handle->live ? handle->target : nullptr; -} -END_API -API(void, il2cpp_gchandle_free, (Il2CppGCHandle value)) -{ - auto* handle = static_cast(value); - Require(handle && handle->live, "handle freed twice"); - handle->live = false; - ++handle_frees; -} -END_API - -namespace -{ -void CallCases() -{ - InitMethod("get_Reference", reference_class); - const auto null_reference = Call(&receiver.object, "get_Reference"); - Require(null_reference && !null_reference.value, "successful null reference is known"); - const auto before_missing = invoke_calls; - Require(!Call(&receiver.object, "missing") && invoke_calls == before_missing, - "missing method must not invoke"); - managed_exception = true; - Require(!Call(&receiver.object, "get_Reference"), "managed exception is unknown, not known null"); - managed_exception = false; - returned = &foreign_object; - Require(!Call(&receiver.object, "get_Reference"), "foreign reference result must be rejected"); - - InitMethod("get_Id", int64_class); - ReturnBox(int64_class, int64_t{0}); - const auto zero = Call(&receiver.object, "get_Id"); - Require(zero && zero.value == 0, "valid Int64 zero must remain known"); - ReturnBox(int64_class, int64_t{0x100000007}); - Require(Call(&receiver.object, "get_Id").value == 0x100000007, "Int64 must preserve high bits"); - method.return_type = &int32_class.byval_arg; - const auto before_width = invoke_calls; - Require(!Call(&receiver.object, "get_Id") && invoke_calls == before_width, - "Int32 metadata is not an Int64 getter"); - method.return_type = &int64_class.byval_arg; - ReturnBox(int32_class, int32_t{0}); - Require(!Call(&receiver.object, "get_Id"), "Int32 box is not known Int64 zero"); - returned = nullptr; - Require(!Call(&receiver.object, "get_Id"), "null scalar result is unknown"); - ReturnBox(int64_class, int64_t{0}); - fail_handle = true; - Require(!Call(&receiver.object, "get_Id"), "failed box custody is unknown, not zero"); - fail_handle = false; - null_unbox = true; - Require(!Call(&receiver.object, "get_Id"), "missing unbox data is unknown"); - - InitMethod("get_Eligible", boolean_class); - ReturnBox(boolean_class, false); - const auto known_false = Call(&receiver.object, "get_Eligible"); - Require(known_false && !known_false.value, "valid false must remain known"); - managed_exception = true; - Require(!Call(&receiver.object, "get_Eligible"), "failed eligibility is unknown, not false"); - - InitMethod("get_NormalizedPosition", vector_class); - ReturnBox(vector_class, Vector2{0.25f, 0.75f}); - const auto position = NormalizedPosition(&receiver.object); - Require(position && position.value.x == 0.25f && position.value.y == 0.75f, "exact Vector2 return"); - ReturnBox(foreign_vector_class, Vector2{0.25f, 0.75f}); - Require(!NormalizedPosition(&receiver.object), "same-sized foreign value is not Vector2"); - method.return_type = &foreign_vector_class.byval_arg; - const auto before_foreign = invoke_calls; - Require(!NormalizedPosition(&receiver.object) && invoke_calls == before_foreign, "foreign Vector2 signature"); -} - -void SignatureAndArgumentsCases() -{ - InitMethod("Apply", void_class, {&int64_class.byval_arg, &reference_class.byval_arg, &boolean_class.byval_arg}); - int64_t id = 0x100000009; - bool enabled = false; - Require(Call(&receiver.object, "Apply", - {Scalar(id, "System.Int64"), Ref(&argument), Scalar(enabled, "System.Boolean")}) - .succeeded, - "void success with scalar and reference arguments"); - Require(seen_target == &receiver.object && seen_arguments[0] == &id && seen_int64 == id - && seen_arguments[1] == &argument && seen_arguments[2] == &enabled && !seen_boolean, - "scalar addresses and direct references must retain their distinct ABI"); - const auto before_invalid = invoke_calls; - Require(!Call(&receiver.object, "Apply", - {Scalar(id, "System.Int32"), Ref(&argument), Scalar(enabled, "System.Boolean")}), - "wrong scalar signature"); - Require(!Call(&receiver.object, "Apply", - {Scalar(id, "System.Int64"), Ref(&foreign_object), Scalar(enabled, "System.Boolean")}), - "foreign reference argument"); - Require(invoke_calls == before_invalid, "invalid argument metadata must not invoke"); - Require(Call(&receiver.object, "Apply", - {Scalar(id, "System.Int64"), Ref(nullptr), Scalar(enabled, "System.Boolean")}) - .succeeded, - "null reference argument is valid"); - - InitMethod("get_Reference", reference_class); - Require(Signature(&method, false, {}), "valid instance signature"); - method.is_generic = true; - Require(!Signature(&method, false, {}), "open generic method must be rejected"); - method.is_generic = false; - method.is_inflated = true; - Require(Call(&receiver.object, "get_Reference").succeeded, "closed accessor metadata remains usable"); - method.invoker_method = nullptr; - Require(!Signature(&method, false, {}), "missing invoker must be rejected"); - method.invoker_method = reinterpret_cast(1); - MethodInfo duplicate = method; - methods[1] = &duplicate; - reference_class.method_count = 2; - const auto before_ambiguous = invoke_calls; - Require(!Call(&receiver.object, "get_Reference") && invoke_calls == before_ambiguous, - "ambiguous compatible overloads must not invoke"); - reference_class.method_count = 1; - method.flags = METHOD_ATTRIBUTE_STATIC; - Require(Call(nullptr, "get_Reference", {}, &reference_class).succeeded && !seen_target, - "static null reference success"); - Require(!Call(&receiver.object, "get_Reference"), "static method is not an instance callback"); - - InitMethod("OnValueChangedCallback", void_class, {&vector_class.byval_arg}); - receiver.object.klass = &child_class; // Method is inherited from the base scroller. - override_method = method; - override_method.klass = &child_class; - use_override = true; - const auto before_virtual = virtual_calls; - Require(NotifyPosition(&receiver.object, {0.125f, 0.875f}), "virtual position notification success"); - Require(virtual_calls == before_virtual + 1 && seen_method == &override_method && seen_target == &receiver.object - && seen_vector.x == 0.125f && seen_vector.y == 0.875f, - "notification must use the selected override and copied Vector2 argument"); - override_method.return_type = &boolean_class.byval_arg; - const auto before_bad_override = invoke_calls; - Require(!NotifyPosition(&receiver.object, {}) && invoke_calls == before_bad_override, - "virtual override signature must be requalified"); -} - -void FieldCases() -{ - InitField(boolean_class); - Field flag; - Require(flag.Resolve(&child_class, "value"), "inherited boolean field binding"); - Require(flag.Write(&receiver.object, true) && flag.Write(&receiver.object, false) && !flag.Read(&receiver.object), - "boolean field preserves false and verifies writeback"); - drop_field_write = true; - Require(!flag.Write(&receiver.object, true), "failed scalar writeback must be visible"); - drop_field_write = false; - const auto before_foreign = field_writes; - Require(!flag.Write(&foreign_object, true) && field_writes == before_foreign, "foreign field owner rejected"); - Require(!flag.Resolve(&reference_class, "missing"), "rebinding missing field must clear prior binding"); - - InitField(anchor_class); - Field anchor; - Require(anchor.Resolve(&reference_class, "value", "Digit.Client.Tooltip.TooltipAnchor"), "Int32 enum field"); - Require(anchor.Write(&receiver.object, -1) && anchor.Read(&receiver.object) == -1, "Undefined anchor is valid"); - Require(anchor.Write(&receiver.object, 0) && anchor.Read(&receiver.object) == 0, "zero anchor is valid"); - - InitField(reference_class); - Field reference; - Require(reference.Resolve(&reference_class, "value", "Tests.Reference"), "reference field binding"); - Require(reference.Write(&receiver.object, &argument) && seen_field_argument == &argument - && reference.Read(&receiver.object) == &argument, - "reference write takes the object itself"); - const auto before_bad_reference = field_writes; - Require(!reference.Write(&receiver.object, &foreign_object) && field_writes == before_bad_reference, - "foreign reference field value rejected"); - Require(reference.Write(&receiver.object, nullptr) && !reference.Read(&receiver.object), "null reference write"); - - InitField(vector_class); - Field vector; - Require(vector.Resolve(&reference_class, "value") && vector.Write(&receiver.object, {0.5f, 0.75f}), - "Vector2 field uses value storage"); - const auto observed = vector.Read(&receiver.object); - Require(observed.x == 0.5f && observed.y == 0.75f, "Vector2 field readback"); - InitField(foreign_vector_class); - Require(!vector.Resolve(&reference_class, "value"), "same-sized foreign field is not Vector2"); - InitField(boolean_class); - boolean_class.byval_arg.attrs = FIELD_ATTRIBUTE_STATIC; - Require(!flag.Resolve(&reference_class, "value"), "static field rejected"); - boolean_class.byval_arg.attrs = 0; - field.offset = sizeof(Instance); - Require(!flag.Resolve(&reference_class, "value"), "field beyond object extent rejected"); - field.offset = 0; - Require(!flag.Resolve(&reference_class, "value"), "field overlapping object header rejected"); -} - -void HandleCases() -{ - const auto before = handle_frees; - { - auto first = Handle::Strong(&argument); - Require(first && first.Get() == &argument, "strong handle custody"); - auto moved = std::move(first); - Require(!first && moved.Get() == &argument, "move transfers handle custody"); - auto replacement = Handle::Strong(&receiver.object); - replacement = std::move(moved); - Require(!moved && replacement.Get() == &argument && handle_frees == before + 1, - "move assignment releases old custody exactly once"); - replacement.Reset(); - replacement.Reset(); - } - Require(handle_frees == before + 2, "reset/destruction do not double free"); - { - const auto weak_before = weak_creations; - auto weak = Handle::Weak(&argument); - Require(weak && weak_creations == weak_before + 1, "weak Unity identity handle"); - last_handle->target = nullptr; // Simulated collection of a weak target. - Require(weak && !weak.Get(), "collected weak target remains distinct from allocation failure"); - } - fail_handle = true; - Require(!Handle::Strong(&argument), "failed strong allocation"); - { - Roots roots; - Require(!roots.Keep(nullptr) && roots.healthy, "null reference requires no custody"); - Require(!roots.Keep(&argument) && !roots.healthy, "failed root marks operation unhealthy"); - } - fail_handle = false; - mismatch_handle = true; - const auto before_mismatch = handle_frees; - Require(!Handle::Strong(&argument) && handle_frees == before_mismatch + 1, - "wrong handle target releases acquired handle"); - mismatch_handle = false; - { - Roots roots; - Require(roots.Keep(&argument) == &argument && roots.Keep(&receiver.object) == &receiver.object && roots.healthy, - "operation roots keep successful custody"); - } - Require(handle_creations == handle_frees, "all acquired handles must be released"); -} -} // namespace - -int main() -{ - InitClass(reference_class, "Tests", "Reference", IL2CPP_TYPE_CLASS); - InitClass(child_class, "Tests", "Child", IL2CPP_TYPE_CLASS); - child_class.parent = &reference_class; - InitClass(foreign_class, "Tests", "Foreign", IL2CPP_TYPE_CLASS); - InitClass(boolean_class, "System", "Boolean", IL2CPP_TYPE_BOOLEAN, true, sizeof(bool)); - InitClass(int32_class, "System", "Int32", IL2CPP_TYPE_I4, true, sizeof(int32_t)); - InitClass(int64_class, "System", "Int64", IL2CPP_TYPE_I8, true, sizeof(int64_t)); - InitClass(void_class, "System", "Void", IL2CPP_TYPE_VOID, true); - InitClass(vector_class, "UnityEngine", "Vector2", IL2CPP_TYPE_VALUETYPE, true, sizeof(Vector2)); - InitClass(foreign_vector_class, "Tests", "ForeignVector", IL2CPP_TYPE_VALUETYPE, true, sizeof(Vector2)); - InitClass(anchor_class, "Digit.Client.Tooltip", "TooltipAnchor", IL2CPP_TYPE_VALUETYPE, true, sizeof(int32_t)); - anchor_class.enumtype = true; - argument.klass = &child_class; - foreign_object.klass = &foreign_class; - CallCases(); - SignatureAndArgumentsCases(); - FieldCases(); - HandleCases(); - std::cout << "Alliance Games runtime adapter regressions passed\n"; -} diff --git a/tests/il2cpp_bindings.cc b/tests/il2cpp_bindings.cc index 1fc383e4d..55c1b7f96 100644 --- a/tests/il2cpp_bindings.cc +++ b/tests/il2cpp_bindings.cc @@ -28,23 +28,23 @@ struct FakeClass { bool is_enum = false; }; -std::deque classes; -std::deque methods; -std::deque> parameter_lists; -std::deque field_types; +std::deque classes; +std::deque methods; +std::deque> parameter_lists; +std::deque field_types; std::unordered_map registry; -std::vector errors; -std::vector initialized; -int method_lookups = 0; +std::vector errors; +std::vector initialized; +int method_lookups = 0; FakeClass& Define(const char* namespaze, const char* name, Il2CppTypeEnum type, FakeClass* parent = nullptr, int32_t value_size = 0) { - auto& fake = classes.emplace_back(); - fake.klass.name = name; - fake.klass.namespaze = namespaze; - fake.klass.parent = parent ? &parent->klass : nullptr; - fake.klass.byval_arg.type = type; + auto& fake = classes.emplace_back(); + fake.klass.name = name; + fake.klass.namespaze = namespaze; + fake.klass.parent = parent ? &parent->klass : nullptr; + fake.klass.byval_arg.type = type; fake.klass.byval_arg.data.dummy = &fake.klass; fake.klass.byval_arg.valuetype = value_size > 0; fake.byref = fake.klass.byval_arg; @@ -104,28 +104,28 @@ struct Vec3 { }; // Runtime classes. -FakeClass& void_class = Define("System", "Void", IL2CPP_TYPE_VOID); -FakeClass& object_class = Define("System", "Object", IL2CPP_TYPE_OBJECT); -FakeClass& int_class = Define("System", "Int32", IL2CPP_TYPE_I4, nullptr, 4); -FakeClass& bool_class = Define("System", "Boolean", IL2CPP_TYPE_BOOLEAN, nullptr, 1); -FakeClass& float_class = Define("System", "Single", IL2CPP_TYPE_R4, nullptr, 4); -FakeClass& string_class = Define("System", "String", IL2CPP_TYPE_STRING, &object_class); -FakeClass& type_class = Define("System", "Type", IL2CPP_TYPE_CLASS, &object_class); -FakeClass& vec3_class = Define("UnityEngine", "Vector3", IL2CPP_TYPE_VALUETYPE, nullptr, sizeof(Vec3)); -FakeClass& state_class = Define("Game", "State", IL2CPP_TYPE_VALUETYPE, nullptr, 4); -FakeClass& base_class = Define("Game", "Base", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& void_class = Define("System", "Void", IL2CPP_TYPE_VOID); +FakeClass& object_class = Define("System", "Object", IL2CPP_TYPE_OBJECT); +FakeClass& int_class = Define("System", "Int32", IL2CPP_TYPE_I4, nullptr, 4); +FakeClass& bool_class = Define("System", "Boolean", IL2CPP_TYPE_BOOLEAN, nullptr, 1); +FakeClass& float_class = Define("System", "Single", IL2CPP_TYPE_R4, nullptr, 4); +FakeClass& string_class = Define("System", "String", IL2CPP_TYPE_STRING, &object_class); +FakeClass& type_class = Define("System", "Type", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& vec3_class = Define("UnityEngine", "Vector3", IL2CPP_TYPE_VALUETYPE, nullptr, sizeof(Vec3)); +FakeClass& state_class = Define("Game", "State", IL2CPP_TYPE_VALUETYPE, nullptr, 4); +FakeClass& base_class = Define("Game", "Base", IL2CPP_TYPE_CLASS, &object_class); FakeClass& derived_class = Define("Game", "Derived", IL2CPP_TYPE_CLASS, &base_class); -FakeClass& other_class = Define("Game", "Other", IL2CPP_TYPE_CLASS, &object_class); -FakeClass& outer_class = Define("Game", "Outer", IL2CPP_TYPE_CLASS, &object_class); -FakeClass& inner_class = Define("", "Inner", IL2CPP_TYPE_CLASS, &object_class); -FakeClass& list_a_class = Define("Game", "ListA", IL2CPP_TYPE_CLASS, &object_class); -FakeClass& list_b_class = Define("Game", "ListB", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& other_class = Define("Game", "Other", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& outer_class = Define("Game", "Outer", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& inner_class = Define("", "Inner", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& list_a_class = Define("Game", "ListA", IL2CPP_TYPE_CLASS, &object_class); +FakeClass& list_b_class = Define("Game", "ListB", IL2CPP_TYPE_CLASS, &object_class); FakeClass& delegate_base = Define("System", "Delegate", IL2CPP_TYPE_CLASS, &object_class); -FakeClass& action_class = Define("System", "Action", IL2CPP_TYPE_CLASS, &delegate_base); -FakeClass& intptr_class = Define("System", "IntPtr", IL2CPP_TYPE_I, nullptr, sizeof(void*)); +FakeClass& action_class = Define("System", "Action", IL2CPP_TYPE_CLASS, &delegate_base); +FakeClass& intptr_class = Define("System", "IntPtr", IL2CPP_TYPE_I, nullptr, sizeof(void*)); -TestObject base_object{}, derived_object{}, other_object{}, list_a{}, list_b{}, allocated{}; -Il2CppObject string_object{}, type_object{}; +TestObject base_object{}, derived_object{}, other_object{}, list_a{}, list_b{}, allocated{}; +Il2CppObject string_object{}, type_object{}; Il2CppDelegate delegate_object{}; // Generated code stand-ins. Each records what the binding passed. @@ -201,24 +201,22 @@ const MethodInfo* delegate_target = nullptr; void DelegateInvoker(Il2CppMethodPointer, const MethodInfo*, void* self, void** args, void*) { // Delegate(object target, IntPtr method): the second argument points at the MethodInfo address. - auto* delegate = static_cast(self); - delegate_target = *static_cast(args[1]); + auto* delegate = static_cast(self); + delegate_target = *static_cast(args[1]); delegate->method = delegate_target; } -void NativeTarget() -{ -} +void NativeTarget() {} -MethodInfo* base_describe = nullptr; -MethodInfo* shared_method = nullptr; -int32_t shared_result = 41; +MethodInfo* base_describe = nullptr; +MethodInfo* shared_method = nullptr; +int32_t shared_result = 41; int boxed_invocations = 0; void BuildRuntime() { auto& get_count = AddMethod(base_class, "get_Count", reinterpret_cast(&Base_get_Count), T(int_class), {}); - auto& set_count = AddMethod(base_class, "set_Count", reinterpret_cast(&Base_set_Count), T(void_class), - {T(int_class)}); + auto& set_count = + AddMethod(base_class, "set_Count", reinterpret_cast(&Base_set_Count), T(void_class), {T(int_class)}); AddProperty(base_class, "Count", &get_count, &set_count); AddMethod(base_class, "Select", reinterpret_cast(&Base_Select_Int), T(void_class), {T(int_class)}); AddMethod(base_class, "Select", reinterpret_cast(&Base_Select_Bool), T(void_class), {T(bool_class)}); @@ -237,8 +235,8 @@ void BuildRuntime() METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_VIRTUAL); AddMethod(base_class, "Twice", reinterpret_cast(&Base_Twice), T(int_class), {T(int_class)}, METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_STATIC); - auto& get_instance = AddMethod(base_class, "get_Instance", reinterpret_cast(&Base_get_Instance), - T(base_class), {}, METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_STATIC); + auto& get_instance = AddMethod(base_class, "get_Instance", reinterpret_cast(&Base_get_Instance), T(base_class), + {}, METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_STATIC); AddProperty(base_class, "Instance", &get_instance); AddMethod(base_class, ".ctor", reinterpret_cast(&Base_ctor), T(void_class), {T(int_class)}); AddMethod(base_class, "Explode", reinterpret_cast(&Base_Explode), T(int_class), {}); @@ -253,7 +251,7 @@ void BuildRuntime() AddMethod(list_b_class, "get_Count", reinterpret_cast(&ListB_get_Count), T(int_class), {}); AddMethod(list_b_class, "get_Item", reinterpret_cast(&List_get_Item), T(object_class), {T(int_class)}); - auto& ctor = AddMethod(action_class, ".ctor", nullptr, T(void_class), {T(object_class), T(intptr_class)}); + auto& ctor = AddMethod(action_class, ".ctor", nullptr, T(void_class), {T(object_class), T(intptr_class)}); ctor.invoker_method = &DelegateInvoker; AddMethod(base_class, "OnEvent", reinterpret_cast(&NativeTarget), T(void_class), {}, METHOD_ATTRIBUTE_PUBLIC | METHOD_ATTRIBUTE_STATIC); @@ -391,6 +389,11 @@ API(int32_t, il2cpp_class_value_size, (Il2CppClass * klass, uint32_t*)) END_API API(bool, il2cpp_class_is_assignable_from, (Il2CppClass * klass, Il2CppClass* other)) { + // The real runtime dereferences both classes. + if (!klass || !other) { + std::fprintf(stderr, "il2cpp_class_is_assignable_from called with a null class\n"); + std::abort(); + } for (; other; other = other->parent) { if (other == klass) return true; @@ -442,7 +445,7 @@ END_API API(void, il2cpp_field_set_value, (Il2CppObject * object, FieldInfo* field, void* value)) { // Like Field::SetValueRaw: value types are copied from the pointer, references are the pointer. - auto size = Fake(static_cast(field->type->data.dummy)).value_size; + auto size = Fake(static_cast(field->type->data.dummy)).value_size; auto* dest = reinterpret_cast(object) + field->offset; if (size) std::memcpy(dest, value, size); @@ -485,6 +488,10 @@ struct Derived : Base { struct Other : il2cpp::Object { using Class = il2cpp::Class<"Game", "Game", "Other">; }; +// A binding whose class is not in the game, as after an update renames it. +struct Gone : il2cpp::Object { + using Class = il2cpp::Class<"Game", "Game", "Gone">; +}; enum class State : int32_t { kIdle = 0, kBusy = 2 }; void Require(bool condition, const char* what) @@ -538,6 +545,14 @@ int main() Il2CppObject* found = nullptr; Require(base->Call<"TryGet", bool>(int32_t{1}, std::ref(found)) && found == &other_object.header, "out argument"); + // A named signature converts the arguments and binds T& parameters to the caller's lvalues. + base->Call<"Select", void(int32_t)>(7); + Require(seen_overload == "int" && seen_int == 7, "signature picks overload"); + found = nullptr; + Require(base->Call<"TryGet", bool(int32_t, Il2CppObject*&)>(1, found) && found == &other_object.header, + "signature out argument"); + Require(Base::Class::Call<"Twice", int32_t(int32_t)>(4) == 8, "static signature call"); + // Virtual calls dispatch on the receiver; non-virtual calls do not. auto* derived = reinterpret_cast(&derived_object); Require(!std::strcmp(base->Describe(), "base"), "base dispatch"); @@ -571,6 +586,8 @@ int main() "property type drift"); Require(base->Field<"_count", Other*>() == nullptr && ErrorsMentioning("field Game.Base._count") == 1, "field type drift"); + Require(base->Field<"_target", Gone*>() == nullptr && ErrorsMentioning("field Game.Base._target") == 1, + "reference to a missing class rejected"); Require(base->Call<"Find", bool>(reinterpret_cast(&other_object)) == false, "unrelated reference rejected"); Require(native_calls == calls, "rejected members do not call native code"); Require(il2cpp::Class<"Game", "Game", "Nope">::Raw() == nullptr && ErrorsMentioning("missing class Game:Game.Nope"), @@ -608,5 +625,14 @@ int main() "hook lookup by parameter types"); Require(!Base::Class::Method("Absent", 0) && ErrorsMentioning("missing method Game.Base.Absent"), "missing hook"); + // TryCall tells a failed call apart from a result that reads as zero. + Require(!base->TryCall<"Explode", int32_t()>() && ErrorsMentioning("Game.Base.Explode threw") == 1, + "tried exception is empty and reported once"); + base->SetCount(0); + const auto zero = base->TryCall<"get_Count", int32_t()>(); + Require(zero && *zero == 0, "tried zero result is known"); + Require(base->TryCall<"Select", void(int32_t)>(5) && seen_int == 5, "tried void call"); + Require(!base->TryCall<"Vanished", int32_t()>(), "tried missing method is empty"); + std::cout << "IL2CPP binding tests passed\n"; } diff --git a/tests/il2cpp_checked.cc b/tests/il2cpp_checked.cc deleted file mode 100644 index 00a8f92eb..000000000 --- a/tests/il2cpp_checked.cc +++ /dev/null @@ -1,134 +0,0 @@ -// Exercise the production checked helpers against controlled IL2CPP metadata. -#include -#if _WIN32 -#undef IL2CPP_IMPORT -#define IL2CPP_IMPORT -#endif -#include "il2cpp/il2cpp_checked.h" -#include - -namespace -{ -Il2CppClass reference_class, boolean_class, other_class; -Il2CppType reference_type{}, boolean_type{}; -Il2CppObject receiver{}, argument{}, result{}; -MethodInfo method{}; -FieldInfo field{}; -const Il2CppType* parameters[] = {&reference_type}; -int invocations = 0; -bool throw_managed = false, boxed_value = false; -void** observed_args = nullptr; -} // namespace - -// Windows imports functions; macOS resolves API function pointers. -#if _WIN32 -#define API(ret, name, params) extern "C" ret name params -#define END_API -#else -#define API(ret, name, params) name##_t name = +[] params->ret -#define END_API ; -#endif -API(const MethodInfo*, il2cpp_class_get_method_from_name, (Il2CppClass*, const char*, int)) -{ return &method; } -END_API -API(FieldInfo*, il2cpp_class_get_field_from_name, (Il2CppClass*, const char*)) -{ return &field; } -END_API -API(Il2CppClass*, il2cpp_class_from_type, (const Il2CppType* type)) -{ return type == &reference_type ? &reference_class : &boolean_class; } -END_API -API(bool, il2cpp_class_is_valuetype, (const Il2CppClass* cls)) -{ return cls == &boolean_class; } -END_API -API(bool, il2cpp_class_is_assignable_from, (Il2CppClass * a, Il2CppClass* b)) -{ return a == b; } -END_API -API(const Il2CppType*, il2cpp_class_get_type, (Il2CppClass * cls)) -{ return cls == &boolean_class ? &boolean_type : &reference_type; } -END_API -API(void*, il2cpp_object_unbox, (Il2CppObject*)) -{ return &boxed_value; } -END_API -API(Il2CppObject*, il2cpp_field_get_value_object, (FieldInfo*, Il2CppObject*)) -{ return &result; } -END_API -API(Il2CppObject*, il2cpp_runtime_invoke, (const MethodInfo*, void*, void** args, Il2CppException** exception)) -{ - ++invocations; - observed_args = args; - if (throw_managed) - *exception = reinterpret_cast(&result); - return &result; -} -END_API - -void Require(bool condition) -{ - if (!condition) - throw std::runtime_error("test failed"); -} -template void Reject(F operation) -{ - try { - operation(); - } catch (const std::runtime_error&) { - return; - } - throw std::runtime_error("expected rejection"); -} -int main() -{ - reference_class.name = "Reference"; - boolean_class.name = "Boolean"; - reference_type.type = IL2CPP_TYPE_CLASS; - boolean_type.type = IL2CPP_TYPE_BOOLEAN; - receiver.klass = argument.klass = &reference_class; - result.klass = &boolean_class; - method.return_type = &boolean_type; - method.parameters = parameters; - method.parameters_count = 1; - method.invoker_method = reinterpret_cast(1); // checked but never called by the mock runtime - void* args[] = {&argument}; - auto invoke = [&] { return Il2CppChecked::Invoke(&receiver, "Method", 1, args); }; - Require(invoke() == &result && invocations == 1 && observed_args == args && args[0] == &argument); - args[0] = nullptr; - Require(invoke() == &result && invocations == 2); // null managed reference is legal - args[0] = &argument; - argument.klass = &other_class; - Reject(invoke); - argument.klass = &reference_class; - reference_type.byref = true; - Reject(invoke); - reference_type.byref = false; - parameters[0] = &boolean_type; - Reject(invoke); - parameters[0] = &reference_type; - method.flags = METHOD_ATTRIBUTE_STATIC; - Reject(invoke); - method.flags = 0; - method.parameters_count = 0; - Reject(invoke); - method.parameters_count = 1; - Require(invocations == 2); // invalid metadata must never reach the managed runtime - throw_managed = true; - Reject(invoke); - throw_managed = false; - boxed_value = false; - Require(!Il2CppChecked::Boolean(&result)); - boxed_value = true; - Require(Il2CppChecked::Boolean(&result)); - result.klass = &reference_class; - Reject([&] { Il2CppChecked::Boolean(&result); }); - field.type = &reference_type; - Require(Il2CppChecked::ReferenceField(&receiver, "Field", "Reference") == &result); - reference_type.attrs = FIELD_ATTRIBUTE_STATIC; - Reject([&] { Il2CppChecked::ReferenceField(&receiver, "Field", "Reference"); }); - reference_type.attrs = 0; - field.type = &boolean_type; - Reject([&] { Il2CppChecked::ReferenceField(&receiver, "Field", "Boolean"); }); - result.klass = &boolean_class; - Require(Il2CppChecked::BooleanField(&receiver, "Flag")); - Require(Il2CppChecked::Invoke(nullptr, "Method") == nullptr); - Require(Il2CppChecked::ReferenceField(nullptr, "Field", "Reference") == nullptr); - std::cout << "Checked IL2CPP invocation/field tests passed\n"; -} diff --git a/tests/il2cpp_class_lookup.cc b/tests/il2cpp_class_lookup.cc deleted file mode 100644 index 0d471ea00..000000000 --- a/tests/il2cpp_class_lookup.cc +++ /dev/null @@ -1,71 +0,0 @@ -// Production helpers with a controlled IL2CPP boundary, on Windows and macOS. -#include -#if _WIN32 -#undef IL2CPP_IMPORT -#define IL2CPP_IMPORT -#endif -#include "il2cpp/il2cpp_helper.h" -#include -#include -#include - -namespace -{ -Il2CppClass klass; -Il2CppObject object{}; -MethodInfo method{}; -int stage = 3; -} // namespace -#if _WIN32 -#define API(ret, name, params) extern "C" ret name params -#define END_API -#else -#define API(ret, name, params) name##_t name = +[] params->ret -#define END_API ; -#endif -API(Il2CppDomain*, il2cpp_domain_get, ()) -{ return stage >= 0 ? reinterpret_cast(&object) : nullptr; } -END_API -API(const Il2CppAssembly*, il2cpp_domain_assembly_open, (Il2CppDomain * domain, const char*)) -{ - if (!domain) - std::abort(); - return stage >= 1 ? reinterpret_cast(&object) : nullptr; -} -END_API -API(const Il2CppImage*, il2cpp_assembly_get_image, (const Il2CppAssembly* assembly)) -{ - if (!assembly) - std::abort(); - return stage >= 2 ? reinterpret_cast(&object) : nullptr; -} -END_API -API(Il2CppClass*, il2cpp_class_from_name, (const Il2CppImage* image, const char*, const char*)) -{ - if (!image) - std::abort(); - return stage >= 3 ? &klass : nullptr; -} -END_API -API(const MethodInfo*, il2cpp_class_get_method_from_name, (Il2CppClass * cls, const char*, int)) -{ - if (!cls) - std::abort(); - return &method; -} -END_API -void Require(bool condition) -{ - if (!condition) - throw std::runtime_error("class lookup regression"); -} -int main() -{ - for (stage = -1; stage < 3; ++stage) - Require(!il2cpp_get_class_helper("Assembly", "Namespace", "Class").get_cls()); - Require(il2cpp_get_class_helper("Assembly", "Namespace", "Class").get_cls() == &klass); - Require(!IL2CppClassHelper(nullptr).GetMethodInfo("Method", 0)); - Require(IL2CppClassHelper(&klass).GetMethodInfo("Method", 0) == &method); - - std::cout << "IL2CPP class lookup regressions passed\n"; -} diff --git a/tests/il2cpp_gc.cc b/tests/il2cpp_gc.cc index 3b8780901..604b3654a 100644 --- a/tests/il2cpp_gc.cc +++ b/tests/il2cpp_gc.cc @@ -74,5 +74,15 @@ int main() Require(root && !root.Get()); } Require(frees == 3 && targets.empty()); + { + // Operation roots keep what they rooted until the set ends; a failed root marks the set. + il2cpp::GcRoots roots; + Require(!roots.Keep(nullptr) && roots.healthy()); + Require(roots.Keep(&object) == &object && roots.Keep(&other) == &other && targets.size() == 2); + fail_allocation = true; + Require(!roots.Keep(&object) && !roots.healthy()); + fail_allocation = false; + } + Require(frees == 5 && targets.empty()); std::cout << "IL2CPP GC ownership tests passed\n"; } diff --git a/tests/il2cpp_runtime.cc b/tests/il2cpp_runtime.cc deleted file mode 100644 index b098a2bbc..000000000 --- a/tests/il2cpp_runtime.cc +++ /dev/null @@ -1,87 +0,0 @@ -// Production helpers with a controlled IL2CPP boundary, on Windows and macOS. -#include -#if _WIN32 -#undef IL2CPP_IMPORT -#define IL2CPP_IMPORT -#endif -#include "il2cpp/runtime.h" -#include -#include -#include - -namespace -{ -Il2CppClass klass; -Il2CppType type{}; -Il2CppObject object{}; -MethodInfo method{}; -int calls = 0; -bool fail = false, boxed = false, null_result = false, null_type = false, null_unbox = false; -void* seen_target = nullptr; -void** seen_args = nullptr; -} // namespace -#if _WIN32 -#define API(ret, name, params) extern "C" ret name params -#define END_API -#else -#define API(ret, name, params) name##_t name = +[] params->ret -#define END_API ; -#endif -API(const Il2CppType*, il2cpp_class_get_type, (Il2CppClass*)) -{ return null_type ? nullptr : &type; } -END_API -API(void*, il2cpp_object_unbox, (Il2CppObject*)) -{ return null_unbox ? nullptr : &boxed; } -END_API -API(Il2CppObject*, il2cpp_runtime_invoke, (const MethodInfo*, void* target, void** args, Il2CppException** error)) -{ - ++calls; - seen_target = target; - seen_args = args; - if (fail) - *error = reinterpret_cast(&object); - return null_result ? nullptr : &object; -} -END_API -void Require(bool condition) -{ - if (!condition) - throw std::runtime_error("runtime helper regression"); -} -int main() -{ - Il2CppObject* result = &object; - Require(!Il2CppRuntime::TryInvoke(nullptr, nullptr, nullptr, &result) && calls == 0 && result == &object); - bool value = true; - void* args[] = {&value, &object, nullptr}; - Require(Il2CppRuntime::TryInvoke(&method, &object, args, &result)); - Require(seen_target == &object && seen_args == args && seen_args[0] == &value && seen_args[1] == &object); - // Static calls, null reference/void returns and exceptions remain distinct. - null_result = true; - Require(Il2CppRuntime::TryInvoke(&method, nullptr, args, &result) && !result && !seen_target); - result = &object; - fail = true; - Require(!Il2CppRuntime::TryInvoke(&method, nullptr, args, &result) && result == &object); - - object.klass = &klass; - type.type = IL2CPP_TYPE_BOOLEAN; - Require(Il2CppRuntime::TryBoolean(&object, value) && !value); - boxed = true; - Require(Il2CppRuntime::TryBoolean(&object, value) && value); - type.type = IL2CPP_TYPE_I4; - Require(!Il2CppRuntime::TryBoolean(&object, value) && value); - Require(!Il2CppRuntime::TryBoolean(nullptr, value)); - type.type = IL2CPP_TYPE_BOOLEAN; - type.byref = true; - Require(!Il2CppRuntime::TryBoolean(&object, value) && value); - type.byref = false; - null_type = true; - Require(!Il2CppRuntime::TryBoolean(&object, value) && value); - null_type = false; - null_unbox = true; - Require(!Il2CppRuntime::TryBoolean(&object, value) && value); - null_unbox = false; - object.klass = nullptr; - Require(!Il2CppRuntime::TryBoolean(&object, value) && value); - std::cout << "IL2CPP runtime helper regressions passed\n"; -} diff --git a/tests/run-shortcut-hint-cache.ps1 b/tests/run-shortcut-hint-cache.ps1 index d3e9f7785..8e9c40bb0 100644 --- a/tests/run-shortcut-hint-cache.ps1 +++ b/tests/run-shortcut-hint-cache.ps1 @@ -1,4 +1,4 @@ -# Run after the workspace's normal AX Windows releasedbg build. +# Run after zig build mods il2cpp -Dtarget=x86_64-windows-msvc -Dmoddbg=true -Dlto=false. # Uses production MapKey/ModifierKey objects from mods.lib; does not launch or modify the game. [CmdletBinding()] param() @@ -7,15 +7,15 @@ $ErrorActionPreference = 'Stop' $repoRoot = Split-Path -Parent $PSScriptRoot Push-Location $repoRoot try { - if (-not (Test-Path -LiteralPath 'build/windows/x64/releasedbg/mods.lib')) { - throw 'Run the normal Windows releasedbg build first.' + if (-not (Test-Path -LiteralPath 'zig-out/lib/mods.lib') -or -not (Test-Path -LiteralPath 'zig-out/lib/il2cpp.lib')) { + throw 'Build the mods and il2cpp targets with -Dlto=false first.' } - # Microsoft link.exe is required for the /GL objects in mods.lib (lld cannot read them). - & clang++ --driver-mode=cl -fuse-ld=link /std:c++latest /EHsc /MT /Imods/src ` - tests/shortcut_hint_cache.cc /Fobuild/shortcut_hint_cache.obj /Febuild/shortcut_hint_cache.exe ` - /link /LTCG /LIBPATH:mods/src/il2cpp build/windows/x64/releasedbg/mods.lib + # Use Microsoft link.exe for the Windows static CRT and import libraries. + & clang++ --driver-mode=cl -fuse-ld=link /std:c++latest /EHsc /MT /Imods/src /Ilibs/il2cpp/include ` + tests/shortcut_hint_cache.cc /Fozig-out/bin/shortcut_hint_cache.obj /Fezig-out/bin/shortcut_hint_cache.exe ` + /link /LTCG /LIBPATH:libs/il2cpp zig-out/lib/mods.lib zig-out/lib/il2cpp.lib if ($LASTEXITCODE -ne 0) { throw 'Shortcut hint test compilation failed.' } - & ./build/shortcut_hint_cache.exe + & ./zig-out/bin/shortcut_hint_cache.exe if ($LASTEXITCODE -ne 0) { throw 'Shortcut hint cache regression failed.' } } finally { Pop-Location