This is the AMP version of this page.
If you want to load the real page instead, click this text.

Help! Loading BNM into Standoff 2

NoPref

Platinian
Hi. In trying to load BNM into the Standoff 2. In the new version devs put some protection. For example: locking System.Reflection and AppDomain in il2cpp_class_from_name, empty methods(il2cpp_image_get_class, il2cpp_image_*, il2cpp_method_get_param_name, and etc.). I'm stuck on il2cpp_domain_get_assemblies. As you can see from ida the method just doesn't make sense


C++:
__int64 __fastcall il2cpp_domain_get_assemblies(__int64 a1, _QWORD *a2)
{
__int64 result; // x0

result = 0LL;
*a2 = 0LL;
return result;
}


But I found the GetAssemblies method in the AppDomain. Its pseudocode from ida:

C++:
System_Reflection_Assembly_array *System_AppDomain__GetAssemblies_66651848(
        System_AppDomain_o *this,
        const MethodInfo *method)
{
  return (System_Reflection_Assembly_array *)sub_200071C(this, 0LL);
}

C++:
__int64 sub_200071C()
{
  __int64 *v0; // x19
  __int64 v1; // x0
  __int64 *v2; // x22
  __int64 v3; // x20
  int v4; // w8
  __int64 v5; // t1
  int v6; // w24
  _QWORD *v7; // x21

  v0 = sub_DE1EC4();
  v1 = il2cpp_array_new_0(qword_45ED790, (v0[1] - *v0) >> 3);
  v2 = (__int64 *)*v0;
  v3 = v1;
  if ( *v0 != v0[1] )
  {
    v4 = 0;
    do
    {
      v5 = *v2++;
      v6 = v4 + 1;
      v7 = (_QWORD *)(v3 + 32 + 8LL * v4);
      *v7 = sub_DE3B70(v5);
      sub_DFBC2C(v7);
      v4 = v6;
    }
    while ( v2 != (__int64 *)v0[1] );
  }
  return v3;
}


For a test, I tried to hook it. My hook looks like this(I added System_Reflection_Assembly_o m_items[0] to struct Il2CppArray):


C++:
BNM::IL2CPP::Il2CppArray* hooked_getassemblies(BNM::IL2CPP::Il2CppAppDomain* domain, const MethodInfo* method) {
auto domains = GetCurrentDomain();

uintptr_t symbol_address = g_il2cppELF.findSymbol("il2cpp_assembly_get_image");
using il2cpp_assembly_get_image_t = Il2CppImage*(*)(Il2CppAssembly*);
il2cpp_assembly_get_image_t il2cpp_assembly_get_image = reinterpret_cast<il2cpp_assembly_get_image_t>(symbol_address);

auto assemblies = original_getassemblies(domains, nullptr);

auto& assembly_items = assemblies->m_Items;  // Referencing the items in the Il2CppArray
auto assembly_count = assemblies->max_length;

for (int i = 0; i < assembly_count; ++i) {
auto& assembly = (Il2CppAssembly*)assembly_items[i];

if (assembly) {
Il2CppImage* image = il2cpp_assembly_get_image(assembly);
if (image) {
KITTY_LOGI("Retrieved image for assembly: %s", image->name);
} else {
KITTY_LOGE("Failed to retrieve image for assembly at index %d", i);
}
}
}

return assemblies;
}


But the logs just don't make sense. It just spams a bunch of “Retrieved image for assembly: .????x”
The count of assemblies is 128


Can I even use this function to retrieve assemblies? If so, I don't understand how I should modify the BNM code to make it work. Would it even be appropriate to use BNM in this game?
 
by the il2cpp_domain_get_assemblies pseudocode its 100% an intentional crash (if you call that) as it dereferences a nullptr, what i suggest you should try is finding the real il2cpp_domain_get_assemblies using either pattern scanning or by manually searching in the code using il2cpp source as a reference
 
C++:
#include <iostream>

// Definição simplificada para Il2CppArray, Il2CppAssembly, e Il2CppImage. Certifique-se de que elas estejam corretas.
namespace BNM {
    namespace IL2CPP {
        struct Il2CppArray {
            void* bounds;  // Bounds do array (se dinâmico)
            size_t max_length;  // Número máximo de elementos
            void* m_Items[0];   // Itens armazenados no array
        };

        struct Il2CppAssembly {};
        struct Il2CppImage {
            const char* name;
        };

        struct Il2CppAppDomain {};
    }
}

