Solved Storing address of a class, then using it later on crashes the game

Status
Not open for further replies.

ThePerplexedOne

Approved Modder
Original poster
Approved Modder
Jun 9, 2019
43
1,989
183
29
United Kingdom
I've got a button on my menu to add 1k currency each time it is pressed:

C++:
        case 5:
            if(playerDataManager) {
                AddGem(playerDataManager, 1000, nullptr, nullptr);
            }
            break;
To get `playerDataManager`, I hook the `Initialize` function:
1655226470257.png

C++:
void(*old_PlayerDataManager_Initialize)(void *instance);
void PlayerDataManager_Initialize(void *instance) {
    old_PlayerDataManager_Initialize(instance);
    playerDataManager = instance;
    //AddGem(instance, 100000, nullptr, nullptr);
}
C++:
HOOK("0xE0A634", PlayerDataManager_Initialize, old_PlayerDataManager_Initialize);
And then I also get the `AddGem` function:
1655226601685.png

C++:
AddGem = (void(*)(void*, u_long, void*, void*))getAbsoluteAddress(targetLibName, 0xE0D2DC);
This only works if I call `AddGem` from inside my hooked function. If I store the address and reference it later, it crashes the game.
 

mino260806

1/3 Games Approved
Dec 23, 2021
6
3
3
44
Tunisia
Strangely, this also exactly happened to me. Seems like the object is protected from read / write when the initialization is finished
 

ThePerplexedOne

Approved Modder
Original poster
Approved Modder
Jun 9, 2019
43
1,989
183
29
United Kingdom
Strangely, this also exactly happened to me. Seems like the object is protected from read / write when the initialization is finished
I'm not sure. I thought it might have something to do with the functions being in two separate libraries (mod lib and libil2cpp), but then how come hooked functions still work?
 

ThePerplexedOne

Approved Modder
Original poster
Approved Modder
Jun 9, 2019
43
1,989
183
29
United Kingdom
Or I'm thinking maybe it's a codestage thing? Something is preventing pointers from being used outside of hooked functions
 

Raebydett

Awesome Active Platinian
Jan 20, 2020
171
60
28
G

ThePerplexedOne

Approved Modder
Original poster
Approved Modder
Jun 9, 2019
43
1,989
183
29
United Kingdom
Maybe u can read this, he explained somewhere there
Hooking "Update" and then toggling inside there is just a workaround for the issue I'm having. Unless I'm missing something?
 

ThePerplexedOne

Approved Modder
Original poster
Approved Modder
Jun 9, 2019
43
1,989
183
29
United Kingdom
I think the issue comes from the cheat being ran on a thread that isn't part of the game. I think if we can somehow attach it to a game thread, rather than creating our own, it might work.
 

Vector4

Platinian
Jun 6, 2022
13
14
3
23
Ireland
It's probably due to IL2CPP garbage collecting the object and deleting it if it detects that no Managed code is using it. You can use the IL2CPP API functions il2cpp_gchandle_new and il2cpp_gchandle_free to make sure it doesn't get garbage collected. Here's an example:

C++:
#include <cstdint>

void* playerDataManager = nullptr;
uint32_t playerDataManager_gcHandle = -1;

// Make sure to get the addresses or offsets of these functions.
void* (*il2cpp_domain_get)(); // Gets the current IL2CPP domain and returns it. Use it for il2cpp_thread_attach.
void* (*il2cpp_thread_attach)(void* domain); // Attaches the current thread to the IL2CPP domain and returns the thread object. Use it for il2cpp_thread_detach. Do not call in a hooked function.
void (*il2cpp_thread_detach)(void* thread); // Detaches the current thread from the IL2CPP domain. Do not call in a hooked function.
uint32_t (*il2cpp_gchandle_new)(void* object, bool weak); // Prevents the object from being garbage collected and returns the handle for it. Use the return value for il2cpp_gchandle_free.
void (*il2cpp_gchandle_free)(uint32_t gchandle); // Allows the object to be garbage collected. Call with the gchandle received from il2cpp_gchandle_new

void (*old_PlayerDataManager_Initialize)(void* instance);

void PlayerDataManager_Initialize(void* instance) {
    old_PlayerDataManager_Initialize(instance);
    
    if (instance) { // check for nullptr
        if (playerDataManager_gchandle != -1) // make sure the handle is valid
            il2cpp_gchandle_free(playerDataManager_gchandle); // a new PlayerDataManager is being initialized, get rid of the old one.
        
        playerDataManager_gchandle = il2cpp_gchandle_new(instance, false); // create a new handle so that the object doesn't get deleted
        playerDataManager = instance;
    }
}

void ButtonClicked() {
    void* thread = il2cpp_thread_attach(il2cpp_domain_get());
    
    AddGem(playerDataManager, 1000, nullptr, nullptr);
    
    il2cpp_thread_detach(thread);
}
If possible, try to post a link to the game so I can try to help.
 

