sneakyevil / IL2CPP_Resolver

A run-time API resolver for IL2CPP Unity.
https://sneakyevil.gitbook.io/il2cpp-resolver/
The Unlicense
364 stars 67 forks source link

Game crash when calling some methods #30

Closed ImAxel0 closed 1 year ago

ImAxel0 commented 1 year ago

Working on a mod menu for Sons of the Forest, calling some of the game methods it's fine, but others always gives access violation. I'm pretty sure it's all setup correctly and there isn't any nullpointer at the moment of calling.

// Finding the GameObject and storing one of his methods (the method is public and inside the CharacterManager GameObject)
Globals::CharacterManager = Unity::GameObject::Find("CharacterManager");

Globals::Methods::DebugAddCharacter = IL2CPP::Class::Utils::GetMethodPointer("Sons.Characters.CharacterManager", "DebugAddCharacter");

The method has a return of type bool and takes two parameters (string, bool). I tried to call it in these different ways:

// Calling passing Unity::System_string*
Unity::System_String* str = IL2CPP::String::New("cannibal");

Globals::CharacterManager->CallMethod<bool, Unity::System_String*, bool>(Globals::Methods::DebugAddCharacter, str, true);
// Calling passing const char*
Globals::CharacterManager->CallMethod<bool, const char*, bool>(Globals::Methods::DebugAddCharacter, "cannibal", true);
// Calling passing std::string
Globals::CharacterManager->CallMethod<bool, std::string, bool>(Globals::Methods::DebugAddCharacter, "cannibal", true);
// Calling passing wchar_t* (doesn't crash but it doesn't do anything)
Globals::CharacterManager->CallMethod<bool, wchar_t*, bool>(Globals::Methods::DebugAddCharacter, L"cannibal", true);

Both GameObject and method pointer have valid values at the moment of calling, stepping through assembly the exception always occur when a nullptr is dereferenced, like lea register, qword ptr [rax] and rax register is 0 so the access violation occur. Passing nullptr as the string parameter doesn't make the game to crash.

I can't understand why it happens since calling the method using UnityExplorer works fine and the same is for other C# codes I've seen online with the same methods on the same game.

I don't want to ask for help/fix since it could be game related, but if anyone has more knowledge than me any suggestion is highly appreciated.

If this is off-topic close the issue without any doubt and sorry for bothering.

BrownBison commented 1 year ago

Take a look at this it's a similar issue -> https://github.com/sneakyevil/IL2CPP_Resolver/issues/21#issuecomment-1259945858

extremeblackliu commented 1 year ago

you have misunderstanding how it works.

  1. ANY string you see in il2cppdumper(dump.cs) or inside UnityExplorer you see is System_String(not counting to custom string class or other obfuscate string or whatever)
  2. if your code "looks" fine but doesnt do anything, that means your code got excepted already but handled by Unity so you dont crash(only happens when in unity thread)
  3. il2cpp_resolver is modern c++ way, you need treat most things as class, besides static class/function

you first find the gameobject, and then get the method "Sons.Characters.CharacterManager.DebugAddCharacter", i assume the class "Sons.Characters.CharacterManager" is a component that attched to "CharacterManager", so you need use it as like @BrownBison mentioned https://github.com/sneakyevil/IL2CPP_Resolver/issues/21#issuecomment-1259945858

also i highly recommended anyone before trying to hack unity game to get unity editor, and make some simple logic scripts in unity first, you will understanding how gameobject/component works

ImAxel0 commented 1 year ago

Thanks for the quick response. From what I understood and seeing #21 I tried like this:

Globals::CharacterManager = Unity::GameObject::Find("CharacterManager");

Globals::CharacterManager_component = Globals::CharacterManager->GetComponent("Sons.Characters.CharacterManager");

Globals::Methods::DebugAddCharacter = IL2CPP::Class::Utils::GetMethodPointer("Sons.Characters.CharacterManager", "DebugAddCharacter");

Both the component and the method are found but I get the same result calling it using Globals::CharacterManager_component. Am I using the wrong GameObject/Component, wrong method pointer or passing wrong argument types (the System_string most likely) ?

extremeblackliu commented 1 year ago

u still didnt understand... Globals::CharacterManager_component.CallMethod("DebugAddCharacter", IL2CPP::String::New("cannibal"), true);

ImAxel0 commented 1 year ago

u still didnt understand... Globals::CharacterManager_component.CallMethod("DebugAddCharacter", IL2CPP::String::New("cannibal"), true);

Tried like that:

