Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,13 @@ jobs:
shell: pwsh
run: sccache --zero-stats

- name: Test IL2CPP runtime helpers
run: |
xmake build -y il2cpp-runtime-tests
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
xmake run il2cpp-runtime-tests
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }

- name: Build
run: xmake build -y stfc-community-mod

Expand Down Expand Up @@ -469,6 +476,11 @@ jobs:
shell: bash
run: sccache --zero-stats

- name: Test IL2CPP runtime helpers
run: |
xmake build -y il2cpp-runtime-tests
xmake run il2cpp-runtime-tests

- name: Build
# -D keeps the existing XMake compiler-cache diagnostics too.
run: xmake -y -D
Expand Down
40 changes: 40 additions & 0 deletions mods/src/il2cpp/runtime.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#pragma once

#include "il2cpp_helper.h"
#include <il2cpp-tabledefs.h>

// Mechanics shared by optional runtime features. Signature/overload selection,
// argument storage and feature-specific failure policy remain with the caller.
namespace Il2CppRuntime
{
// The method and argument ABI must already be established by the caller.
// IL2CPP takes references directly, but value/byref arguments as addresses.
// A null target is valid for static methods. Success is independent of whether
// the return value is null (including void methods). Outputs change on success only.
inline 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;
}

inline bool TryBoolean(Il2CppObject* boxed, bool& value)
{
if (!boxed || !boxed->klass)
return false;
const auto* type = il2cpp_class_get_type(boxed->klass);
if (!type || type->byref || type->type != IL2CPP_TYPE_BOOLEAN)
return false;
auto* data = static_cast<bool*>(il2cpp_object_unbox(boxed));
if (!data)
return false;
value = *data;
return true;
}
} // namespace Il2CppRuntime
16 changes: 6 additions & 10 deletions mods/src/patches/parts/loading_screen_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
#include "config.h"
#include "errormsg.h"

#include <il2cpp/il2cpp_helper.h>
#include <il2cpp/runtime.h>
#include <il2cpp/il2cpp-functions.h>
#include <spdlog/spdlog.h>

Expand All @@ -28,9 +28,8 @@ struct FakeColor { float r, g, b, a; };
inline Il2CppObject* InvokeRuntime(const MethodInfo* method, void* target, void** args, const char* name)
{
if (!method) return nullptr;
Il2CppException* exception = nullptr;
Il2CppObject* result = il2cpp_runtime_invoke(method, target, args, &exception);
if (exception) {
Il2CppObject* result = nullptr;
if (!Il2CppRuntime::TryInvoke(method, target, args, &result)) {
spdlog::warn("[LS] {} invocation failed", name);
return nullptr;
}
Expand All @@ -40,9 +39,7 @@ inline Il2CppObject* InvokeRuntime(const MethodInfo* method, void* target, void*
inline bool InvokeVoid(const MethodInfo* method, void* target, void** args, const char* name)
{
if (!method) return false;
Il2CppException* exception = nullptr;
il2cpp_runtime_invoke(method, target, args, &exception);
if (exception) {
if (!Il2CppRuntime::TryInvoke(method, target, args)) {
spdlog::warn("[LS] {} invocation failed", name);
return false;
}
Expand All @@ -52,9 +49,8 @@ inline bool InvokeVoid(const MethodInfo* method, void* target, void** args, cons
inline bool InvokeBool(const MethodInfo* method, void* target, void** args, const char* name)
{
Il2CppObject* result = InvokeRuntime(method, target, args, name);
if (!result) return false;
void* value = il2cpp_object_unbox(result);
return value ? *reinterpret_cast<bool*>(value) : false;
bool value = false;
return Il2CppRuntime::TryBoolean(result, value) && value;
}

inline int32_t InvokeInt32(const MethodInfo* method, void* target, int32_t fallback, const char* name)
Expand Down
87 changes: 87 additions & 0 deletions tests/il2cpp_runtime.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Production helpers with a controlled IL2CPP boundary, on Windows and macOS.
#include <il2cpp-config.h>
#if _WIN32
#undef IL2CPP_IMPORT
#define IL2CPP_IMPORT
#endif
#include "il2cpp/runtime.h"
#include <cstdlib>
#include <iostream>
#include <stdexcept>

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<Il2CppException*>(&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";
}
11 changes: 11 additions & 0 deletions xmake.lua
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,14 @@ add_rules("mode.releasedbg")
includes("xmake/rules/protobuf_sccache.lua")
includes("xmake/rules/cxx_sccache.lua")
includes("mods")

target("il2cpp-runtime-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
Loading