kinnay / SMB35

An open source server for Super Mario Bros. 35
GNU Affero General Public License v3.0
180 stars 24 forks source link

Not an issue with SMB35, but rather Splatoon 2's Global Testfire ~ See below #18

Closed Sheldon10095 closed 1 year ago

Sheldon10095 commented 2 years ago

Hey! This isn't exactly an issue with Super Mario Bros. 35

I was trying to adapt this custom server to the Splatoon 2 Global Testfire, and I managed to get the console to connect to my PC which is hosting the server, but then I realized that it was just giving me error 2306-0102, (only when I had the server up of course). And yes, I have indeed changed the access key to match that of the testfire. Also, I've placed an image of the error screen at the bottom of this post.

From what I can tell, there are a few needed funcs that aren't yet implemented... Is there any chance you could perhaps implement a few of them? I tried making some adjustments but I had no luck. I'm thinking its just a few authentication funcs. Maybe matchmaking eventually, but I'm not worried about that yet haha. I'm assuming that I should probably also mention that handle_validate_and_request_ticket_with_custom_data is where it's failing.

If you need more info, lemme know!

Global Testfire Patches

Here are the patches that I've made ~~ Feel free to test them if you'd like.

//Set Custom Server URL @enabled 024FC706 "192.168.4.156:20000"

//nn::nex::JobAcquireNsaIdToken::StepWaitingForGetGameAuthentication(void)+9C -> Replace GetResult call with MOV X0, XZR @enabled 01132844 E0031FAA

//force branch at "CBZ W8, loc_7101132844" @enabled 01132924 08000014


