Help! IL2CPP Game crash when Hooking a specific Function/Method

Sunghera

Platinian
Original poster
Jan 4, 2024
11
1
3
26
I've tried with the LGL Menu's HOOK_LIB and BNM's BNM::HOOK but neither has worked and both will crash when this method is hooked.

My Hook code:
C++:
void *(*old_getClassPassive)(void *instance);
void *getClassPassive(void *instance) {
    LOGD("Return getClassPassive");
    return old_getClassPassive(instance);
}
The hooking methods I've tried (of course I wouldn't use both at the same time):
C++:
HOOK_LIB("libil2cpp.so", "0x1546698", getClassPassive, old_getClassPassive);
C++:
auto BattleUserServantDataClass = LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"));
BNM::HOOK(BattleUserServantDataClass.GetMethodByName(OBFUSCATE_BNM("getClassPassive")), getClassPassive, old_getClassPassive);
The game is Fate/Grand Order (Japanese version) ver. 2.85.2 (but I think the ENG version has the same issue with the same method).
What the method looks like in dnSpy:
Screenshot 2024-01-04 230504.png


I have about 30 other hooks for this game yet only this one crashes. On top of which the game crashes before even reaching/printing the LOGD line...
Does anyone know what is wrong and how I can overcome this?
 

Backshift

Solid & Active Platinian
Oct 10, 2023
56
37
18
32
Just had a look in IDA at this function, it looks like its too small, check the spoiler for the screenshot
1704512057140.png
getClassPassive() is at 0x1546698, but its only 8 bytes (00 D4 40 F9 C0 03 5F D6]), the next function starts right after it and is GetSelfSkillArray() at 0x15466A0.

Hooking usually requires 8 bytes ALTEAST, it can also require more, which would mean getClassPassive is to small to fit the hook.
I logged the bytes starting from getClassPassive(), using a very quickly made function and using Dobby Hook:
C++:
void LogMemory(uintptr_t address, int numInstructions)
{
    int bytesToRead = numInstructions * 4;

    std::vector<uintptr_t> byteVec = {};
    LOGD("========== START ==========");
    LOGD("");
    LOGD("> getClassPassive <");
    for (int i = 0; i <= bytesToRead; i++)
    {
        if (i > 0 && (i % 4) == 0)
        {
            char bytes[128];
            sprintf(bytes, "0x%01x\t0x%01x\t0x%01x\t0x%01x", *(char*)byteVec[0], *(char*)byteVec[1], *(char*)byteVec[2], *(char*)byteVec[3]);
            LOGD("Address: %p | Bytes: %s", (void*)(address-4), bytes);
            byteVec.clear();
        }

        if (i == 8)
        {
            LOGD("");
            LOGD("> GetSelfSkillArray < ");
        }

        byteVec.push_back(address);
        address++;
    }
    LOGD("");
    LOGD("=========== END ===========");
}
Then logging the bytes before and after the hook:
C++:
LOGD("BEFORE");
LogMemory(getclassPassiveAddr, 8);
install_hook_getClassPassive((void*)getclassPassiveAddr);
LOGD("AFTER");
LogMemory(getclassPassiveAddr, 8);
The first time it logs the same bytes shown in IDA:
FGO_TEST: > getClassPassive <
FGO_TEST: Address: 0x7666c9d698 | Bytes: 0x00 0xD4 0x40 0xf9
FGO_TEST: Address: 0x7666c9d69c | Bytes: 0xC0 0x03 0x5f 0xd6
FGO_TEST:
FGO_TEST: > GetSelfSkillArray <
FGO_TEST: Address: 0x7666c9d6a0 | Bytes: 0xf4 0x4f 0xbe 0xa9
FGO_TEST: Address: 0x7666c9d6a4 | Bytes: 0xfd 0x7b 0x1 0xa9
FGO_TEST: Address: 0x7666c9d6a8 | Bytes: 0xfd 0x43 0x0 0x91
FGO_TEST: Address: 0x7666c9d6ac | Bytes: 0xf3 0x3 0x0 0xaa
FGO_TEST: Address: 0x7666c9d6b0 | Bytes: 0xbd 0xfe 0xff 0x97
FGO_TEST: Address: 0x7666c9d6b4 | Bytes: 0x68 0x2 0x40 0xf9
After the hook is done, check the bytes getClassPassive() and the first instructions bytes for the GetSelfSkillArray(), they are different
FGO_TEST: > getClassPassive <
FGO_TEST: Address: 0x7666c9d698 | Bytes: 0x71 0x41 0x3 0xf0
FGO_TEST: Address: 0x7666c9d69c | Bytes: 0x31 0xf2 0x22 0x91
FGO_TEST:
FGO_TEST: > GetSelfSkillArray <
FGO_TEST: Address: 0x7666c9d6a0 | Bytes: 0x20 0x2 0x1f 0xd6
FGO_TEST: Address: 0x7666c9d6a4 | Bytes: 0xfd 0x7b 0x1 0xa9
FGO_TEST: Address: 0x7666c9d6a8 | Bytes: 0xfd 0x43 0x0 0x91
FGO_TEST: Address: 0x7666c9d6ac | Bytes: 0xf3 0x3 0x0 0xaa
FGO_TEST: Address: 0x7666c9d6b0 | Bytes: 0xbd 0xfe 0xff 0x97
FGO_TEST: Address: 0x7666c9d6b4 | Bytes: 0x68 0x2 0x40 0xf9
Dobby is writing 12 bytes to getClassPassive() but getClassPassive only has 8 bytes or space, so its bleeding into the next function

