Help! Spoofing nickname on photon games

RazerTexz

Platinian
Original poster
May 17, 2020
36
5
8
the void
UPDATE: I managed to figure out how to correctly hooked the get_Nickname, i can change the nickname but it doesn't update ingame. Any reason to why? (I used LOGI to check the nickname)
 

RazerTexz

Platinian
Original poster
May 17, 2020
36
5
8
the void
Here's the code:
C++:
typedef struct _monoString {
    void *klass;
    void *monitor;
    int length;
    char chars[1];

    int getLength() {
        return length;
    }

    char *getChars() {
        return chars;
    }
} monoString;
monoString *(*String_CreateString)(void *_this, const char *str, int startIndex, int length);
void (*get_StringInstance);

#include <codecvt>
#include <locale>
std::string FromUTF16(monoString* str) {
    std::u16string u16(reinterpret_cast<const char16_t*>(str->chars));
    return std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.to_bytes(u16);
}

bool isGetNickname;
monoString* (*get_Nickname)(void *instance);
void (*old_Update)(void *instance);
void Update(void *instance) {
    if (instance != NULL) {
        if (isGetNickname) {
            LOGI("Nickname: %s", FromUTF16(get_Nickname(instance)).c_str());
        }
    }
    return old_Update(instance);
}

bool isSetNickname;
const char *Nickname;
monoString* (*old_getNickname)(void* instance);
monoString* getNickname(void* instance) {
    if (instance != NULL && isSetNickname) {
        return String_CreateString(get_StringInstance, Nickname, 0, (int)strlen(Nickname));
    }
    return old_getNickname(instance);
}

#if defined(__aarch64__) //To compile this code for arm64 lib only. Do not worry about greyed out highlighting code, it still works
    A64HookFunction((void*)getAbsoluteAddress(targetLibName, 0xFFC538), (void*)Update, (void**)&old_Update);
    A64HookFunction((void*)getAbsoluteAddress(targetLibName, 0x233574C), (void*)getNickname, (void**)&old_getNickname);
    String_CreateString = (monoString *(*)(void *, const char *, int startIndex, int length))getAbsoluteAddress(targetLibName, 0x2066CB0);
    get_StringInstance = (void (*))getAbsoluteAddress(targetLibName, 0x2066CB0);
    get_Nickname = (monoString* (*)(void *))getAbsoluteAddress(targetLibName, 0x233574C);

const char *features[] = {
    OBFUSCATE("30_CollapseAdd_Toggle_Get Nickname"),
    OBFUSCATE("37_CollapseAdd_InputText_Nickname"),
    OBFUSCATE("38_CollapseAdd_Toggle_Set Nickname"),
};

switch (featNum) {
    case 30:
        isGetNickname = boolean;
        break;
    case 37:
        Nickname = env->GetStringUTFChars(str, 0);
        break;
    case 38:
        isSetNickname = boolean;
        break;
}
 

MIDDLE

Platinian
Mar 16, 2020
8
3
3
23
[email protected]
Here's the code:
C++:
typedef struct _monoString {
    void *klass;
    void *monitor;
    int length;
    char chars[1];

    int getLength() {
        return length;
    }

    char *getChars() {
        return chars;
    }
} monoString;
monoString *(*String_CreateString)(void *_this, const char *str, int startIndex, int length);
void (*get_StringInstance);

#include <codecvt>
#include <locale>
std::string FromUTF16(monoString* str) {
    std::u16string u16(reinterpret_cast<const char16_t*>(str->chars));
    return std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.to_bytes(u16);
}

bool isGetNickname;
monoString* (*get_Nickname)(void *instance);
void (*old_Update)(void *instance);
void Update(void *instance) {
    if (instance != NULL) {
        if (isGetNickname) {
            LOGI("Nickname: %s", FromUTF16(get_Nickname(instance)).c_str());
        }
    }
    return old_Update(instance);
}

bool isSetNickname;
const char *Nickname;
monoString* (*old_getNickname)(void* instance);
monoString* getNickname(void* instance) {
    if (instance != NULL && isSetNickname) {
        return String_CreateString(get_StringInstance, Nickname, 0, (int)strlen(Nickname));
    }
    return old_getNickname(instance);
}

#if defined(__aarch64__) //To compile this code for arm64 lib only. Do not worry about greyed out highlighting code, it still works
    A64HookFunction((void*)getAbsoluteAddress(targetLibName, 0xFFC538), (void*)Update, (void**)&old_Update);
    A64HookFunction((void*)getAbsoluteAddress(targetLibName, 0x233574C), (void*)getNickname, (void**)&old_getNickname);
    String_CreateString = (monoString *(*)(void *, const char *, int startIndex, int length))getAbsoluteAddress(targetLibName, 0x2066CB0);
    get_StringInstance = (void (*))getAbsoluteAddress(targetLibName, 0x2066CB0);
    get_Nickname = (monoString* (*)(void *))getAbsoluteAddress(targetLibName, 0x233574C);