ThePerplexedOne

Approved Modder
Original poster
Approved Modder
Jun 9, 2019
43
1,989
183
29
United Kingdom
It's probably due to IL2CPP garbage collecting the object and deleting it if it detects that no Managed code is using it. You can use the IL2CPP API functions il2cpp_gchandle_new and il2cpp_gchandle_free to make sure it doesn't get garbage collected. Here's an example:

C++:
#include <cstdint>

void* playerDataManager = nullptr;
uint32_t playerDataManager_gcHandle = -1;

// Make sure to get the addresses or offsets of these functions.
void* (*il2cpp_domain_get)(); // Gets the current IL2CPP domain and returns it. Use it for il2cpp_thread_attach.
void* (*il2cpp_thread_attach)(void* domain); // Attaches the current thread to the IL2CPP domain and returns the thread object. Use it for il2cpp_thread_detach. Do not call in a hooked function.
void (*il2cpp_thread_detach)(void* thread); // Detaches the current thread from the IL2CPP domain. Do not call in a hooked function.
uint32_t (*il2cpp_gchandle_new)(void* object, bool weak); // Prevents the object from being garbage collected and returns the handle for it. Use the return value for il2cpp_gchandle_free.
void (*il2cpp_gchandle_free)(uint32_t gchandle); // Allows the object to be garbage collected. Call with the gchandle received from il2cpp_gchandle_new

void (*old_PlayerDataManager_Initialize)(void* instance);

void PlayerDataManager_Initialize(void* instance) {
    old_PlayerDataManager_Initialize(instance);
   
    if (instance) { // check for nullptr
        if (playerDataManager_gchandle != -1) // make sure the handle is valid
            il2cpp_gchandle_free(playerDataManager_gchandle); // a new PlayerDataManager is being initialized, get rid of the old one.
       
        playerDataManager_gchandle = il2cpp_gchandle_new(instance, false); // create a new handle so that the object doesn't get deleted
        playerDataManager = instance;
    }
}

void ButtonClicked() {
    void* thread = il2cpp_thread_attach(il2cpp_domain_get());
   
    AddGem(playerDataManager, 1000, nullptr, nullptr);
   
    il2cpp_thread_detach(thread);
}
If possible, try to post a link to the game so I can try to help.
This is huge information, thank you so much! I will try this and see what happens :)
 

ThePerplexedOne

Approved Modder
Original poster
Approved Modder
Jun 9, 2019
43
1,989
183
29
United Kingdom
It's probably due to IL2CPP garbage collecting the object and deleting it if it detects that no Managed code is using it. You can use the IL2CPP API functions il2cpp_gchandle_new and il2cpp_gchandle_free to make sure it doesn't get garbage collected. Here's an example:

C++:
#include <cstdint>

void* playerDataManager = nullptr;
uint32_t playerDataManager_gcHandle = -1;

// Make sure to get the addresses or offsets of these functions.
void* (*il2cpp_domain_get)(); // Gets the current IL2CPP domain and returns it. Use it for il2cpp_thread_attach.
void* (*il2cpp_thread_attach)(void* domain); // Attaches the current thread to the IL2CPP domain and returns the thread object. Use it for il2cpp_thread_detach. Do not call in a hooked function.
void (*il2cpp_thread_detach)(void* thread); // Detaches the current thread from the IL2CPP domain. Do not call in a hooked function.
uint32_t (*il2cpp_gchandle_new)(void* object, bool weak); // Prevents the object from being garbage collected and returns the handle for it. Use the return value for il2cpp_gchandle_free.
void (*il2cpp_gchandle_free)(uint32_t gchandle); // Allows the object to be garbage collected. Call with the gchandle received from il2cpp_gchandle_new

void (*old_PlayerDataManager_Initialize)(void* instance);

void PlayerDataManager_Initialize(void* instance) {
    old_PlayerDataManager_Initialize(instance);
   
    if (instance) { // check for nullptr
        if (playerDataManager_gchandle != -1) // make sure the handle is valid
            il2cpp_gchandle_free(playerDataManager_gchandle); // a new PlayerDataManager is being initialized, get rid of the old one.
       
        playerDataManager_gchandle = il2cpp_gchandle_new(instance, false); // create a new handle so that the object doesn't get deleted
        playerDataManager = instance;
    }
}

void ButtonClicked() {
    void* thread = il2cpp_thread_attach(il2cpp_domain_get());
   
    AddGem(playerDataManager, 1000, nullptr, nullptr);
   
    il2cpp_thread_detach(thread);
}
If possible, try to post a link to the game so I can try to help.
It actually worked! Thank you so much, you've saved me so much headache :D
 
Status
Not open for further replies.