Dobby is trying to write these 12 bytes [71 41 03 f0 31 f2 22 91 20 02 1f d6] which in arm64 assembly is:
Code:
adrp x17, #0x682f000
add x17, x17, #0x8bc
br x17
This will cause a crash in the affected functions since getClassPassive now look like:
Code:
adrp x17, #0x682f000
add x17, x17, #0x8bc
and the first instruction of GetSelfSkillArray now has the actual jump instruction to what dobby intended to be your hook code:
Code:
br x17
 

mIsmanXP

Approved Modder
Approved Modder
Feb 20, 2022
205
9,728
193
Republic of Indonesia
Just had a look in IDA at this function, it looks like its too small, check the spoiler for the screenshot
1704512031200.png
getClassPassive() is at 0x1546698, but its only 8 bytes (00 D4 40 F9 C0 03 5F D6]), the next function starts right after it and is GetSelfSkillArray() at 0x15466A0.

Hooking usually requires 8 bytes ALTEAST, it can also require more, which would mean getClassPassive is to small to fit the hook.
I logged the bytes starting from getClassPassive(), using a very quickly made function and using Dobby Hook:
C++:
void LogMemory(uintptr_t address, int numInstructions)
{
    int bytesToRead = numInstructions * 4;

    std::vector<uintptr_t> byteVec = {};
    LOGD("========== START ==========");
    LOGD("");
    LOGD("> getClassPassive <");
    for (int i = 0; i <= bytesToRead; i++)
    {
        if (i > 0 && (i % 4) == 0)
        {
            char bytes[128];
            sprintf(bytes, "0x%01x\t0x%01x\t0x%01x\t0x%01x", *(char*)byteVec[0], *(char*)byteVec[1], *(char*)byteVec[2], *(char*)byteVec[3]);
            LOGD("Address: %p | Bytes: %s", (void*)(address-4), bytes);
            byteVec.clear();
        }

        if (i == 8)
        {
            LOGD("");
            LOGD("> GetSelfSkillArray < ");
        }

        byteVec.push_back(address);
        address++;
    }
    LOGD("");
    LOGD("=========== END ===========");
}
Then logging the bytes before and after the hook:
C++:
LOGD("BEFORE");
LogMemory(getclassPassiveAddr, 8);
install_hook_getClassPassive((void*)getclassPassiveAddr);
LOGD("AFTER");
LogMemory(getclassPassiveAddr, 8);
The first time it logs the same bytes shown in IDA:
FGO_TEST: > getClassPassive <
FGO_TEST: Address: 0x7666c9d698 | Bytes: 0x00 0xD4 0x40 0xf9
FGO_TEST: Address: 0x7666c9d69c | Bytes: 0xC0 0x03 0x5f 0xd6
FGO_TEST:
FGO_TEST: > GetSelfSkillArray <
FGO_TEST: Address: 0x7666c9d6a0 | Bytes: 0xf4 0x4f 0xbe 0xa9
FGO_TEST: Address: 0x7666c9d6a4 | Bytes: 0xfd 0x7b 0x1 0xa9
FGO_TEST: Address: 0x7666c9d6a8 | Bytes: 0xfd 0x43 0x0 0x91
FGO_TEST: Address: 0x7666c9d6ac | Bytes: 0xf3 0x3 0x0 0xaa
FGO_TEST: Address: 0x7666c9d6b0 | Bytes: 0xbd 0xfe 0xff 0x97
FGO_TEST: Address: 0x7666c9d6b4 | Bytes: 0x68 0x2 0x40 0xf9
After the hook is done, check the bytes getClassPassive() and the first instructions bytes for the GetSelfSkillArray(), they are different
FGO_TEST: > getClassPassive <
FGO_TEST: Address: 0x7666c9d698 | Bytes: 0x71 0x41 0x3 0xf0
FGO_TEST: Address: 0x7666c9d69c | Bytes: 0x31 0xf2 0x22 0x91
FGO_TEST:
FGO_TEST: > GetSelfSkillArray <
FGO_TEST: Address: 0x7666c9d6a0 | Bytes: 0x20 0x2 0x1f 0xd6
FGO_TEST: Address: 0x7666c9d6a4 | Bytes: 0xfd 0x7b 0x1 0xa9
FGO_TEST: Address: 0x7666c9d6a8 | Bytes: 0xfd 0x43 0x0 0x91
FGO_TEST: Address: 0x7666c9d6ac | Bytes: 0xf3 0x3 0x0 0xaa
FGO_TEST: Address: 0x7666c9d6b0 | Bytes: 0xbd 0xfe 0xff 0x97
FGO_TEST: Address: 0x7666c9d6b4 | Bytes: 0x68 0x2 0x40 0xf9
Dobby is writing 12 bytes to getClassPassive() but getClassPassive only has 8 bytes or space, so its bleeding into the next function

