From a40046c0bc7f580e540feeaac623f54e477caab8 Mon Sep 17 00:00:00 2001 From: MikeIsAStar <99037623+MikeIsAStar@users.noreply.github.com> Date: Thu, 23 May 2024 00:30:30 -0400 Subject: [PATCH] [MKW] Prevent clients from stalling rooms Closes issue #23. --- payload/import/mkw/net/net.hpp | 64 +++++++++++--- payload/import/mkw/net/selectHandler.hpp | 107 +++++++++++++++++++---- payload/import/mkw/net/userHandler.hpp | 8 ++ payload/wwfcFeature.cpp | 57 ++++++++++++ payload/wwfcGPReport.hpp | 2 +- payload/wwfcSecurity.cpp | 74 ++++++++-------- 6 files changed, 246 insertions(+), 66 deletions(-) diff --git a/payload/import/mkw/net/net.hpp b/payload/import/mkw/net/net.hpp index f56d6c6..6f58cd7 100644 --- a/payload/import/mkw/net/net.hpp +++ b/payload/import/mkw/net/net.hpp @@ -1,7 +1,9 @@ #pragma once +#include "import/dwc.h" #include "import/mkw/hostSystem.hpp" #include "import/mkw/system/system.hpp" +#include namespace mkw::Net { @@ -71,14 +73,13 @@ class NetController FriendContinentalBattle = 10, }; - struct ConnectionInfo { - /* 0x00 */ u8 _00[0x21 - 0x00]; - /* 0x21 */ u8 myAid; - /* 0x22 */ u8 serverAid; - /* 0x23 */ u8 _23[0x58 - 0x23]; - }; + void sendRacePacket() + { + LONGCALL void sendRacePacket(NetController * netController) + AT(RMCXD_PORT(0x80657E30, 0x806539A8, 0x8065749C, 0x80646148)); - static_assert(sizeof(ConnectionInfo) == 0x58); + sendRacePacket(this); + } void processRacePacket(u32 playerAid, RacePacket* racePacket, u32 packetSize) @@ -91,14 +92,14 @@ class NetController processRacePacket(this, playerAid, racePacket, packetSize); } - const ConnectionInfo& currentConnectionInfo() const + JoinType joinType() const { - return m_connectionInfo[m_currentConnectionInfoIndex]; + return m_joinType; } - JoinType joinType() const + u32 availableAids() const { - return m_joinType; + return currentConnectionInfo().availableAids; } u8 myAid() const @@ -172,18 +173,57 @@ class NetController return inVanillaMatch(); } + void reportAndKick(const char* key, u32 playerAid) const + { + using namespace DWC; + + DWCiNodeInfo* nodeInfo = DWCi_NodeInfoList_GetNodeInfoForAid(playerAid); + if (nodeInfo) { + wwfc::GPReport::ReportU32(key, nodeInfo->profileId); + } + + if (amITheServer()) { + DWC_CloseConnectionHard(playerAid); + } + } + static NetController* Instance() { return s_instance; } private: + struct ConnectionInfo { + /* 0x00 */ u8 _00[0x10 - 0x00]; + /* 0x10 */ u32 availableAids; + /* 0x14 */ u8 _14[0x21 - 0x14]; + /* 0x21 */ u8 myAid; + /* 0x22 */ u8 serverAid; + /* 0x23 */ u8 _23[0x58 - 0x23]; + }; + + static_assert(sizeof(ConnectionInfo) == 0x58); + + const ConnectionInfo& currentConnectionInfo() const + { + return m_connectionInfo[m_currentConnectionInfoIndex]; + } + /* 0x0000 */ u8 _0000[0x0038 - 0x0000]; /* 0x0038 */ ConnectionInfo m_connectionInfo[2]; /* 0x00E8 */ JoinType m_joinType; /* 0x00EC */ u8 _00EC[0x291C - 0x00EC]; /* 0x291C */ int m_currentConnectionInfoIndex; - /* 0x2920 */ u8 _2920[0x29C8 - 0x2920]; + /* 0x2920 */ u8 _2920[0x29B0 - 0x2920]; + +public: + /* 0x29B0 */ u32 _29B0; + /* 0x29B4 */ u32 _29B4; + /* 0x29B8 */ u32 _29B8; + /* 0x29BC */ u32 _29BC; + +private: + /* 0x29C0 */ u8 _29C0[0x29C8 - 0x29C0]; static NetController* s_instance AT(RMCXD_PORT(0x809C20D8, 0x809BD918, 0x809C1138, 0x809B0718)); diff --git a/payload/import/mkw/net/selectHandler.hpp b/payload/import/mkw/net/selectHandler.hpp index 6f01a03..6c1d3a3 100644 --- a/payload/import/mkw/net/selectHandler.hpp +++ b/payload/import/mkw/net/selectHandler.hpp @@ -1,6 +1,7 @@ #pragma once #include "import/mkw/util.hpp" +#include "net.hpp" namespace mkw::Net { @@ -12,23 +13,16 @@ class SelectHandler { public: struct __attribute__((packed)) Packet { - enum class Character : u8 { - NotSelected = 0x30, - }; - - enum class Vehicle : u8 { - NotSelected = 0x24, - }; - - enum class CourseVote : u8 { - NotSelected = 0x43, - Random = 0xFF, - }; - enum class SelectedCourse : u8 { NotSelected = 0xFF, }; + enum class Phase : u8 { + Preparing = 0, + Voting = 1, + Lottery = 2, + }; + enum class EngineClass : u8 { e100cc = 1, e150cc = 2, @@ -36,6 +30,19 @@ class SelectHandler }; struct Player { + enum class Character : u8 { + NotSelected = 0x30, + }; + + enum class Vehicle : u8 { + NotSelected = 0x24, + }; + + enum class CourseVote : u8 { + NotSelected = 0x43, + Random = 0xFF, + }; + /* 0x00 */ u8 _00[0x04 - 0x00]; /* 0x04 */ Character character; /* 0x05 */ Vehicle vehicle; @@ -49,7 +56,8 @@ class SelectHandler /* 0x10 */ Player player[2]; /* 0x20 */ u8 _20[0x34 - 0x20]; /* 0x34 */ SelectedCourse selectedCourse; - /* 0x35 */ u8 _35[0x37 - 0x35]; + /* 0x35 */ Phase phase; + /* 0x36 */ u8 _36; /* 0x37 */ EngineClass engineClass; }; @@ -72,15 +80,84 @@ class SelectHandler } } + // https://github.com/CLF78/OpenPayload/blob/master/payload/wiimmfi/RoomStall.cpp + void processKicks() + { + NetController* netController = NetController::Instance(); + if (!netController->amITheServer()) { + return; + } + + if (s_kickTimerFrames < s_kickTimerThresholdFrames) { + s_kickTimerFrames++; + + return; + } + s_kickTimerFrames = 0; + + u32 availableAids = + netController->availableAids() & ~(1 << netController->myAid()); + + u32 aidsStillLoading = 0x00000000; + switch (m_sendPacket.phase) { + case Packet::Phase::Preparing: { + aidsStillLoading = (~m_aidsWithNewSelectPacket) & availableAids; + if (aidsStillLoading) { + break; + } + + aidsStillLoading = (~m_aidsWithNewMatchSettings) & availableAids; + break; + } + case Packet::Phase::Voting: { + aidsStillLoading = (~m_aidsWithVote) & availableAids; + if (aidsStillLoading) { + break; + } + + aidsStillLoading = (~m_aidsWithVoteData) & availableAids; + break; + } + default: { + return; + } + } + + // Support modifications that allow for clients to be connected to more + // than 11 peers at once. + for (u32 n = 0; n < sizeof(aidsStillLoading) * 8; n++) { + if (((aidsStillLoading >> n) & 1) == 0) { + continue; + } + + netController->reportAndKick("mkw_room_stall", n); + } + } + static SelectHandler* Instance() { return s_instance; } + static void ResetKickTimer() + { + s_kickTimerFrames = 0; + } + private: /* 0x000 */ u8 _000[0x008 - 0x000]; /* 0x008 */ Packet m_sendPacket; - /* 0x040 */ u8 _040[0x3F8 - 0x040]; + /* 0x040 */ u8 _040[0x3E0 - 0x040]; + /* 0x3E0 */ u32 m_aidsWithNewSelectPacket; + /* 0x3E4 */ u8 _3E4[0x3E8 - 0x3E4]; + /* 0x3E8 */ u32 m_aidsWithNewMatchSettings; + /* 0x3EC */ u32 m_aidsWithVoteData; + /* 0x3F0 */ u32 m_aidsWithVote; + /* 0x3F4 */ u8 _3F4[0x3F8 - 0x3F4]; + + static u32 s_kickTimerFrames; + + static constexpr u32 s_kickTimerThresholdFrames = 90 * 60; static SelectHandler* s_instance AT(RMCXD_PORT(0x809C2100, 0x809BD930, 0x809C1160, 0x809B0740)); diff --git a/payload/import/mkw/net/userHandler.hpp b/payload/import/mkw/net/userHandler.hpp index 64a74d5..cdbb73a 100644 --- a/payload/import/mkw/net/userHandler.hpp +++ b/payload/import/mkw/net/userHandler.hpp @@ -97,6 +97,14 @@ class UserHandler static_assert(sizeof(Packet) == 0xC0); + void calc() + { + LONGCALL void calc(UserHandler * userHandler) + AT(RMCXD_PORT(0x806629C0, 0x806608DC, 0x8066202C, 0x80650CD8)); + + calc(this); + } + static UserHandler* Instance() { return s_instance; diff --git a/payload/wwfcFeature.cpp b/payload/wwfcFeature.cpp index d3932d4..5973481 100644 --- a/payload/wwfcFeature.cpp +++ b/payload/wwfcFeature.cpp @@ -1,11 +1,23 @@ #include "import/mkw/net/itemHandler.hpp" #include "import/mkw/net/selectHandler.hpp" +#include "import/mkw/net/userHandler.hpp" #include "import/mkw/ui/page/friendRoomPage.hpp" #include "import/mkw/ui/page/wifiFriendMenuPage.hpp" #include "import/mkw/ui/page/wifiMenuPage.hpp" #include "wwfcPatch.hpp" #include +namespace mkw::Net +{ + +#if RMC + +u32 SelectHandler::s_kickTimerFrames = 0; + +#endif + +} // namespace mkw::Net + namespace mkw::UI { @@ -124,6 +136,28 @@ WWFC_DEFINE_PATCH = { ), }; +// Prevent clients from stalling rooms +WWFC_DEFINE_PATCH = { + Patch::CallWithCTR( // + WWFC_PATCH_LEVEL_FEATURE, // + RMCXD_PORT(0x806579A0, 0x80653518, 0x8065700C, 0x80645CB8), // + // clang-format off + [](mkw::Net::NetController* netController) -> void { + using namespace mkw::Net; + + netController->sendRacePacket(); + + UserHandler::Instance()->calc(); + + SelectHandler* selectHandler = SelectHandler::Instance(); + if (selectHandler) { + selectHandler->processKicks(); + } + } + // clang-format on + ), +}; + // Fix a bug that leads to the rejection of one's item request without // justification WWFC_DEFINE_PATCH = { @@ -139,6 +173,29 @@ WWFC_DEFINE_PATCH = { ), }; +// Reset the timer that is used to detect if clients are stalling the room +WWFC_DEFINE_PATCH = { + Patch::CallWithCTR( // + WWFC_PATCH_LEVEL_FEATURE, // + RMCXD_PORT(0x8065FF34, 0x80657FF8, 0x8065F5A0, 0x8064E24C), // + // clang-format off + [](mkw::Net::SelectHandler* selectHandler, + mkw::Net::NetController* netController) -> mkw::Net::SelectHandler* { + using namespace mkw::Net; + + netController->_29B0 = 0; + netController->_29B4 = 0; + netController->_29B8 = 0; + netController->_29BC = 0; + + SelectHandler::ResetKickTimer(); + + return selectHandler; + } + // clang-format on + ), +}; + // Remove the 100cc engine class from vanilla matches WWFC_DEFINE_PATCH = { Patch::CallWithCTR( // diff --git a/payload/wwfcGPReport.hpp b/payload/wwfcGPReport.hpp index 2f7765a..d95ef27 100644 --- a/payload/wwfcGPReport.hpp +++ b/payload/wwfcGPReport.hpp @@ -14,4 +14,4 @@ void ReportB64Encode(const char* key, const void* data, size_t dataSize); #endif -} // namespace wwfc::GPReport \ No newline at end of file +} // namespace wwfc::GPReport diff --git a/payload/wwfcSecurity.cpp b/payload/wwfcSecurity.cpp index 7acbd43..dd0fbfa 100644 --- a/payload/wwfcSecurity.cpp +++ b/payload/wwfcSecurity.cpp @@ -1,14 +1,11 @@ -#include "import/dwc.h" #include "import/mkw/net/eventHandler.hpp" #include "import/mkw/net/itemHandler.hpp" #include "import/mkw/net/matchHeaderHandler.hpp" -#include "import/mkw/net/net.hpp" #include "import/mkw/net/roomHandler.hpp" #include "import/mkw/net/selectHandler.hpp" #include "import/mkw/net/userHandler.hpp" #include "import/mkw/registry.hpp" #include "import/mkw/system/system.hpp" -#include "wwfcGPReport.hpp" #include "wwfcPatch.hpp" namespace wwfc::Security @@ -355,13 +352,14 @@ IsRoomSelectPacketDataValid(const void* packet, u8 packetSize, u8 playerAid) n < ARRAY_ELEMENT_COUNT(SelectHandler::Packet::player); n++) { SelectHandler::Packet::Player player = selectPacket->player[n]; - SelectHandler::Packet::Character selectedCharacter = + SelectHandler::Packet::Player::Character selectedCharacter = player.character; - SelectHandler::Packet::Vehicle selectedVehicle = player.vehicle; + SelectHandler::Packet::Player::Vehicle selectedVehicle = + player.vehicle; if (selectedCharacter != - SelectHandler::Packet::Character::NotSelected || + SelectHandler::Packet::Player::Character::NotSelected || selectedVehicle != - SelectHandler::Packet::Vehicle::NotSelected) { + SelectHandler::Packet::Player::Vehicle::NotSelected) { Character character = static_cast(selectedCharacter); Vehicle vehicle = static_cast(selectedVehicle); if (scenario->isOnlineVersusRace()) { @@ -375,10 +373,12 @@ IsRoomSelectPacketDataValid(const void* packet, u8 packetSize, u8 playerAid) } } - SelectHandler::Packet::CourseVote courseVote = + SelectHandler::Packet::Player::CourseVote courseVote = selectPacket->player[n].courseVote; - if (courseVote == SelectHandler::Packet::CourseVote::NotSelected || - courseVote == SelectHandler::Packet::CourseVote::Random) { + if (courseVote == + SelectHandler::Packet::Player::CourseVote::NotSelected || + courseVote == + SelectHandler::Packet::Player::CourseVote::Random) { continue; } Course course = static_cast(courseVote); @@ -592,41 +592,39 @@ WWFC_DEFINE_PATCH = {Patch::BranchWithCTR( // RMCXD_PORT(0x80658604, 0x8065417C, 0x80657C70, 0x8064691C), // [](NetController* netController, RacePacket* racePacket, u32 packetSize, u32 _, u8 playerAid) -> void { - if (packetSize >= sizeof(RacePacket)) { - LONGCALL u32 NETCalcCRC32( // - const void* data, u32 size - ) AT(RMCXD_PORT(0x801D1CA0, 0x801D1C00, 0x801D1BC0, 0x801D1FFC)); - - u32 savedChecksum = racePacket->checksum; - racePacket->checksum = 0; - u32 realChecksum = NETCalcCRC32(racePacket, packetSize); - racePacket->checksum = savedChecksum; - - if (realChecksum != savedChecksum) { - LOG_WARN_FMT( - "Invalid Race packet from aid %u (checksum mismatch)", playerAid - ); - return; - } + if (packetSize < sizeof(RacePacket)) { + LOG_WARN_FMT( + "Invalid Race packet from aid %u (insufficient size)", playerAid + ); + + netController->reportAndKick("mkw_malicious_packet", playerAid); + + return; } - if (!IsRacePacketValid(racePacket, packetSize, playerAid)) { - using namespace DWC; + LONGCALL u32 NETCalcCRC32( // + const void* data, u32 size + ) AT(RMCXD_PORT(0x801D1CA0, 0x801D1C00, 0x801D1BC0, 0x801D1FFC)); + u32 savedChecksum = racePacket->checksum; + racePacket->checksum = 0; + u32 realChecksum = NETCalcCRC32(racePacket, packetSize); + racePacket->checksum = savedChecksum; + + if (realChecksum != savedChecksum) { LOG_WARN_FMT( - "Invalid Race packet from aid %u (malicious packet)", playerAid + "Invalid Race packet from aid %u (checksum mismatch)", playerAid ); - DWCiNodeInfo* nodeInfo = DWCi_NodeInfoList_GetNodeInfoForAid(playerAid); - if (nodeInfo) { - wwfc::GPReport::ReportU32( - "mkw_malicious_packet", nodeInfo->profileId - ); - } + return; + } - if (netController->amITheServer()) { - DWC_CloseConnectionHard(playerAid); - } + if (!IsRacePacketValid(racePacket, packetSize, playerAid)) { + LOG_WARN_FMT( + "Invalid Race packet from aid %u (malicious packet)", playerAid + ); + + netController->reportAndKick("mkw_malicious_packet", playerAid); return; }