diff --git a/payload/game/net/MiscPacketHandler.S b/payload/game/net/MiscPacketHandler.S new file mode 100644 index 00000000..f9f385cf --- /dev/null +++ b/payload/game/net/MiscPacketHandler.S @@ -0,0 +1,8 @@ +#include + +PATCH_REPLACE_START(MiscPacketHandler_updateAsRacer, 0x4c) + // Original instruction sets RaceManager::m_canCountdownStart to true + // Nopped since mkw-server now handles this. Ideally, we'd rewrite in C++, + // but this function is complex and difficult to functionally match. + nop +PATCH_REPLACE_END(MiscPacketHandler_updateAsRacer, 0x4c) diff --git a/payload/game/net/MiscPacketHandler.cc b/payload/game/net/MiscPacketHandler.cc index 57f376d4..d116ad4c 100644 --- a/payload/game/net/MiscPacketHandler.cc +++ b/payload/game/net/MiscPacketHandler.cc @@ -1,3 +1,43 @@ #include "MiscPacketHandler.hh" -namespace Net {} // namespace Net +#include + +namespace Net { + +void MiscPacketHandler::setAckReady() { + m_readyAcked = true; +} + +void MiscPacketHandler::init() { + REPLACED(init)(); + + m_timeUntilReady = 240; + m_readyAcked = false; +} + +void MiscPacketHandler::updateReadyTimer() { + if (m_timeUntilReady > 0) { + m_timeUntilReady--; + } else { + MKWServer::sendReadyPacket(); + // Until we get an ack, try to send again in 5 frames. + m_timeUntilReady = 5; + } +} + +void MiscPacketHandler::updateAsRacer() { + if (!m_readyAcked) { + updateReadyTimer(); + } + + REPLACED(updateAsRacer)(); +} + +} // namespace Net + +void MiscPacketHandler_setAckReady() { + // Need to check for nullptr in case if an ack is unexpectedly sent outside of race scene. + if (auto *miscPacketHandler = Net::MiscPacketHandler::Instance()) { + miscPacketHandler->setAckReady(); + } +} diff --git a/payload/game/net/MiscPacketHandler.h b/payload/game/net/MiscPacketHandler.h new file mode 100644 index 00000000..52518820 --- /dev/null +++ b/payload/game/net/MiscPacketHandler.h @@ -0,0 +1,4 @@ +#pragma once + +// Defined here to expose to C code. +void MiscPacketHandler_setAckReady(); diff --git a/payload/game/net/MiscPacketHandler.hh b/payload/game/net/MiscPacketHandler.hh index ad258a5c..fa33f57a 100644 --- a/payload/game/net/MiscPacketHandler.hh +++ b/payload/game/net/MiscPacketHandler.hh @@ -3,10 +3,7 @@ #include #include "game/net/records/Event.hh" -#include "game/net/records/RH1.hh" -#include "game/net/records/RH2.hh" #include "game/net/records/RaceData.hh" -#include "game/net/records/Select.hh" extern "C" { #include @@ -16,16 +13,36 @@ namespace Net { // MiscPacketHandler runs exclusively in race scene. It handles everything netcode during a race. class MiscPacketHandler { +public: + // Added. sets m_readyAcked + void setAckReady(); + + static MiscPacketHandler *Instance() { + return s_instance; + } + private: + // 0x806532d8 + // Called exactly once at the start of a race to initialize members. + // Hooked to initialize mkw-server members. + REPLACE void init(); + void REPLACED(init)(); + // 0x80653728 // The main loop. Runs when m_isPrepared is true. This runs in online races // but also rival ghost races to update friend status void update(); + // Added. + // Updates m_timeUntilReady. Calls MKWServer::sendReadyPacket() when m_timeUntilReady is 0. + void updateReadyTimer(); + // 0x80654150 // Runs only when racing. Synchronizes race start, imports, exports, and processes records // specifically as a racer. - void updateAsRacer(); + // Hooked to handle mkw-server client synchronization. + REPLACE void updateAsRacer(); + void REPLACED(updateAsRacer)(); // 0x80654d08 // Exports relevant records as a racer (as opposed to a spectator) @@ -55,8 +72,14 @@ private: // Schecules a disconnect. Set if we're out of sync with other players. bool m_scheduleDisconnect; - // Padding - u8 _002[0x004 - 0x002]; + // Added, was padding. Time (in frames) until we're ready to start the countdown. + // This is initialized to 240 frames (4 seconds) at the start of each race to account + // for the intro camera pan, and decremented each frame. When the timer is 0, send + // a ready packet to mkw-server. + u8 m_timeUntilReady; + + // Added, was padding. Set when mkw-server has acked our ready packet. + bool m_readyAcked; // Bitfield set by RH1.raceSeed u32 m_aidsLoadedIntoRace; @@ -94,3 +117,7 @@ private: static_assert(sizeof(MiscPacketHandler) == 0x1c8); } // namespace Net + +// Exposed to C b/c UDP packets are handled in a C function (DWCi_GT2UnrecognizedMessageCallback). +// TODO: Rewrite in C++. +extern "C" void MiscPacketHandler_setAckReady(); diff --git a/payload/game/system/RaceManager.cc b/payload/game/system/RaceManager.cc index eb2c91c1..a76f64d2 100644 --- a/payload/game/system/RaceManager.cc +++ b/payload/game/system/RaceManager.cc @@ -60,3 +60,9 @@ void RaceManager::Player::calc() { } } // namespace System + +void RaceManager_startCountdown() { + if (auto *raceManager = System::RaceManager::Instance()) { + raceManager->startCountdown(); + } +} diff --git a/payload/game/system/RaceManager.h b/payload/game/system/RaceManager.h new file mode 100644 index 00000000..53097b9c --- /dev/null +++ b/payload/game/system/RaceManager.h @@ -0,0 +1,4 @@ +#pragma once + +// Defined here to expose to C code. +void RaceManager_startCountdown(); diff --git a/payload/game/system/RaceManager.hh b/payload/game/system/RaceManager.hh index 398d51fe..fa3dece8 100644 --- a/payload/game/system/RaceManager.hh +++ b/payload/game/system/RaceManager.hh @@ -46,6 +46,10 @@ public: return m_countdownTimer; } + void startCountdown() { + m_canCountdownStart = true; + } + static RaceManager *Instance() { return s_instance; } @@ -78,3 +82,8 @@ private: static_assert(sizeof(RaceManager) == 0x4c); } // namespace System + +// Exposed to C b/c UDP packets are handled in a C function (DWCi_GT2UnrecognizedMessageCallback). +// And we need to be able to start the countdown from UDP packet handling. +// TODO: Rewrite in C++. +extern "C" void RaceManager_startCountdown(); diff --git a/payload/revolution/dwc/DWCTransport.c b/payload/revolution/dwc/DWCTransport.c index c1df84c6..524256ac 100644 --- a/payload/revolution/dwc/DWCTransport.c +++ b/payload/revolution/dwc/DWCTransport.c @@ -1,7 +1,29 @@ #include "DWCTransport.h" +#include +#include + #include +#include + +#define START_MESSAGE "ST" +#define PING_MESSAGE "PI" +#define READY_ACK_MESSAGE "RA" + +bool isReadyAckPacket(const u8 *message, s32 len) { + return len == 2 && + strncmp((const char *)message, READY_ACK_MESSAGE, strlen(READY_ACK_MESSAGE)) == 0; +} + +bool isStartPacket(const u8 *message, s32 len) { + return len == 2 && strncmp((const char *)message, START_MESSAGE, strlen(START_MESSAGE)) == 0; +} + +bool isPingPacket(const u8 *message, s32 len) { + return len == 2 && strncmp((const char *)message, PING_MESSAGE, strlen(PING_MESSAGE)) == 0; +} + BOOL DWCi_GT2UnrecognizedMessageCallback(GT2Socket socket, u32 ip, u16 port, const u8 *message, s32 len) { if (message == NULL || len == 0) { @@ -14,6 +36,23 @@ BOOL DWCi_GT2UnrecognizedMessageCallback(GT2Socket socket, u32 ip, u16 port, con return GT2True; } + if (isReadyAckPacket(message, len)) { + SP_LOG("Received ready ACK packet from mkw-server!"); + MiscPacketHandler_setAckReady(); + return GT2True; + } + + if (isStartPacket(message, len)) { + SP_LOG("Received Start packet from mkw-server!"); + RaceManager_startCountdown(); + return GT2True; + } + + if (isPingPacket(message, len)) { + sendPong(); + return GT2True; + } + MKWServerPacketType messageType = message[0]; switch (messageType) { case MKW_SERVER_RACE_PACKET: diff --git a/payload/sp/net/mkw_server/MKWServer.cc b/payload/sp/net/mkw_server/MKWServer.cc index 5615cff8..fdae11a7 100644 --- a/payload/sp/net/mkw_server/MKWServer.cc +++ b/payload/sp/net/mkw_server/MKWServer.cc @@ -53,6 +53,20 @@ bool hasMKWServerAddress() { return s_mkwServerAddr.addr.addr != 0 && s_mkwServerAddr.port != 0; } +void sendReadyPacket() { + if (!hasMKWServerAddress()) { + SP_LOG("sendReady() can't send: Don't have mkw-server address!"); + return; + } + + u8 msg[2] = {0x52, 0x45}; // RE + + bool result = SOSendTo(s_dwcMatch->qrec->hbsock, &msg, sizeof(msg), 0, &s_mkwServerAddr); + if (!result) { + SP_LOG("Failed to send Race packet to mkw-server! SOSendTo failed!"); + } +} + bool trySendRacePacketToMKWServer(const void *data, u32 size) { if (!hasMKWServerAddress()) { return false; @@ -60,7 +74,7 @@ bool trySendRacePacketToMKWServer(const void *data, u32 size) { bool result = SOSendTo(s_dwcMatch->qrec->hbsock, data, size, 0, &s_mkwServerAddr); if (!result) { - SP_LOG("Failed to send to MKW Server!"); + SP_LOG("Failed to send Race packet to mkw-server!"); } return result; } @@ -94,6 +108,20 @@ bool handleSearchIdPacket(const u8 *packet, u32 size) { return true; } +void sendPong() { + if (!hasMKWServerAddress()) { + SP_LOG("sendPong() can't send: Don't have mkw-server address!"); + return; + } + + u8 msg[2] = {0x50, 0x4F}; // PO + + bool result = SOSendTo(s_dwcMatch->qrec->hbsock, &msg, sizeof(msg), 0, &s_mkwServerAddr); + if (!result) { + SP_LOG("Failed to send Pong packet to mkw-server! SOSendTo failed!"); + } +} + bool sendMessageToQR2(const u8 *data, u32 size) { SOSockAddrIn qr2Addr; qr2Addr.len = sizeof(SOSockAddrIn); diff --git a/payload/sp/net/mkw_server/MKWServer.h b/payload/sp/net/mkw_server/MKWServer.h index 973c7d59..168cad76 100644 --- a/payload/sp/net/mkw_server/MKWServer.h +++ b/payload/sp/net/mkw_server/MKWServer.h @@ -6,4 +6,6 @@ EXTERN_C bool verifySearchIdMagic(const u8 *packet, u32 size); -EXTERN_C bool handleSearchIdPacket(const u8 *packet, u32 size); \ No newline at end of file +EXTERN_C bool handleSearchIdPacket(const u8 *packet, u32 size); + +EXTERN_C void sendPong(); diff --git a/payload/sp/net/mkw_server/MKWServer.hh b/payload/sp/net/mkw_server/MKWServer.hh index 798a73f3..311723d4 100644 --- a/payload/sp/net/mkw_server/MKWServer.hh +++ b/payload/sp/net/mkw_server/MKWServer.hh @@ -19,11 +19,15 @@ u64 getSearchId(); bool hasMKWServerAddress(); +void sendReadyPacket(); + EXTERN_C bool trySendRacePacketToMKWServer(const void *data, u32 size); EXTERN_C bool verifySearchIdMagic(const u8 *packet, u32 size); EXTERN_C bool handleSearchIdPacket(const u8 *packet, u32 size); +EXTERN_C void sendPong(); + bool sendMessageToQR2(const u8 *data, u32 size); } // namespace MKWServer diff --git a/port.py b/port.py index 393935d2..cf54c4a0 100755 --- a/port.py +++ b/port.py @@ -478,6 +478,7 @@ def port(self, address): Chunk(0x809c1988, 0x809c198c, 0x809bd378), Chunk(0x809c19a0, 0x809c19bc, 0x809bd180), Chunk(0x809c1e38, 0x809c1e3c, 0x809bd508), + Chunk(0x809c1f50, 0x809c1f54, 0x809bd790), Chunk(0x809c20e0, 0x809c20e4, 0x809bd920), Chunk(0x809c2108, 0x809c210c, 0x809bd958), Chunk(0x809c2118, 0x809c211c, 0x809bd940), @@ -536,6 +537,7 @@ def port(self, address): Chunk(0x809c18f8, 0x809c18fc, 0x809c0958), Chunk(0x809c1988, 0x809c198c, 0x809c09e8), Chunk(0x809c19a0, 0x809c19bc, 0x809c0a00), + Chunk(0x809c1f50, 0x809c1f54, 0x809c0fb0), Chunk(0x809c20e0, 0x809c20e4, 0x809c1140), Chunk(0x809c2108, 0x809c210c, 0x809c1168), Chunk(0x809c2118, 0x809c211c, 0x809c1178), @@ -635,6 +637,7 @@ def port(self, address): Chunk(0x809c1988, 0x809c198c, 0x809affc8), Chunk(0x809c19a0, 0x809c19bc, 0x809affe0), Chunk(0x809c1e38, 0x809c1e3c, 0x809b0478), + Chunk(0x809c1f50, 0x809c1f54, 0x809b0590), Chunk(0x809c20e0, 0x809c20e4, 0x809b0720), Chunk(0x809c2108, 0x809c210c, 0x809b0748), Chunk(0x809c2118, 0x809c211c, 0x809b0758), diff --git a/symbols.txt b/symbols.txt index 87b95f5f..4855d90f 100644 --- a/symbols.txt +++ b/symbols.txt @@ -528,7 +528,9 @@ 0x8063e924 _ZN2UI24LayoutUIControlScaleFade5vf_28Ev 0x80643168 onInit 0x80649590 DCPageCalc +0x806532d8 _ZN3Net17MiscPacketHandler4initEv 0x80654038 _ZNK3Net17MiscPacketHandler16isEveryoneInRaceEv +0x80654150 MiscPacketHandler_updateAsRacer 0x80654150 _ZN3Net17MiscPacketHandler13updateAsRacerEv 0x806554a0 _ZN3Net17MiscPacketHandler21processRecvRH1RecordsEv 0x806554a0 MiscPacketHandler_handleLaggings @@ -588,6 +590,7 @@ 0x809bd740 _ZN6System9RootScene10s_instanceE 0x809c18f8 _ZN4Kart17KartObjectManager10s_instanceE 0x809c1e38 _ZN2UI14SectionManager10s_instanceE +0x809c1f50 _ZN3Net17MiscPacketHandler10s_instanceE 0x809c20d8 _ZN3Net10NetManager10s_instanceE 0x809c20e0 _ZN3Net11RoomHandler10s_instanceE 0x809c2100 _ZN3Net13SelectHandler10s_instanceE