Dobby is trying to write these 12 bytes [71 41 03 f0 31 f2 22 91 20 02 1f d6] which in arm64 assembly is:
Code:
adrp x17, #0x682f000
add x17, x17, #0x8bc
br x17
This will cause a crash in the affected functions since getClassPassive now look like:
Code:
adrp x17, #0x682f000
add x17, x17, #0x8bc
and the first instruction of GetSelfSkillArray now has the actual jump instruction to what dobby intended to be your hook code:
Code:
br x17
Tried this with frida and they're only using 7 bytes, don't know if it's working tho
1704512037055.png

I wonder if frida detects that this function length is 8 bytes and use hooking method which require less bytes
 

Backshift

Solid & Active Platinian
Oct 10, 2023
56
37
18
32
I wonder if frida detects that this function length is 8 bytes and use hooking method which require less bytes
Yeah, I would imagine its hooking framework dependent based on their implementation?,

BNM::HOOK calls through to Dobby from what I saw, which would make sense why it crashes in both since I used plain dobby and got 12 bytes written to an 8 byte function.

Frida staying within the bounds of the function would mean its ok too I would think, although you would need to get into a battle (based on the class/method names) to confirm that. Interesting frida stays within 8 bytes though.

EDIT: Just converted the arm64 hex:

Dobby writes:
Code:
adrp x17, #0x682f000
add x17, x17, #0x8bc
Bx x17
Frida writes:
Code:
 
  • Like
Reactions: CodeJutsu

Sunghera

Platinian
Original poster
Jan 4, 2024
11
1
3
26
Thank you legends for finding what the issue is! I don't suppose this will be the last 8 byte function that we'll encounter, so what are some methods to circumvent this problem? Any frameworks/libs that we know of that can have the option to squeeze the hook within 8 bytes?
 

CodeJutsu