// Hook da função `System_AppDomain__GetAssemblies`
BNM::IL2CPP::Il2CppArray* hooked_getassemblies(
    BNM::IL2CPP::Il2CppAppDomain* domain,
    const MethodInfo* method)
{
    // Obtém o domínio atual
    auto domains = GetCurrentDomain();

    // Obtém o endereço do símbolo da função `il2cpp_assembly_get_image`
    uintptr_t symbol_address = g_il2cppELF.findSymbol("il2cpp_assembly_get_image");
    using il2cpp_assembly_get_image_t = BNM::IL2CPP::Il2CppImage* (*)(BNM::IL2CPP::Il2CppAssembly*);
    il2cpp_assembly_get_image_t il2cpp_assembly_get_image =
        reinterpret_cast<il2cpp_assembly_get_image_t>(symbol_address);

    // Chama a função original para obter os assemblies
    auto assemblies = original_getassemblies(domains, nullptr);

    if (!assemblies) {
        KITTY_LOGE("Falha ao obter os assemblies");
        return nullptr;
    }

    // Verifica os elementos do array
    auto& assembly_items = assemblies->m_Items;
    auto assembly_count = assemblies->max_length;

    KITTY_LOGI("Total de assemblies: %zu", assembly_count);

    for (size_t i = 0; i < assembly_count; ++i) {
        auto assembly = reinterpret_cast<BNM::IL2CPP::Il2CppAssembly*>(assembly_items[i]);

        if (assembly) {
            BNM::IL2CPP::Il2CppImage* image = il2cpp_assembly_get_image(assembly);

            if (image && image->name) {
                KITTY_LOGI("Assembly encontrado: %s", image->name);
            } else if (image) {
                KITTY_LOGE("Imagem encontrada, mas nome inválido para o assembly no índice %zu", i);
            } else {
                KITTY_LOGE("Falha ao obter a imagem para o assembly no índice %zu", i);
            }
        } else {
            KITTY_LOGE("Assembly nulo no índice %zu", i);
        }
    }

    return assemblies;
}
 
What’s Going On

What you're dealing with is intentional anti-mod reverse engineering traps in Standoff 2. You're trying to hook into IL2CPP’s GetAssemblies functionality using BNM (Bad Native Mod Menu), but the function you’re targeting (il2cpp_domain_get_assemblies) has been intentionally sabotaged.

That Dummy Function

This code:

__int64 __fastcall il2cpp_domain_get_assemblies(__int64 a1, _QWORD *a2)
{
__int64 result;
result = 0LL;
*a2 = 0LL;
return result;
}

is not a real function. It's a stub or a decoy—it always returns null and is meant to crash or mislead any hookers or reflection tools. The devs set this up on purpose.

The .????x you're seeing in logs is what happens when your hook iterates through null or garbage memory in the returned array. It’s reading random data, not real assembly info.


---

Your Hook Actually Works… but on a Fake

The method you’re calling (System_AppDomain__GetAssemblies) points to sub_200071C, which loops over some structure from sub_DE1EC4().

That structure might not be real assemblies, or it might be filled with dummy/fake pointers, which would explain why il2cpp_assembly_get_image() fails or returns corrupted names.

The fact that it always finds 128 assemblies suggests it’s hardcoded, or a fixed dummy array.


---

How to Fix / What to Do Next

1. Trace Runtime Functions

Use Frida or a similar runtime tracer to hook into functions like:

il2cpp_assembly_get_image

il2cpp_runtime_invoke


These functions are used internally to call IL2CPP methods. Tracing them will let you see what assemblies/methods are actually used during runtime, even if names are stripped.

2. Reverse the Real GetAssemblies

Use Ghidra or IDA on libil2cpp.so and:

Search for any function calling il2cpp_array_new_0 and looping over arrays

Look for patterns where it returns or populates an Il2CppArray containing Il2CppAssembly types


You're looking for a function like GetAssemblies, but probably renamed or restructured to avoid easy detection.

3. Dump the Game

Use Il2CppDumper or Il2CppInspector with:

libil2cpp.so

global-metadata.dat


Once dumped, you can search for actual usable function pointers, even if names are stripped—they’re still there.

4. Hook Lower-Level Functions

If GetAssemblies is blocked or hidden, go deeper:

Hook il2cpp_runtime_invoke to capture all IL2CPP method calls

Or hook il2cpp_class_get_methods, il2cpp_image_get_class, and others directly


This bypasses the need for GetAssemblies entirely.


---

Summary (TL;DR)

The method you’re hooking is fake and returns garbage

The log messages with ????x confirm you're dealing with invalid or obfuscated data

You need to trace or reverse the real methods the game uses

Tools: Frida, Il2CppDumper, Ghidra, IDA, or even runtime hijacking