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/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/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..13a37c4c2 --- /dev/null +++ b/libs/il2cpp/include/il2cpp/il2cpp.h @@ -0,0 +1,614 @@ +#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 +#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(); + } + + // 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{}; + 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 auto Call(A&&... 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() + { + 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); + } + // 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) + { + 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 + { + 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; +}; + +// 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)); } +} // 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..334a8a519 --- /dev/null +++ b/libs/il2cpp/src/il2cpp.cc @@ -0,0 +1,409 @@ +// 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/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/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/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 new file mode 100644 index 000000000..55c1b7f96 --- /dev/null +++ b/tests/il2cpp_bindings.cc @@ -0,0 +1,638 @@ +// 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)) +{ + // 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; + } + 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">; +}; +// 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) +{ + 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"); + + // 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"); + 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->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"), + "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"); + + // 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 new file mode 100644 index 000000000..604b3654a --- /dev/null +++ b/tests/il2cpp_gc.cc @@ -0,0 +1,88 @@ +#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()); + { + // 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/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/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 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")