Platinian
Oct 1, 2023
47
25
18
30
Thank you legends for finding what the issue is! I don't suppose this will be the last 8 byte function that we'll encounter, so what are some methods to circumvent this problem? Any frameworks/libs that we know of that can have the option to squeeze the hook within 8 bytes?
Why not use the field offset of the function instead of hooking the function itself
 

Backshift

Solid & Active Platinian
Oct 10, 2023
56
37
18
32
Any frameworks/libs that we know of that can have the option to squeeze the hook within 8 bytes?
Not that I know of personally, Frida appears to work as @mIsmanXP said above, although frida is amazing for testing to actually make a packaged mod apk its not the best, although it is possible.

Personally, I would look for alternative ways to get the data you want like @CodeJutsu said.

If you dont mind me asking, what was your goal with getClassPassive(), I might be able to suggest something if you can give me the goal you had in mind, totally up to you though.
 

Sunghera

Platinian
Original poster
Jan 4, 2024
11
1
3
26
I haven't had much luck in successfully creating a new monoArray and replacing the old one with the new one within an instance.
I have no problem in replacing/updating individual elements of the native int array, however the size of the array is predetermined by who the servant/character is.
So if I wanted to add a few more passives then I'll need a bigger array.

I've had luck in using BNM's method of creating a new array, and have a function that suppose to return the monoArray to return the array that I've made. e.g.

C++:
monoArray<int> *customArray = nullptr;

void initCustomArrays() {
    customArray = monoArray<int>::Create(3);
    customArray ->Update(0, 100);
    customArray ->Update(1, 100);
    customArray ->Update(2, 100);

}

monoArray<int> *(*old_someMethodToReturnAnArray)(void *instance);
monoArray<int> *someMethodToReturnAnArray(void *instance) {
    if (someCondition) {
        return customArray;
    }
    return old_someMethodToReturnAnArray(instance);
}
The code above works fine and I haven't had any crashes related to it.


However, if I were to try something like this in the BattleUserServantData:
C++:
BNM::Field<monoArray<int>*> BattleUserServantData_classPassive__Field{};