const char *features[] = {
    OBFUSCATE("30_CollapseAdd_Toggle_Get Nickname"),
    OBFUSCATE("37_CollapseAdd_InputText_Nickname"),
    OBFUSCATE("38_CollapseAdd_Toggle_Set Nickname"),
};

switch (featNum) {
    case 30:
        isGetNickname = boolean;
        break;
    case 37:
        Nickname = env->GetStringUTFChars(str, 0);
        break;
    case 38:
        isSetNickname = boolean;
        break;
}
This is either an unsuitable method, or try to hook in the Update() method.
 
  • Like
Reactions: RazerTexz

RazerTexz

Platinian
Original poster
May 17, 2020
36
5
8
the void
You could try playerName in networkingpeer and photonnetwork
Already tried everything that's related (method, variables, etc).
I'm trying to find the actual username because i think the name from photon or online stuff in the code doesn't update "offline" name. (the game is Offline/Online).
 

Backshift

Solid & Active Platinian
Oct 10, 2023
56
37
18
32
Remove instance != NULLin your getNickName hook and try again.

C++:
monoString* getNickname(void* instance) {
    if (isSetNickname) {
        return String_CreateString(get_StringInstance, Nickname, 0, (int)strlen(Nickname));
    }
    return old_getNickname(instance);
}
get_NickName in PhotonNetwork is public static string get_NickName() { }, get_NickName is static, static methods dont have a class instance the il2cpp documentation states the this pointer is set to NULL by default, but your code is checking if instance != NULL, your hook probably works fine, but the mod conditions is always skipped over due to this.
 
  • Like
Reactions: RazerTexz

fedesito

Platinian
Jun 11, 2023
6
5
3
25
no
hello, please read photon documentation
to set your nickname you must do so by changing setting PhotonNetwork.playerName before entering a game, this will change your OWN playername to whatever you set the string to and it will sync with other players
 
  • Like
Reactions: RazerTexz

RazerTexz

Platinian
Original poster
May 17, 2020
36
5
8
the void
hello, please read photon documentation
to set your nickname you must do so by changing setting PhotonNetwork.playerName before entering a game, this will change your OWN playername to whatever you set the string to and it will sync with other players
PhotonNetwork doesnt have playerName as a variable or a function. May i ask where did you find about PhotonNetwork have a variable or function called playerName?

I think you are referring to NickName which says:
Set to synchronize the player's nickname with everyone in the room(s) you enter. This sets PhotonNetwork.player.NickName.

The NickName is just a nickname and does not have to be unique or backed up with some account.
Set the value any time (e.g. before you connect) and it will be available to everyone you play with.
Access the names of players by: Player.NickName.
PhotonNetwork.PlayerListOthers is a list of other players - each contains the NickName the remote player set.
 

RazerTexz

Platinian
Original poster
May 17, 2020
36
5
8
the void
Remove instance != NULLin your getNickName hook and try again.

C++:
monoString* getNickname(void* instance) {
    if (isSetNickname) {
        return String_CreateString(get_StringInstance, Nickname, 0, (int)strlen(Nickname));
    }
    return old_getNickname(instance);
}
get_NickName in PhotonNetwork is public static string get_NickName() { }, get_NickName is static, static methods dont have a class instance the il2cpp documentation states the this pointer is set to NULL by default, but your code is checking if instance != NULL, your hook probably works fine, but the mod conditions is always skipped over due to this.
I'll try it, but i also have hooked
public static bool get_IsMasterClient() { }
which uses
C++:
if (instance != nullptr)
(I changed to nullptr but even with NULL it still works)
Why in ur opinion it has to be different if its static? oh and also the getNickname works because i already used LOGI to check the value.
 

RazerTexz

Platinian
Original poster
May 17, 2020
36
5
8
the void
Remove instance != NULLin your getNickName hook and try again.

C++:
monoString* getNickname(void* instance) {
    if (isSetNickname) {
        return String_CreateString(get_StringInstance, Nickname, 0, (int)strlen(Nickname));
    }
    return old_getNickname(instance);
}
get_NickName in PhotonNetwork is public static string get_NickName() { }, get_NickName is static, static methods dont have a class instance the il2cpp documentation states the this pointer is set to NULL by default, but your code is checking if instance != NULL, your hook probably works fine, but the mod conditions is always skipped over due to this.
If you just scroll up a bit u will see:
"UPDATE: I managed to figure out how to correctly hooked the get_Nickname, i can change the nickname but it doesn't update ingame. Any reason to why? (I used LOGI to check the nickname)"
 

Backshift

Solid & Active Platinian
Oct 10, 2023
56
37
18
32
oh and also the getNickname works because i already used LOGI to check the value.
your get_NickName LOGI is in your update() hook.... update is not a static method, so of course that's going to work, also why are you logging get_NickName in update() but modifying it in the get_NickName method?

it would be much easier and more consistent to check all this in your actual get_NickName hook:

C++:
if (isSetNickname) {
    LOGI("get_NickName hook!");
    std::string result = String_CreateString(get_StringInstance, Nickname, 0, (int)strlen(Nickname));
    LOGI("New name is: %s", result);
    return result;
}
This way if you see logs you know its getting to that code, if you don't even see logs then you know something is wrong such as: instance is null.

Why in ur opinion it has to be different if its static?
This isn't my opinion this is from reading info from the Unity developers themselves, they themself have stated that the this pointer in static methods is set to NULL by default since the this pointer (or instance) is a pointer to the instance of the object but static methods don't have an instance so Unity devs decided to set it to NULL by default.

Also, you aren't even using the instance parameter in your get_NickName hook so you shouldn't even need to check for or care what the this pointer is set to.

To be honest, in the 6 years I was modding and staff on android sites, I never ever checked if instance is null or not when il2cpp became a thing, I'm assuming it was done like that in some modding template and now everyone copy pastes that into their code without knowing why, to me honestly its unnecessary unless you understand what its for and have a specific reason to need to check if the this pointer is valid.
 

mIsmanXP

Approved Modder
Approved Modder
Feb 20, 2022
205
9,710
193
Republic of Indonesia
To be honest, in the 6 years I was modding and staff on android sites, I never ever checked if instance is null or not when il2cpp became a thing, I'm assuming it was done like that in some modding template and now everyone copy pastes that into their code without knowing why, to me honestly its unnecessary unless you understand what its for and have a specific reason to need to check if the this pointer is valid.
I've encounter a few cases where the instance is nullptr, especially when im hooking a constructor (.ctor)

There's also a case where the instance is random number and not a pointer to Il2CppObject

Im sure the people that make the template encounter this issue and add a check, and like you said people just copy pasting stuff without even knowing what they do
 

Backshift

Solid & Active Platinian
Oct 10, 2023
56
37
18
32
I've encounter a few cases where the instance is nullptr, especially when im hooking a constructor (.ctor)

There's also a case where the instance is random number and not a pointer to Il2CppObject

Im sure the people that make the template encounter this issue and add a check, and like you said people just copy pasting stuff without even knowing what they do
Yeah, that is what I was trying to get at, the fact that it can depend on the current situation rather than just checking if its NULL everywhere beacuse: "Thats how the template did it", especially since for static methods like I mentioned here, il2cpp devs state themselves its default value is NULL, which means instance != NULL can actually work against you.

Thanks for adding your own input/observations too!
 

RazerTexz

Platinian
Original poster
May 17, 2020
36
5
8
the void
your get_NickName LOGI is in your update() hook.... update is not a static method, so of course that's going to work, also why are you logging get_NickName in update() but modifying it in the get_NickName method?

it would be much easier and more consistent to check all this in your actual get_NickName hook:

C++:
if (isSetNickname) {
    LOGI("get_NickName hook!");
    std::string result = String_CreateString(get_StringInstance, Nickname, 0, (int)strlen(Nickname));
    LOGI("New name is: %s", result);
    return result;
}
This way if you see logs you know its getting to that code, if you don't even see logs then you know something is wrong such as: instance is null.
Thanks for the information! the time when i made the get_Nickname hook was when i was learning about string hooking and instance pointers i think, so i didnt really consider it as a possibility. I will try nonetheless.

This isn't my opinion this is from reading info from the Unity developers themselves, they themself have stated that the this pointer in static methods is set to NULL by default since the this pointer (or instance) is a pointer to the instance of the object but static methods don't have an instance so Unity devs decided to set it to NULL by default.

Also, you aren't even using the instance parameter in your get_NickName hook so you shouldn't even need to check for or care what the this pointer is set to.

To be honest, in the 6 years I was modding and staff on android sites, I never ever checked if instance is null or not when il2cpp became a thing, I'm assuming it was done like that in some modding template and now everyone copy pastes that into their code without knowing why, to me honestly its unnecessary unless you understand what its for and have a specific reason to need to check if the this pointer is valid.
Again, thanks for the information!
About the instance parameter thingy, i used it to return the original method of get_NickName (if u are not talking about the NickName hook part).

I don't have much information about instance but i think instance is the memory address of the class? (correct me if im wrong)
but then there is using another update method from another class and passing its instance to a method of another class to hook it like my update method.
 

RazerTexz

Platinian
Original poster
May 17, 2020
36
5
8
the void

Thanks for the information! the time when i made the get_Nickname hook was when i was learning about string hooking and instance pointers i think, so i didnt really consider it as a possibility. I will try nonetheless.


Again, thanks for the information!
About the instance parameter thingy, i used it to return the original method of get_NickName (if u are not talking about the NickName hook part).

I don't have much information about instance but i think instance is the memory address of the class? (correct me if im wrong)
but then there is using another update method from another class and passing its instance to a method of another class to hook it like my update method.
Okay, after thinking about it for a while it makes sense that u can return the new string, because the method isn't a void (method that doesn't return a value) which in this case it can return strings.