Globals::CharacterManager_component->CallMethod<bool, Unity::System_String*, bool>("DebugAddCharacter", IL2CPP::String::New("cannibal"), true);

Still gives access violation. Don't know if that matters but from UnityExplorer I see the "CharacterManager" gameobject is a child of the "GameManagers" gameobject: https://i.imgur.com/74LDw0F.png

extremeblackliu commented 1 year ago

Globals::CharacterManager = Unity::GameObject::Find("GameManager/CharacterManager");

Globals::CharacterManager_component = Globals::CharacterManager->GetComponent("CharacterManager");

Globals::CharacterManager_component->CallMethod("DebugAddCharacter", IL2CPP::String::New("cannibal"), true);

if still doesnt work, i will check the game later

ImAxel0 commented 1 year ago

Still got the same. This doesn't just happen with functions which have System_string as an argument but even only an int etc.

extremeblackliu commented 1 year ago

i got no problem with that code, your crash probably calling function outside of game thread or something. https://imgur.com/a/nZgJOGv

inside of hooked update, i choosed DamageDebugManager::Update

if (config::sometest)
{
    Unity::CGameObject* characterManager = Unity::GameObject::Find("CharacterManager");
    Logger::AddLog("CharacterManager Object: %p", characterManager);
    if (characterManager)
    {
        Unity::CComponent* component = characterManager->GetComponent("CharacterManager");
        Logger::AddLog("CharacterManager.Component: %p", characterManager);
        if (component)
        {
            bool result = component->CallMethod<bool>("DebugAddCharacter", IL2CPP::String::New("cannibal"), true);
            Logger::AddLog("CharacterManager.Component.DebugAddCharacter.result : %d", result);
        }
    }
    config::sometest = false;
}
ImAxel0 commented 1 year ago

Thank you very much! It was all because I didn't called it like this:

IL2CPP::Callback::Initialize();
IL2CPP::Callback::OnUpdate::Add(OurUpdateFunction);
void OurUpdateFunction()
{
    if (!Config::sometest)
    {
        Globals::CharacterManager_component->CallMethod<bool>("DebugAddCharacter", IL2CPP::String::New("cannibal"), true);
        printf("called\n");
        Config::sometest = true;
    }
}

Basically I need to call the method one time inside the OurUpdateFunction() and than I can call it everywhere like this:

if (ImGui::SmallButton("Spawn cannibal"))
{
    Globals::CharacterManager_component->CallMethod<bool>("DebugAddCharacter", IL2CPP::String::New("cannibal"), true);
}

Would you mind briefly explaining what IL2CPP::Callback::OnUpdate::Add(OurUpdateFunction) does and why the method needed to be called one time inside this function before calling it outside of it? Just curious and want to understand

Edit: seems like It doesn't always work, still crashes sometimes when calling with the imgui small button, will figure it out myself. Anyway thanks for all your help!

extremeblackliu commented 1 year ago

Edit: seems like It doesn't always work, still crashes sometimes when calling with the imgui small button, will figure it out myself. Anyway thanks for all your help!

you cant use it like that, in my code example, in menu , i did something like:

if (ImGui::Button1("Test"))
{
    config::sometest = true;
}

Would you mind briefly explaining what IL2CPP::Callback::OnUpdate::Add(OurUpdateFunction) does and why the method needed to be called one time inside this function before calling it outside of it? Just curious and want to understand

Update() is called each frame, unity has like

while(true)
{
      for(int i = 0; i < updatefunctions.size; i++)
     {
             if(updatefunctions[i].available)
             {
                    updatefunctions[i].func(); // call it
             }
     }
}

its pseudocode but it works like that. also every unity thread is inside unity thread list, because GC(garbage collector) needs it ,also for exception handler, and etc... remember unity is c#? it becomes native code on compile because thats it, il2cpp(IL code to C++). still highly recommend to you to try unity editor, also try thinking how the game was developed

IL2CPP::Callback::OnUpdate::Add(OurUpdateFunction) is the function hooks root update(all update called from there), so you dont need find a suitable update like i did :

inside of hooked update, i choosed DamageDebugManager::Update

this issue is good for anyone who is new on hacking il2cpp games for reference

ImAxel0 commented 1 year ago

Yes that's what I have figured out my self, used this:

if (ImGui::Button("Spawn Cannibal", ImVec2(ImGui::GetContentRegionAvail().x, NULL)))
{
    Config::MethodToggleCall::Value::DebugAddCharacter::character = "cannibal";
    Config::MethodToggleCall::DebugAddDefinedCharacter = true;
}

to toggle call the methods inside the OurUpdateFunction()

Thank you for the explanation and all!