From 92f78b4e529fd7b19960d52e5fdbf12fa7ee62a3 Mon Sep 17 00:00:00 2001 From: Narr the Reg <5944268+german77@users.noreply.github.com> Date: Mon, 16 Feb 2026 17:47:51 -0600 Subject: [PATCH] ModeBallon: Implement `RankingLocalFunction` (#906) --- data/file_list.yml | 20 ++-- src/ModeBalloon/NexRankingUploader.h | 4 +- src/ModeBalloon/RankingCategory.h | 24 ++++ src/ModeBalloon/RankingLocalFunction.cpp | 141 +++++++++++++++++++++++ src/ModeBalloon/RankingLocalFunction.h | 5 +- src/System/NetworkUploadFlag.h | 2 +- src/Util/SequentialUtil.cpp | 2 +- src/Util/SequentialUtil.h | 2 +- 8 files changed, 183 insertions(+), 17 deletions(-) create mode 100644 src/ModeBalloon/RankingCategory.h create mode 100644 src/ModeBalloon/RankingLocalFunction.cpp diff --git a/data/file_list.yml b/data/file_list.yml index 6731c316..c937b70b 100644 --- a/data/file_list.yml +++ b/data/file_list.yml @@ -94280,35 +94280,35 @@ ModeBalloon/RankingLocalFunction.h: - offset: 0x35c158 size: 16 label: _ZN20RankingLocalFunction27isValidCourceIdYukimaruRaceEi - status: NotDecompiled + status: Matching - offset: 0x35c168 size: 32 label: _ZN20RankingLocalFunction30getRankingCategoryYukimaruRaceEi - status: NotDecompiled + status: Matching - offset: 0x35c188 size: 40 label: _ZN20RankingLocalFunction25isValidWorldIdRaceManRaceEi - status: NotDecompiled + status: Matching - offset: 0x35c1b0 size: 32 label: _ZN20RankingLocalFunction29getRankingCategoryRaceManRaceEi - status: NotDecompiled + status: Matching - offset: 0x35c1d0 size: 16 label: _ZN20RankingLocalFunction29isRankingCategoryYukimaruRaceE15RankingCategory - status: NotDecompiled + status: Matching - offset: 0x35c1e0 size: 52 label: _ZN20RankingLocalFunction28isRankingCategoryRaceManRaceE15RankingCategory - status: NotDecompiled + status: Matching - offset: 0x35c214 size: 56 label: _ZN20RankingLocalFunction21getRaceManRaceWorldIdE15RankingCategory - status: NotDecompiled + status: Matching - offset: 0x35c24c size: 84 label: _ZN20RankingLocalFunction21isRankingCategoryRaceE15RankingCategory - status: NotDecompiled + status: Matching - offset: 0x35c2a0 size: 100 label: _ZN20RankingLocalFunction26tryGetRankingCategoryIndexEPi15RankingCategoryRKN4sead8ObjArrayIS1_EE @@ -94320,11 +94320,11 @@ ModeBalloon/RankingLocalFunction.h: - offset: 0x35c354 size: 232 label: _ZN20RankingLocalFunction35findRankingCategoryByRaceRecordNameEPKc - status: NotDecompiled + status: Matching - offset: 0x35c43c size: 136 label: _ZN20RankingLocalFunction24isRankingUploadableScoreEj15RankingCategory - status: NotDecompiled + status: Matching - offset: 0x35c4c4 size: 36 label: _ZN20RankingDataOneRecord5clearEv diff --git a/src/ModeBalloon/NexRankingUploader.h b/src/ModeBalloon/NexRankingUploader.h index a5327b39..46af268a 100644 --- a/src/ModeBalloon/NexRankingUploader.h +++ b/src/ModeBalloon/NexRankingUploader.h @@ -7,6 +7,8 @@ #include "Library/LiveActor/LiveActor.h" #include "Library/Ranking/IUseRanking.h" +#include "ModeBalloon/RankingCategory.h" + namespace al { struct ActorInitInfo; class IUseDataStore; @@ -30,8 +32,6 @@ class BalloonDataServer; class NoticeDataServer; } // namespace TimeBalloon -enum class RankingCategory : s32; - class NexRankingUploader : public al::IUseRanking, public al::LiveActor { public: NexRankingUploader(const char*, const char*); diff --git a/src/ModeBalloon/RankingCategory.h b/src/ModeBalloon/RankingCategory.h new file mode 100644 index 00000000..74d8e94d --- /dev/null +++ b/src/ModeBalloon/RankingCategory.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +SEAD_ENUM(RankingCategory, + JumpingRope, + Volleyball, + Radicon, + Yukimaru_1, + Yukimaru_2, + RaceManRace_1, + RaceManRace_2, + RaceManRace_3, + RaceManRace_4, + RaceManRace_5, + RaceManRace_6, + RaceManRace_7, + RaceManRace_8, + RaceManRace_9, + RaceManRace_10, + RaceManRace_11, + RaceManRace_12, + RaceManRace_13, +) diff --git a/src/ModeBalloon/RankingLocalFunction.cpp b/src/ModeBalloon/RankingLocalFunction.cpp new file mode 100644 index 00000000..af1a9a4b --- /dev/null +++ b/src/ModeBalloon/RankingLocalFunction.cpp @@ -0,0 +1,141 @@ +#include "ModeBalloon/RankingLocalFunction.h" + +#include + +#include "Library/Base/StringUtil.h" + +#include "Util/SequentialUtil.h" + +namespace RankingLocalFunction { +bool isValidCourceIdYukimaruRace(s32 courseId) { + return courseId == 0 || courseId == 1; +} + +RankingCategory getRankingCategoryYukimaruRace(s32 courseId) { + RankingCategory yukimaruCourseIdToCategory[2]{ + RankingCategory::Yukimaru_1, + RankingCategory::Yukimaru_2, + }; + + return yukimaruCourseIdToCategory[courseId]; +} + +bool isValidWorldIdRaceManRace(s32 worldId) { + // NOTE: Cloud (5), Ruined (11), Dark (15) and Darker Side (16) do not have a KFR + return worldId >= 0 && worldId != 5 && worldId != 11 && (u32)worldId < 15; +} + +RankingCategory getRankingCategoryRaceManRace(s32 worldId) { + switch (worldId) { + case 0: + return RankingCategory::RaceManRace_1; + case 1: + return RankingCategory::RaceManRace_2; + case 2: + return RankingCategory::RaceManRace_3; + case 3: + return RankingCategory::RaceManRace_4; + case 4: + return RankingCategory::RaceManRace_5; + case 6: + return RankingCategory::RaceManRace_6; + case 7: + return RankingCategory::RaceManRace_7; + case 8: + return RankingCategory::RaceManRace_8; + case 9: + return RankingCategory::RaceManRace_9; + case 10: + return RankingCategory::RaceManRace_10; + case 12: + return RankingCategory::RaceManRace_11; + case 13: + return RankingCategory::RaceManRace_12; + case 14: + return RankingCategory::RaceManRace_13; + + default: + return RankingCategory::RaceManRace_1; + } +} + +bool isRankingCategoryYukimaruRace(RankingCategory category) { + return category == RankingCategory::Yukimaru_1 || category == RankingCategory::Yukimaru_2; +} + +bool isRankingCategoryRaceManRace(RankingCategory category) { + return category >= RankingCategory::RaceManRace_1 && + category <= RankingCategory::RaceManRace_13; +} + +s32 getRaceManRaceWorldId(RankingCategory category) { + switch (category) { + case RankingCategory::RaceManRace_1: + return 0; + case RankingCategory::RaceManRace_2: + return 1; + case RankingCategory::RaceManRace_3: + return 2; + case RankingCategory::RaceManRace_4: + return 3; + case RankingCategory::RaceManRace_5: + return 4; + case RankingCategory::RaceManRace_6: + return 6; + case RankingCategory::RaceManRace_7: + return 7; + case RankingCategory::RaceManRace_8: + return 8; + case RankingCategory::RaceManRace_9: + return 9; + case RankingCategory::RaceManRace_10: + return 10; + case RankingCategory::RaceManRace_11: + return 12; + case RankingCategory::RaceManRace_12: + return 13; + case RankingCategory::RaceManRace_13: + return 14; + + default: + return -1; + } +} + +bool isRankingCategoryRace(RankingCategory category) { + return isRankingCategoryYukimaruRace(category) || isRankingCategoryRaceManRace(category) || + category == RankingCategory::Radicon; +} + +RankingCategory findRankingCategoryByRaceRecordName(const char* name) { + if (al::isEqualString(name, "Yukimaru_1")) + return RankingCategory::Yukimaru_1; + if (al::isEqualString(name, "Yukimaru_2")) + return RankingCategory::Yukimaru_2; + if (al::isEqualString(name, "Radicon")) + return RankingCategory::Radicon; + + if (al::isEqualSubString(name, "RaceManRace")) { + const char* worldIdStr = al::searchSubString(name, "_") + 1; + s32 worldId = 0; + sead::StringUtil::tryParseS32(&worldId, worldIdStr, + sead::StringUtil::CardinalNumber::BaseAuto); + return getRankingCategoryRaceManRace(worldId); + } + + return RankingCategory::Yukimaru_1; +} + +bool isRankingUploadableScore(u32 score, RankingCategory category) { + if (category == RankingCategory::JumpingRope) + return score != 0; + + if (category == RankingCategory::Volleyball) + return score != 0; + + if (isRankingCategoryRace(category)) + return score <= RaceTimeFunction::getRaceTimeMaxCsec(); + + return true; +} +} // namespace RankingLocalFunction diff --git a/src/ModeBalloon/RankingLocalFunction.h b/src/ModeBalloon/RankingLocalFunction.h index f44b15a0..7a7bfe54 100644 --- a/src/ModeBalloon/RankingLocalFunction.h +++ b/src/ModeBalloon/RankingLocalFunction.h @@ -3,7 +3,7 @@ #include #include -enum class alignas(8) RankingCategory : s32; +#include "ModeBalloon/RankingCategory.h" namespace RankingLocalFunction { bool isValidCourceIdYukimaruRace(s32); @@ -26,6 +26,8 @@ struct RankingDataOneRecord { void* filler_0[14]; }; +static_assert(sizeof(RankingDataOneRecord) == 0x70); + class RankingDataOneCategory { public: RankingDataOneCategory(RankingCategory, s32); @@ -37,5 +39,4 @@ private: void* filler_8[3]; }; -static_assert(sizeof(RankingDataOneRecord) == 0x70); static_assert(sizeof(RankingDataOneCategory) == 0x20); diff --git a/src/System/NetworkUploadFlag.h b/src/System/NetworkUploadFlag.h index 0f3c1a0b..14b9626f 100644 --- a/src/System/NetworkUploadFlag.h +++ b/src/System/NetworkUploadFlag.h @@ -2,13 +2,13 @@ #include +#include "ModeBalloon/RankingCategory.h" #include "System/ByamlSave.h" namespace al { class ByamlIter; class ByamlWriter; } // namespace al -enum class RankingCategory : s32; class NetworkUploadFlag : public ByamlSave { public: diff --git a/src/Util/SequentialUtil.cpp b/src/Util/SequentialUtil.cpp index 3451faf7..a7fae293 100644 --- a/src/Util/SequentialUtil.cpp +++ b/src/Util/SequentialUtil.cpp @@ -12,7 +12,7 @@ s32 RaceTimeFunction::getRaceTimeMaxFrame() { return 215999; } -s32 RaceTimeFunction::getRaceTimeMaxCsec() { +u32 RaceTimeFunction::getRaceTimeMaxCsec() { return 359999; } diff --git a/src/Util/SequentialUtil.h b/src/Util/SequentialUtil.h index 4ce1ca34..c7ef3620 100644 --- a/src/Util/SequentialUtil.h +++ b/src/Util/SequentialUtil.h @@ -8,6 +8,6 @@ void convertCsecTo(s32* minutes, s32* seconds, s32* csec, s32 time); namespace RaceTimeFunction { s32 getRaceTimeMaxFrame(); -s32 getRaceTimeMaxCsec(); +u32 getRaceTimeMaxCsec(); s32 clampRaceRecordCsec(s32 csec); } // namespace RaceTimeFunction