int (*old_getBattleSvtId)(void *instance);
int getBattleSvtId(void *instance) {
   
    bool isMyServant;
   
    // Some code around here to determine which are the characters/servants I'm looking for
   
    if (isMyServant) {
        monoArray<int> *classPassive = *BattleUserServantData_classPassive__Field[instance].GetPointer();
       
        for (int i = 0; i < classPassive->GetCapacity(); i++){
            LOGD("The Servants's classPassive: i: %d, v: %d", i, *classPassive->At(i));
        }
       
        monoArray<int> *myOwnArray = BNM::LoadClass(BNM::LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"))).NewArray<int>(3);
        for (int i = 0; i < myOwnArray->GetCapacity(); i++) {
            myOwnArray->Update(i, 2188000);
        }
       
        BattleUserServantData_classPassive__Field[instance].Set(myOwnArray);
       
        monoArray<int> *classPassiveVerify = *BattleUserServantData_classPassive__Field[instance].GetPointer();
        for (int i = 0; i < classPassiveVerify->GetCapacity(); i++){
            LOGD("The Servants's classPassive UPDATED: i: %d, v: %d", i, *classPassiveVerify->At(i));
        }
   
    }
    return old_getBattleSvtId(instance);
}

void OnLoaded_Hooks() {
    auto BattleUserServantDataClass = LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"));
    BattleUserServantData_classPassive__Field = BattleUserServantDataClass.GetFieldByName(OBFUSCATE_BNM("classPassive"));
}

Through classPassive, I'm able to see what was there before, and through classPassiveVerify what is there after I've updated the field.
However the game would crash before fully loading in the battle.

I'm currently assuming it is something to do with Unity's garbage collection that is causing it to crash perhaps?
 

CodeJutsu

Platinian
Oct 1, 2023
47
25
18
30
I haven't had much luck in successfully creating a new monoArray and replacing the old one with the new one within an instance.

I have no problem in replacing/updating individual elements of the native int array, however the size of the array is predetermined by who the servant/character is.

So if I wanted to add a few more passives then I'll need a bigger array.



I've had luck in using BNM's method of creating a new array, and have a function that suppose to return the monoArray to return the array that I've made. e.g.



C++:
monoArray<int> *customArray = nullptr;



void initCustomArrays() {

    customArray = monoArray<int>::Create(3);

    customArray ->Update(0, 100);

    customArray ->Update(1, 100);

    customArray ->Update(2, 100);



}



monoArray<int> *(*old_someMethodToReturnAnArray)(void *instance);

monoArray<int> *someMethodToReturnAnArray(void *instance) {

    if (someCondition) {

        return customArray;

    }

    return old_someMethodToReturnAnArray(instance);

}
The code above works fine and I haven't had any crashes related to it.





However, if I were to try something like this in the BattleUserServantData:

C++:
BNM::Field<monoArray<int>*> BattleUserServantData_classPassive__Field{};



int (*old_getBattleSvtId)(void *instance);

int getBattleSvtId(void *instance) {

  

    bool isMyServant;

  

    // Some code around here to determine which are the characters/servants I'm looking for

  

    if (isMyServant) {

        monoArray<int> *classPassive = *BattleUserServantData_classPassive__Field[instance].GetPointer();

      

        for (int i = 0; i < classPassive->GetCapacity(); i++){

            LOGD("The Servants's classPassive: i: %d, v: %d", i, *classPassive->At(i));

        }

      

        monoArray<int> *myOwnArray = BNM::LoadClass(BNM::LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"))).NewArray<int>(3);

        for (int i = 0; i < myOwnArray->GetCapacity(); i++) {

            myOwnArray->Update(i, 2188000);

        }

      

        BattleUserServantData_classPassive__Field[instance].Set(myOwnArray);

      

        monoArray<int> *classPassiveVerify = *BattleUserServantData_classPassive__Field[instance].GetPointer();

        for (int i = 0; i < classPassiveVerify->GetCapacity(); i++){

            LOGD("The Servants's classPassive UPDATED: i: %d, v: %d", i, *classPassiveVerify->At(i));

        }

  

    }

    return old_getBattleSvtId(instance);

}



void OnLoaded_Hooks() {

    auto BattleUserServantDataClass = LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"));

    BattleUserServantData_classPassive__Field = BattleUserServantDataClass.GetFieldByName(OBFUSCATE_BNM("classPassive"));

}




Through classPassive, I'm able to see what was there before, and through classPassiveVerify what is there after I've updated the field.

However the game would crash before fully loading in the battle.



I'm currently assuming it is something to do with Unity's garbage collection that is causing it to crash perhaps?
Could you provide crash log when you crash in battle maybe it's causing null reference somewhere
 

Backshift

Solid & Active Platinian
Oct 10, 2023
56
37
18
32
I haven't had much luck in successfully creating a new monoArray and replacing the old one with the new one within an instance.
I have no problem in replacing/updating individual elements of the native int array, however the size of the array is predetermined by who the servant/character is.
So if I wanted to add a few more passives then I'll need a bigger array.

I've had luck in using BNM's method of creating a new array, and have a function that suppose to return the monoArray to return the array that I've made. e.g.

C++:
monoArray<int> *customArray = nullptr;

void initCustomArrays() {
    customArray = monoArray<int>::Create(3);
    customArray ->Update(0, 100);
    customArray ->Update(1, 100);
    customArray ->Update(2, 100);

}

monoArray<int> *(*old_someMethodToReturnAnArray)(void *instance);
monoArray<int> *someMethodToReturnAnArray(void *instance) {
    if (someCondition) {
        return customArray;
    }
    return old_someMethodToReturnAnArray(instance);
}
The code above works fine and I haven't had any crashes related to it.


However, if I were to try something like this in the BattleUserServantData:
C++:
BNM::Field<monoArray<int>*> BattleUserServantData_classPassive__Field{};

int (*old_getBattleSvtId)(void *instance);
int getBattleSvtId(void *instance) {

    bool isMyServant;

    // Some code around here to determine which are the characters/servants I'm looking for

    if (isMyServant) {
        monoArray<int> *classPassive = *BattleUserServantData_classPassive__Field[instance].GetPointer();
    
        for (int i = 0; i < classPassive->GetCapacity(); i++){
            LOGD("The Servants's classPassive: i: %d, v: %d", i, *classPassive->At(i));
        }
    
        monoArray<int> *myOwnArray = BNM::LoadClass(BNM::LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"))).NewArray<int>(3);
        for (int i = 0; i < myOwnArray->GetCapacity(); i++) {
            myOwnArray->Update(i, 2188000);
        }
    
        BattleUserServantData_classPassive__Field[instance].Set(myOwnArray);
    
        monoArray<int> *classPassiveVerify = *BattleUserServantData_classPassive__Field[instance].GetPointer();
        for (int i = 0; i < classPassiveVerify->GetCapacity(); i++){
            LOGD("The Servants's classPassive UPDATED: i: %d, v: %d", i, *classPassiveVerify->At(i));
        }

    }
    return old_getBattleSvtId(instance);
}

void OnLoaded_Hooks() {
    auto BattleUserServantDataClass = LoadClass(OBFUSCATE_BNM(""),OBFUSCATE_BNM("BattleUserServantData"));
    BattleUserServantData_classPassive__Field = BattleUserServantDataClass.GetFieldByName(OBFUSCATE_BNM("classPassive"));
}

Through classPassive, I'm able to see what was there before, and through classPassiveVerify what is there after I've updated the field.
However the game would crash before fully loading in the battle.

I'm currently assuming it is something to do with Unity's garbage collection that is causing it to crash perhaps?
Ok just had a look, here is the code I came up with:
C++:
install_hook_name(getBattleSvtId, int, BattleUserServantData_o* pThis)
{
    bool isMyServant = true; // force it because I dont have the logic

    // Some code around here to determine which are the characters/servants I'm looking for

    if (isMyServant) {
        monoArray<int> *passives = (monoArray<int>*)pThis->fields.classPassive;

        for(int i = 0; i < passives->GetCapacity(); i++) {
            if (*passives->At(i) == 2188000) {
                LOGD("Char already has passive added!");
                return orig_getBattleSvtId(pThis);
            }
        }

        for(int i = 0; i < passives->GetCapacity(); i++)
            LOGD("[BEFORE] The Servants's classPassive: i: %d, v: %d", i, *passives->At(i));

        std::vector<int> newPassives = {};
        for(int i = 0; i < passives->GetCapacity(); i++)
            newPassives.push_back(passives->At(i));
        newPassives.push_back(2188000);

        pThis->fields.classPassive = (System_Int32_array*)monoArray<int>().Create(newPassives);

        monoArray<int> *passivesVerify = (monoArray<int>*)pThis->fields.classPassive;
        for(int i = 0; i < passivesVerify->GetCapacity(); i++)
            LOGD("[AFTER] The Servants's classPassive: i: %d, v: %d", i, *passivesVerify->At(i));
    }

    return orig_getBattleSvtId(pThis);
}
First thing, I changed the void* instance, void* is just a raw address, il2cppdumper actually generates an il2cpp.h which can be massively useful, although I dont see anyone ever make use of it. including that header from FGO il2cppdumper you have access to all the games classes/fields etc as structs hence I changed it to BattleUserServantData_o* pThis which then allows me to do this:
C++:
monoArray<int> *passives = (monoArray<int>*)pThis->fields.classPassive;
Note that in il2cpp.h pThis->fields.classPassive is of type struct System_Int32_array * hence the cast to BNM's type for easy use.

Here is the output from the code above:
Code:
FGO_TEST: [BEFORE] The Servants's classPassive: i: 0, v: 34550
FGO_TEST: [BEFORE] The Servants's classPassive: i: 1, v: 88350
FGO_TEST: [AFTER] The Servants's classPassive: i: 0, v: 34550
FGO_TEST: [AFTER] The Servants's classPassive: i: 1, v: 88350
FGO_TEST: [AFTER] The Servants's classPassive: i: 2, v: 2188000
FGO_TEST: Char already has passive added!
FGO_TEST: [AFTER] The Servants's classPassive: i: 0, v: 2188000
FGO_TEST: Char already has passive added!
--------- beginning of crash
Yes, it does crash, although the first time it goes through it adds a 3rd passive [34550, 88350, 2188000], the next log though.. it never logs the before message only the after where it adds a single [2188000], I have a feeling this is the enemy since I dont have the code for IsMyServant? It may work fine for you with this issue if you are filtering to only your team?

The crash happens a few seconds after the final message is logged FGO_TEST: Char already has passive added! and the backtrace for the crash doesnt mention my ELF binary at all, so it makes me wonder if the crash is potentially related to the last last log for the single passive being added to what I assume is the enemy is then confusing the game state later on in il2cpp.so since the enemy maybe isnt supposed to have this passive? or any passives at all? No idea since I never actually played FGO. Anyway here is the crashlog:
Code:
backtrace:
      #00 pc 0000000000cbc91c  libil2cpp.so
      #01 pc 0000000000cbc850  libil2cpp.so
      #02 pc 0000000000cbc4b8  libil2cpp.so
      #03 pc 0000000000cbc6f8  libil2cpp.so
      #04 pc 0000000000cbc4b8  libil2cpp.so
      #05 pc 0000000000cbc75c  libil2cpp.so
      #06 pc 0000000000cbc4b8  libil2cpp.so
      #07 pc 0000000000cbc6f8  libil2cpp.so
      #08 pc 0000000000cbc4b8  libil2cpp.so
      #09 pc 0000000000cbc75c  libil2cpp.so
      #10 pc 0000000000cbc4b8  libil2cpp.so
      #11 pc 0000000000cbc6f8  libil2cpp.so
      #12 pc 0000000000cbc4b8  libil2cpp.so
      #13 pc 0000000000cbc75c  libil2cpp.so
      #14 pc 0000000000cbc4b8  libil2cpp.so
      #15 pc 0000000000cbcf48  libil2cpp.so
      #16 pc 00000000002a0258  libunity.so
      #17 pc 00000000002adf00  libunity.so
      #18 pc 00000000002ad194  libunity.so
      #19 pc 00000000002adaf4  libunity.so
      #20 pc 00000000002a15b4  libunity.so
      #21 pc 00000000002a15e8  libunity.so
      #22 pc 00000000002a182c  libunity.so
      #23 pc 00000000003bf37c  libunity.so
      #24 pc 00000000003d51c4  libunity.so
      #25 pc 00000000000448fc  base.odex
 

Sunghera

Platinian
Original poster
Jan 4, 2024
11
1
3
26
I'm 99.99% sure that 2188000 is a passive that can be added to enemies as well.

Here's my version of the crash log:
at libil2cpp.0xcbc90c(Native Method)
at libil2cpp.0xcbc6d0(Native Method)
at libil2cpp.0xcbc4b8(Native Method)
at libil2cpp.0xcbc6f8(Native Method)
at libil2cpp.0xcbc4b8(Native Method)
at libil2cpp.0xcbc75c(Native Method)
at libil2cpp.0xcbc4b8(Native Method)
at libil2cpp.0xcbc6f8(Native Method)
at libil2cpp.0xcbc4b8(Native Method)
at libil2cpp.0xcbc75c(Native Method)
at libil2cpp.0xcbc4b8(Native Method)
at libil2cpp.0xcbc6f8(Native Method)
at libil2cpp.0xcbc4b8(Native Method)
at libil2cpp.0xcbc75c(Native Method)
at libil2cpp.0xcbc4b8(Native Method)
at libil2cpp.0xcbcf48(Native Method)
at libunity.0x2a0258(Native Method)
at libunity.0x2adf00(Native Method)
at libunity.0x2ad194(Native Method)
at libunity.0x2adaf4(Native Method)
at libunity.0x2a15b4(Native Method)
at libunity.0x2a15e8(Native Method)
at libunity.0x2a182c(Native Method)
at libunity.0x3bf37c(Native Method)
at libunity.0x3d51c4(Native Method)
at base.0x4bbb4(Native Method)