* Skyline hook to disable SSL & log calls to QLOG (I now realize that there is a thing you can use to disable it system-wide, but I'm sharing this anyways)
```cpp
typedef u32 CURLcode;
typedef void CURL;
enum CURLoption { };
CURLcode (*original_curl_easy_perform)(CURL *curl);
CURLcode curl_easy_perform_hook(CURL *curl)
{
    u64 curl_easy_setopt_addr;
    nn::ro::LookupSymbol(&curl_easy_setopt_addr, "curl_easy_setopt");

    skyline::TcpLogger::SendRaw("curl_easy_perform() was called!\n");
    // yes I'm aware this is disgusting.
    ((CURLcode (*)(CURL *curl, CURLoption curlopt, long val))curl_easy_setopt_addr)(curl, (CURLoption)64, 0L);
    return original_curl_easy_perform(curl);
}
Result (*original_nnNexQLOG_func)(int logLevel, const char* message, ...);
Result nnNexQLOG_hook(int logLevel, const char* message, ...)
{
    va_list argptr;
    va_start(argptr, message);
    char s[0x100];
    int maxlen = 255;
    Result result = vsnprintf(s, maxlen, message, argptr);
    va_end(argptr);

    skyline::TcpLogger::SendRawFormat("[Log Level %d]: %s\n", logLevel, s);
    return original_nnNexQLOG_func(logLevel, s);
}

int main() {
    u64 curl_easy_perform_addr;
    nn::ro::LookupSymbol(&curl_easy_perform_addr, "curl_easy_perform");
    A64HookFunction(
        reinterpret_cast<void*>(curl_easy_perform_addr),
        reinterpret_cast<void*>(curl_easy_perform_hook),
        (void**) &original_curl_easy_perform
    );

    u64 nnNexQLOG_addr;
    nn::ro::LookupSymbol(&nnNexQLOG_addr, "_ZN2nn3nex5_QLOGENS0_8EventLog8LogLevelEPKcz");
    A64HookFunction(
        reinterpret_cast<void*>(nnNexQLOG_addr),
        reinterpret_cast<void*>(nnNexQLOG_hook),
        (void**) &original_nnNexQLOG_func
    );
}

Error Screen

2021112416225400-1AB131B6E6571375B79964211BB3F5AE

kinnay commented 2 years ago

You probably need to implement ValidateAndRequestTicketWithCustomData. It us very similar to ValidateAndRequestTicketWithParam (which is already implemented here for SMB35). The only difference is the structure of the request and response parameters.

Can you implement this on your own?

Sheldon10095 commented 2 years ago

You probably need to implement ValidateAndRequestTicketWithCustomData. It us very similar to ValidateAndRequestTicketWithParam (which is already implemented here for SMB35). The only difference is the structure of the request and response parameters.

Can you implement this on your own?

I'm currently trying, but I'm not sure I understand the errors I'm getting at the moment...

Sheldon10095 commented 2 years ago

Can you implement this on your own?

Alright. I've got this added so far (code below), but I think I'm doing something wrong, since the game calls for RequestTicket afterwards. According to your docs, I don't think that should happen unless I'm sending it a bad ticket? I have a feeling that it's not too bad of an error. Do you by chance see something wrong with this? Perhaps I'm forgetting to encrypt something?

        async def validate_and_request_ticket_with_custom_data(self, client, strUserName, oExtraData):
        pid = next(self.pid)
        key = secrets.token_bytes(16)

        #Connection Data thing
        url = common.StationURL(
            scheme="prudps", address="0.0.0.1", port=1,
            PID = SERVER_PID, CID = 1, type = 2,
            sid = 2, stream = 10
        )
        conn_data = authentication.RVConnectionData()
        conn_data.main_station = url
        conn_data.special_protocols = []
        conn_data.special_station = common.StationURL()

        result = rmc.RMCResponse()
        result.result = common.Result().success()
        result.pid = pid
        result.ticket = self.generate_ticket(pid, SERVER_PID, key, SERVER_KEY)
        result.connection_data = conn_data
        result.server_time = common.DateTime.now()
        result.server_name = "S2GT - TEST"
        result.source_key = key.hex()
        return result

    async def request_ticket(self, user_pid, server_pid, pSourceKey):
        pid = next(self.pid)
        key = secrets.token_bytes(16)

        ticket = self.generate_ticket(pid, SERVER_PID, key, SERVER_KEY)

        result = rmc.RMCResponse()
        result.result = common.Result()
        result.ticket = ticket
        result.key = str(key)
        return result
kinnay commented 2 years ago

I'm not sure why it calls RequestTicket, because your validate_and_request_ticket_with_custom_data method looks fine to me. The server_time field doesn't exist, but that shouldn't cause any problems.

Your request_ticket method is wrong. But even if it were correct it wouldn't work because the RequestTicket method should never have been called.

Sheldon10095 commented 2 years ago

I should note that it doesn't seem like the console is sending any token to the server. It's just totally blank if I try to log it... Perhaps that's related?

kinnay commented 2 years ago

Probably not. The token is blank because the ips patch disables NSO authentication.

Sheldon10095 commented 2 years ago

Hmm alright... I'll try to figure out which function is causing the error then.

Also I forgot to mention, after implementing those two functions, the error the shown on the switch has changed to 2306-0502 again, and it displays the following message:

A server communication error has occurred. Please try again later.

My _QLOG hook is sending these to the logger as well. Not much info aha. Logs:

[Log Level 2]: It took 22 msec for nn::account::EnsureNetworkServiceAccountIdTokenCacheAsync()
[Log Level 2]: It took 239 msec for RendezVous::Login()
kinnay commented 2 years ago

Wait sorry.

the ips patch disables NSO authentication.

This is wrong. You are not using my ips patch of course. I forgot we are talking about Splatoon 2 instead of SMB35 😅

Sheldon10095 commented 2 years ago

Hold on a second.... It's giving me a different error now, If I change the "prudps" to "prudp" here:

url = common.StationURL(
    scheme="prudp", address="0.0.0.1", port=1,
    PID = SERVER_PID, CID = 1, type = 2,
    sid = 2, stream = 10
)

The server is raising buffer overflow errors, though it isn't really telling me where exactly the root cause lies... 🤔

Sheldon10095 commented 2 years ago

Ok- from what I've tested, when it's set to use "prudps", it calls RequestTicket. But when it's set to just "prudp", it's raising a BufferOverflow exception in process_login_request in when it tries to read data from the packet via a call to stream.buffer()

After having it print out the data in the packet, I've noticed that there's no data there. Basically it's trying to parse data that doesn't exist? Would you know what could be causing this? I can follow up with more info if needed.

kinnay commented 2 years ago

It probably tries to log in anonymously if you use prudp instead of prudps, but the server only accepts connection requests with credentials. If you remove key=SERVER_KEY from the line with rmc.serve_prudp the server will only accept anonymous connections instead, which might work in your situation, but it is not the correct way to fix this.

Sheldon10095 commented 2 years ago

I think it has to with the key in validate_and_request_ticket_with_custom_data.

key = secrets.token_bytes(16)

If I change that to something like key = secrets.token_bytes(4) , then I get error 2306-0304, which is RendezVous::InvalidPassword. When this happens, it doesn't call RequestTicket. I have no idea. After all, it is only calling RequestTicket since validate_and_request_ticket_with_custom_data is incorrect. I just cannot figure out how to make it correct.


If you remove key=SERVER_KEY from the line with rmc.serve_prudp the server will only accept anonymous connections instead, which might work in your situation, but it is not the correct way to fix this.

That allowed me to pass the tutorial, but it failed at matchmaking of course because there was no PID sent. I hope I'm not asking too much 😅

kinnay commented 2 years ago

I think it has to with the key in validate_and_request_ticket_with_custom_data.

I don't think so. The error code is probably misleading. For special user accounts, such as Administrator, the key is derived from their password, but normal user accounts don't have a password. Instead, the server generates a random key and sends it to the client (this is the source_key).

If you change the key size, the client probably fails to decrypt the ticket. It probably assumes that the key was derived from an incorrect password, because the decryption of the ticket should never fail for accounts that do not have a password.

it failed at matchmaking of course because there was no PID sent.

Yeah, that's because the client is connecting anonymously now :slightly_smiling_face:

So you either have to rewrite the match making server to work without PIDs, or fix your authentication server and use prudps.

I still don't see what's wrong with your ValidateAndRequestTicketWithCustomData implementation... sorry I can't help.

ghost commented 2 years ago

Oh and I was also trying to do Splatoon 2 but not testfire and the server works when i start it locally but when i try to host it (on heroku) it has issues when i try to connect to it. Do you know what could be the problem or maybe you can recommend something else for hosting the server image0

kinnay commented 2 years ago

Looks like your server is using SSL but the client is not. So, either remove SSL from your server or make sure that the client uses SSL.

kinnay commented 2 years ago

Wait sorry I misread your comment.

I don't have any experience with heroku, but I would guess that there is some kind of load balancer or proxy in front of your server that forwards traffic to your Python server without TLS. It probably works if you disable TLS on your Python server.

ghost commented 2 years ago

Thanks it worked, but now there’s a new problem. When someone tries to create a room, it succeeds for some people like me but for others it is softlocked on the joining screen after which they for some reason call unregister gathering. This doesn’t happen for me, but when others try to join me they seem to send the join event (the timer in the lobby increases which happens when a person joins) and get added to the participants of it but they don’t join it on their side and get a game connection error (no error code). However they remain in the participants and when they try to join the lobby again they get RendezVous::AlreadyParticipatedGathering. I don’t know if this is game specific but if you could say anything about that it would be really helpful. DDB72510-8D38-4FEB-845F-3C1E648D747D 04F0B414-88D7-42CB-B837-CB5F66971BEB

kinnay commented 2 years ago

That is probably a problem in your match making implementation. I don't think I can help you with that, sorry. Also, Splatoon 2 doesn't use libeagle, so you can probably remove that from your code.

ghost commented 2 years ago

I’ve removed eagle and I’ve also observed that the error that prevented people from creating a lobby was connected to nat traversal. The game calls report_nat_properties and then secureserver send_report, where it gives a buffer overflow error when loading qbuffer from input stream (from the default lib’s handle_send_report). How can I implement changing nat properties when the game reports them to me (which it does when joining a match) and could the send_report be connected with the other disconnect which always happens when someone tries to join me? Also the other dc which happens when no one is using additional network settings happens because after the auto matchmake task finishes enl::PeerManagerCommon::isConnected returns 0 and goes to the error sequence. I don’t really have much experience with servers so some of the things I said might be stupid so sorry about that 8859E97C-A634-440C-813A-492660B2063D

ghost commented 2 years ago

Actually I figured out the issue. The game is trying to call replace_url, but calls send_report instead. How do I inform the new station url

Sheldon10095 commented 2 years ago

I've also been trying with the regular Splatoon 2 and progress has been a bit easier, but I'm stuck on how I should set up the NAT server because it uses a different port (being 10025 or 10125 like you've mentioned in your docs). I've been getting the error 2618-0201 aka ResultNatCheckFailed, which makes sense considering the game points to both nncs1-lp1.n.n.srv.nintendo.net and nncs2-lp1.n.n.srv.nintendo.net. So I've patched it to just point it to my local computer's IP while I test it. I'm just not sure how I should have the NATTraversalServer running compared to the other ones... any chance you could point me in the right direction?

kinnay commented 2 years ago

NAT traversal is not the same as NAT check. The nncs servers use a completely different protocol and are not implemented by my Python package. You either need to implement them on your own or use Nintendo's servers.

You probably need to implement NATTraversalServer as well after solving your NAT check problems, but NATTraversalServer will not help you with NAT check.