From 296148309e2dfa8ee29a461d63342a87274f5715 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Mon, 9 Jun 2025 17:54:52 +0200 Subject: [PATCH 1/7] Statistics --- WheelWizard/Models/GameData/LicenseProfile.cs | 1 + WheelWizard/Models/GameData/Statistics.cs | 273 ++++++++++++++++++ 2 files changed, 274 insertions(+) create mode 100644 WheelWizard/Models/GameData/Statistics.cs diff --git a/WheelWizard/Models/GameData/LicenseProfile.cs b/WheelWizard/Models/GameData/LicenseProfile.cs index d4e2f74b..73279f92 100644 --- a/WheelWizard/Models/GameData/LicenseProfile.cs +++ b/WheelWizard/Models/GameData/LicenseProfile.cs @@ -5,4 +5,5 @@ public class LicenseProfile : PlayerProfileBase public required uint TotalRaceCount { get; set; } public required uint TotalWinCount { get; set; } public List Friends { get; set; } = []; + public Statistics Statistics { get; set; } } diff --git a/WheelWizard/Models/GameData/Statistics.cs b/WheelWizard/Models/GameData/Statistics.cs new file mode 100644 index 00000000..323e3d07 --- /dev/null +++ b/WheelWizard/Models/GameData/Statistics.cs @@ -0,0 +1,273 @@ +using System.Reflection.PortableExecutable; + +namespace WheelWizard.Models.GameData; + +public class Statistics +{ + public RaceTotals RaceTotals { get; set; } = new(); + public PreferredControls Controls { get; set; } = new(); + public Performance Performance { get; init; } = new(); + public RaceCompletions RaceCompletions { get; init; } = new(); + public BattleCompletions BattleCompletions { get; init; } = new(); + public ushort TotalCompetitions { get; init; } + public TrophyCabinet? Trophies { get; init; } +} + +public class TrophyCabinet +{ + /// + /// Key = “Mushroom Cup 50cc”, Value = TrophyInfo + /// + public Dictionary PerCup { get; init; } = new(); +} + +/// +/// Single cup’s result: which trophy and what star rank. +/// +public class TrophyInfo +{ + public CupTrophyType CupType { get; set; } // Gold, Silver, Bronze, None + public CupRank Rank { get; set; } // ThreeStars, TwoStars, OneStar, A, B, C, D, E, F + public bool Completed { get; set; } // bit 0x52: 1 = completed +} + +public enum CupTrophyType +{ + Gold = 0, + Silver = 1, + Bronze = 2, + None = 3 +} + +public enum CupRank +{ + ThreeStars = 0, + TwoStars = 1, + OneStar = 2, + A = 3, + B = 4, + C = 5, + D = 6, + E = 7, + F = 8 +} + +public class BattleCompletions +{ + /// + /// Key = Battle Stage, Value = Amount of times the stage was played + /// + public Dictionary Stage { get; set; } = new(); +} + +// The 10 battle stages in the order they appear in RKPD (0x1A6 onward). +public enum Stage : byte +{ + DelfinoPier = 0, + BlockPlaza, + ChainChompWheel, + FunkyStadium, + ThwompDesert, + GCNCookieLand, + DSTwilightHouse, + SNESBattleCourse4, + GBABattleCourse3, + N64Skyscraper +} + +public class RaceCompletions +{ + public Dictionary Character { get; set; } = new(); + + /// + /// Key = Vehicle enum, Value = races completed count. + /// Uses 36 entries starting at 0x11E. + /// + public Dictionary Vehicle { get; init; } = new(); + + /// + /// Key = Course enum, Value = races completed count. + /// Uses 32 entries starting at 0x166. + /// + public Dictionary Course { get; init; } = new(); +} + +public enum Course : byte +{ + MarioCircuit = 0, + MooMooMeadows, + MushroomGorge, + GrumbleVolcano, + ToadsFactory, + CoconutMall, + DKSummit, + WariosGoldMine, + LuigiCircuit, + DaisyCircuit, + MoonviewHighway, + MapleTreeway, + BowserCastle, + RainbowRoad, + DryDryRuins, + KoopaCape, + GCNPeachBeach, + GCNMarioCircuit, + GCNWaluigiStadium, + GCNDKMountain, + DSYoshiFalls, + DSDesertHills, + DSPeachGardens, + DSDelfinoSquare, + SNESMarioCircuit3, + SNESGhostValley2, + N64MarioRaceway, + N64SherbetLand, + N64BowsersCastle, + N64DKsJungleParkway, + GBABowserCastle3, + GBAShyGuyBeach +} + + + +public enum Vehicle : byte +{ + StandardKartS = 0, + StandardKartM, + StandardKartL, + BoosterSeat, + ClassicDragster, + Offroader, + MiniBeast, + WildWing, + FlameFlyer, + CheepCharger, + SuperBlooper, + PiranhaProwler, + TinyTitan, + Daytripper, + Jetsetter, + BlueFalcon, + Sprinter, + Honeycoupe, + StandardBikeS, + StandardBikeM, + StandardBikeL, + BulletBike, + MachBike, + FlameRunner, + BitBike, + Sugarscoot, + WarioBike, + Quacker, + ZipZip, + ShootingStar, + Magikruiser, + Sneakster, + Spear, + JetBubble, + DolphinDasher, + Phantom +} + +// The 24 playable characters in the order they appear in RKPD (0xEC onward). +public enum Character : byte +{ + Mario = 0, + BabyPeach, + Waluigi, + Bowser, + BabyDaisy, + DryBones, + BabyMario, + Luigi, + Toad, + DonkeyKong, + Yoshi, + Wario, + BabyLuigi, + Toadette, + KoopaTroopa, + Daisy, + Peach, + Birdo, + DiddyKong, + KingBoo, + BowserJr, + DryBowser, + FunkyKong, + Rosalina, + Mii = 23 +} + +public class Performance +{ + public int TricksPerformed { get; set; } // total amount of tricks performed + public int ItemHitsDealt { get; set; } // total amount of item hits dealt + public int ItemHitsReceived { get; set; } // total amount of item hits received + public int FirstPlaces { get; set; } // total amount of first places achieved + public float DistanceTotal { get; set; } // Total Distance driven + public float DistanceInFirstPlace { get; set; } // Total Distance driven in first place + public float DistanceVsRaces { get; set; } + // percentage of time in 1st = floor(DistanceInFirstPlace / DistanceVsRaces * 100). + + public int PercentTimeInFirstPlace { + get + { + if (DistanceVsRaces == 0) + return 0; + + return (int)Math.Floor(DistanceInFirstPlace / DistanceVsRaces * 100); + } + } +} + +public class PreferredControls +{ + public int WiiWheelRaces { get; set; } // amount of matched played with the Wii Wheel + public int WiiWheelBattles { get; set; } // amount of matched played with the Wii Wheel in battles + public float WheelWheelUsageRatio + { + get + { + if (WiiWheelRaces + WiiWheelBattles == 0) + return 0f; + + return (float)WiiWheelRaces / (WiiWheelRaces + WiiWheelBattles); + } + } + public DriftType PreferredDriftType { get; set; } = DriftType.Standard; // preferred drift type, default is standard +} +public enum DriftType +{ + Standard = 0, + Manual = 1, + Automatic = 2 +} + + + + +public class RaceTotals +{ + public uint Races { get; set; } // total amount of races completed + public uint BattleMatches { get; set; } // total amount of battle matches completed + public WinsVsLosses WinsVsLosses { get; set; } = new(); + public int GhostChallengesSent { get; set; } // total amount of ghost challenges sent + public int GhostChallengesReceived { get; set; } // total amount of ghost challenges received +} + +public class WinsVsLosses +{ + public WinLoss OfflineVs { get; set; } = new(); + public WinLoss OfflineBattle { get; set; } = new(); + public WinLoss OnlineVs { get; set; } = new(); + public WinLoss OnlineBattle { get; set; } = new(); +} +public class WinLoss +{ + public uint Wins { get; set; } + public uint Losses { get; set; } +} + + From 1348a8f9fecb398a3601a9e40aadad90f3ea65a2 Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Tue, 10 Jun 2025 06:30:05 +0200 Subject: [PATCH 2/7] Statistics --- .../WiiManagement/GameLicenseService.cs | 5 + .../WiiManagement/StatisticsSerializer.cs | 171 ++++++++++++++++ WheelWizard/Models/GameData/Statistics.cs | 44 ++--- WheelWizard/Models/RRInfo/RrRoom.cs | 187 +++++++++--------- .../SaveData/BigEndianBinaryReader.cs | 9 + 5 files changed, 298 insertions(+), 118 deletions(-) create mode 100644 WheelWizard/Features/WiiManagement/StatisticsSerializer.cs diff --git a/WheelWizard/Features/WiiManagement/GameLicenseService.cs b/WheelWizard/Features/WiiManagement/GameLicenseService.cs index 4bc2bb37..5a5cc6fd 100644 --- a/WheelWizard/Features/WiiManagement/GameLicenseService.cs +++ b/WheelWizard/Features/WiiManagement/GameLicenseService.cs @@ -151,6 +151,7 @@ private static LicenseProfile CreateDummyLicense() Friends = [], RegionId = 10, // 10 => “unknown” IsOnline = false, + Statistics = new(), }; return dummyLicense; } @@ -196,6 +197,9 @@ private OperationResult ParseLicenseUser(int rkpdOffset) var friendCode = FriendCodeGenerator.GetFriendCode(_rksysData, rkpdOffset + 0x5C); var miiDataResult = ParseMiiData(rkpdOffset); var miiToUse = miiDataResult.IsFailure ? new() : miiDataResult.Value; + + var statistics = StatisticsSerializer.ParseStatistics(_rksysData, rkpdOffset); + var user = new LicenseProfile { Mii = miiToUse, @@ -208,6 +212,7 @@ private OperationResult ParseLicenseUser(int rkpdOffset) // Region is often found near offset 0x23308 + 0x3802 in RKGD. This code is a partial guess. // In practice, region might be read differently depending on your rksys layout. RegionId = BigEndianBinaryReader.BufferToUint16(_rksysData, 0x23308 + 0x3802) / 4096, + Statistics = statistics, }; ParseFriends(user, rkpdOffset); diff --git a/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs b/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs new file mode 100644 index 00000000..b37bac98 --- /dev/null +++ b/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using WheelWizard.Models.GameData; +using WheelWizard.Services.WiiManagement.SaveData; + +namespace WheelWizard.WiiManagement; + +public static class StatisticsSerializer +{ + private static readonly string[] CupNames = { "Mushroom", "Flower", "Star", "Special", "Shell", "Banana", "Leaf", "Lightning" }; + private static readonly string[] EngineClasses = { "50cc", "100cc", "150cc", "Mirror" }; + + public static Statistics ParseStatistics(byte[] rksysData, int rkpdOffset) + { + return new() + { + RaceTotals = ParseRaceTotals(rksysData, rkpdOffset), + Performance = ParsePerformance(rksysData, rkpdOffset), + PreferredControls = ParsePreferredControls(rksysData, rkpdOffset), + RaceCompletions = ParseRaceCompletions(rksysData, rkpdOffset), + BattleCompletions = ParseBattleCompletions(rksysData, rkpdOffset), + TotalCompetitions = (ushort)BigEndianBinaryReader.BufferToUint16(rksysData, rkpdOffset + 0xE8), + Trophies = ParseTrophyCabinet(rksysData, rkpdOffset), + }; + } + + private static RaceTotals ParseRaceTotals(byte[] rksysData, int rkpdOffset) + { + return new() + { + Races = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xB4), + BattleMatches = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xB8), + GhostChallengesSent = (int)BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xC8), + GhostChallengesReceived = (int)BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xCC), + WinsVsLosses = new() + { + OfflineVs = new() + { + Wins = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0x88), + Losses = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0x8C), + }, + OfflineBattle = new() + { + Wins = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0x90), + Losses = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0x94), + }, + OnlineVs = new() + { + Wins = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0x98), + Losses = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0x9C), + }, + OnlineBattle = new() + { + Wins = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xA0), + Losses = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xA4), + }, + }, + }; + } + + private static Performance ParsePerformance(byte[] rksysData, int rkpdOffset) + { + return new() + { + ItemHitsDealt = (int)BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xD0), + ItemHitsReceived = (int)BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xD4), + TricksPerformed = (int)BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xD8), + FirstPlaces = (int)BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xDC), + DistanceTotal = BigEndianBinaryReader.BufferToFloat(rksysData, rkpdOffset + 0xC4), + DistanceInFirstPlace = BigEndianBinaryReader.BufferToFloat(rksysData, rkpdOffset + 0xE0), + DistanceVsRaces = BigEndianBinaryReader.BufferToFloat(rksysData, rkpdOffset + 0xE4), + }; + } + + private static PreferredControls ParsePreferredControls(byte[] rksysData, int rkpdOffset) + { + var driftByte = rksysData[rkpdOffset + 0xEA]; + var driftValue = (driftByte >> 0) & 0x03; // Get first 2 bits + var driftType = driftValue switch + { + 1 => DriftType.Manual, + 2 => DriftType.Automatic, + _ => DriftType.Standard, + }; + + return new PreferredControls + { + WiiWheelRaces = (int)BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xBC), + WiiWheelBattles = (int)BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xC0), + PreferredDriftType = driftType, + }; + } + + private static RaceCompletions ParseRaceCompletions(byte[] rksysData, int rkpdOffset) + { + var completions = new RaceCompletions(); + + // Favorite Character (0xEC, 24 entries) + var charBaseOffset = rkpdOffset + 0xEC; + foreach (Character character in Enum.GetValues(typeof(Character))) + { + var offset = charBaseOffset + (int)character * 2; + completions.Character[character] = (int)BigEndianBinaryReader.BufferToUint16(rksysData, offset); + } + + // Favorite Vehicle (0x11E, 36 entries) + var vehicleBaseOffset = rkpdOffset + 0x11E; + foreach (Vehicle vehicle in Enum.GetValues(typeof(Vehicle))) + { + var offset = vehicleBaseOffset + (int)vehicle * 2; + completions.Vehicle[vehicle] = (int)BigEndianBinaryReader.BufferToUint16(rksysData, offset); + } + + // Favorite Course (0x166, 32 entries) + var courseBaseOffset = rkpdOffset + 0x166; + foreach (Course course in Enum.GetValues(typeof(Course))) + { + var offset = courseBaseOffset + (int)course * 2; + completions.Course[course] = (int)BigEndianBinaryReader.BufferToUint16(rksysData, offset); + } + + return completions; + } + + private static BattleCompletions ParseBattleCompletions(byte[] rksysData, int rkpdOffset) + { + var completions = new BattleCompletions(); + + // Favorite Stage (0x1A6, 10 entries) + var stageBaseOffset = rkpdOffset + 0x1A6; + foreach (Stage stage in Enum.GetValues(typeof(Stage))) + { + var offset = stageBaseOffset + (int)stage * 2; + completions.Stage[stage] = (int)BigEndianBinaryReader.BufferToUint16(rksysData, offset); + } + return completions; + } + + private static TrophyCabinet ParseTrophyCabinet(byte[] rksysData, int rkpdOffset) + { + var cabinet = new TrophyCabinet(); + var cupDataStartOffset = rkpdOffset + 0x1C0; + const int cupBlockSize = 0x60; + + var cupIndex = 0; + for (var engineIdx = 0; engineIdx < EngineClasses.Length; engineIdx++) + { + for (var nameIdx = 0; nameIdx < CupNames.Length; nameIdx++) + { + var cupOffset = cupDataStartOffset + (cupIndex * cupBlockSize); + + var trophyByte = rksysData[cupOffset + 0x4F]; + var rankByte = rksysData[cupOffset + 0x51]; + var completedByte = rksysData[cupOffset + 0x52]; + + var info = new TrophyInfo + { + CupType = (CupTrophyType)(trophyByte & 0x03), + Rank = (CupRank)((rankByte >> 4) & 0x0F), + Completed = (completedByte & 0x01) == 1, + }; + + var key = $"{CupNames[nameIdx]} Cup ({EngineClasses[engineIdx]})"; + cabinet.PerCup[key] = info; + + cupIndex++; + } + } + return cabinet; + } +} diff --git a/WheelWizard/Models/GameData/Statistics.cs b/WheelWizard/Models/GameData/Statistics.cs index 323e3d07..5efef4fd 100644 --- a/WheelWizard/Models/GameData/Statistics.cs +++ b/WheelWizard/Models/GameData/Statistics.cs @@ -1,5 +1,3 @@ -using System.Reflection.PortableExecutable; - namespace WheelWizard.Models.GameData; public class Statistics @@ -8,6 +6,7 @@ public class Statistics public PreferredControls Controls { get; set; } = new(); public Performance Performance { get; init; } = new(); public RaceCompletions RaceCompletions { get; init; } = new(); + public PreferredControls PreferredControls { get; init; } = new(); public BattleCompletions BattleCompletions { get; init; } = new(); public ushort TotalCompetitions { get; init; } public TrophyCabinet? Trophies { get; init; } @@ -16,7 +15,7 @@ public class Statistics public class TrophyCabinet { /// - /// Key = “Mushroom Cup 50cc”, Value = TrophyInfo + /// Key = “Mushroom Cup 50cc”, Value = TrophyInfo /// public Dictionary PerCup { get; init; } = new(); } @@ -26,9 +25,9 @@ public class TrophyCabinet /// public class TrophyInfo { - public CupTrophyType CupType { get; set; } // Gold, Silver, Bronze, None - public CupRank Rank { get; set; } // ThreeStars, TwoStars, OneStar, A, B, C, D, E, F - public bool Completed { get; set; } // bit 0x52: 1 = completed + public CupTrophyType CupType { get; set; } // Gold, Silver, Bronze, None + public CupRank Rank { get; set; } // ThreeStars, TwoStars, OneStar, A, B, C, D, E, F + public bool Completed { get; set; } // bit 0x52: 1 = completed } public enum CupTrophyType @@ -36,7 +35,7 @@ public enum CupTrophyType Gold = 0, Silver = 1, Bronze = 2, - None = 3 + None = 3, } public enum CupRank @@ -49,7 +48,7 @@ public enum CupRank C = 5, D = 6, E = 7, - F = 8 + F = 8, } public class BattleCompletions @@ -72,21 +71,21 @@ public enum Stage : byte DSTwilightHouse, SNESBattleCourse4, GBABattleCourse3, - N64Skyscraper + N64Skyscraper, } public class RaceCompletions { public Dictionary Character { get; set; } = new(); - + /// - /// Key = Vehicle enum, Value = races completed count. + /// Key = Vehicle enum, Value = races completed count. /// Uses 36 entries starting at 0x11E. /// public Dictionary Vehicle { get; init; } = new(); /// - /// Key = Course enum, Value = races completed count. + /// Key = Course enum, Value = races completed count. /// Uses 32 entries starting at 0x166. /// public Dictionary Course { get; init; } = new(); @@ -125,11 +124,9 @@ public enum Course : byte N64BowsersCastle, N64DKsJungleParkway, GBABowserCastle3, - GBAShyGuyBeach + GBAShyGuyBeach, } - - public enum Vehicle : byte { StandardKartS = 0, @@ -167,7 +164,7 @@ public enum Vehicle : byte Spear, JetBubble, DolphinDasher, - Phantom + Phantom, } // The 24 playable characters in the order they appear in RKPD (0xEC onward). @@ -197,7 +194,7 @@ public enum Character : byte DryBowser, FunkyKong, Rosalina, - Mii = 23 + Mii = 23, } public class Performance @@ -209,9 +206,11 @@ public class Performance public float DistanceTotal { get; set; } // Total Distance driven public float DistanceInFirstPlace { get; set; } // Total Distance driven in first place public float DistanceVsRaces { get; set; } + // percentage of time in 1st = floor(DistanceInFirstPlace / DistanceVsRaces * 100). - public int PercentTimeInFirstPlace { + public int PercentTimeInFirstPlace + { get { if (DistanceVsRaces == 0) @@ -238,16 +237,14 @@ public float WheelWheelUsageRatio } public DriftType PreferredDriftType { get; set; } = DriftType.Standard; // preferred drift type, default is standard } + public enum DriftType { Standard = 0, Manual = 1, - Automatic = 2 + Automatic = 2, } - - - public class RaceTotals { public uint Races { get; set; } // total amount of races completed @@ -264,10 +261,9 @@ public class WinsVsLosses public WinLoss OnlineVs { get; set; } = new(); public WinLoss OnlineBattle { get; set; } = new(); } + public class WinLoss { public uint Wins { get; set; } public uint Losses { get; set; } } - - diff --git a/WheelWizard/Models/RRInfo/RrRoom.cs b/WheelWizard/Models/RRInfo/RrRoom.cs index f6db258c..2a0bea6c 100644 --- a/WheelWizard/Models/RRInfo/RrRoom.cs +++ b/WheelWizard/Models/RRInfo/RrRoom.cs @@ -23,112 +23,111 @@ public class RrRoom Rk switch { // Retro Rewind - "vs_10" => "RR", - "vs_11" => "TT", - "vs_12" => "200", - "vs_20" => "RR Ct", - "vs_21" => "TT Ct", - "vs_22" => "200 Ct", - + "vs_10" => "RR", + "vs_11" => "TT", + "vs_12" => "200", + "vs_20" => "RR Ct", + "vs_21" => "TT Ct", + "vs_22" => "200 Ct", + // CTGP_C - "vs_668" => "CTGP-C", - + "vs_668" => "CTGP-C", + // Insane Kart Wii - "vs_69" => "IKW", - "vs_70" => "Ultras", - "vs_71" => "Crazy", - "vs_72" => "Bomb", - "vs_73" => "Accel", - "vs_74" => "Banana", - "vs_75" => "RndItm", - "vs_76" => "Unfair", - "vs_77" => "Blue", - "vs_78" => "Shroom", - "vs_79" => "Bumper", - "vs_80" => "Rampage", - "vs_81" => "Rain", - "vs_82" => "Break", - "vs_83" => "Riibal", - + "vs_69" => "IKW", + "vs_70" => "Ultras", + "vs_71" => "Crazy", + "vs_72" => "Bomb", + "vs_73" => "Accel", + "vs_74" => "Banana", + "vs_75" => "RndItm", + "vs_76" => "Unfair", + "vs_77" => "Blue", + "vs_78" => "Shroom", + "vs_79" => "Bumper", + "vs_80" => "Rampage", + "vs_81" => "Rain", + "vs_82" => "Break", + "vs_83" => "Riibal", + // Luminous - "vs_666" => "Lumi", - + "vs_666" => "Lumi", + // OptPack - "vs_875" => "OP 150", - "vs_876" => "OP TT", - "vs_877" => "OP R1", - "vs_878" => "OP R2", - "vs_879" => "OP R3", - "vs_880" => "OP R4", - + "vs_875" => "OP 150", + "vs_876" => "OP TT", + "vs_877" => "OP R1", + "vs_878" => "OP R2", + "vs_879" => "OP R3", + "vs_880" => "OP R4", + // WTP "vs_1312" => "WTP 150", "vs_1313" => "WTP 200", "vs_1314" => "WTP TT", - + // Generic Versus - "vs_751" => "VS", - "vs_-1" => "Reg", - "vs" => "Reg", - + "vs_751" => "VS", + "vs_-1" => "Reg", + "vs" => "Reg", + _ => IsPublic ? "??" : "Lock", }; -public string GameMode => - Rk switch - { - //Max Size:"----------------------" - // Retro Rewind - "vs_10" => "RR 150CC", - "vs_11" => "RR Time Tr", - "vs_12" => "RR 200CC", - "vs_20" => "RR 150CC CTs", - "vs_21" => "RR TT CTs", - "vs_22" => "RR 200CC CTs", - - // CTGP - "vs_668" => "CTGP-C", - - // Insane Kart Wii - "vs_69" => "Insane Kart", - "vs_70" => "Ultras VS", - "vs_71" => "Crazy Items", - "vs_72" => "Bob-omb Blast", - "vs_73" => "Inf Accel", - "vs_74" => "Banan Slip", - "vs_75" => "Rand Items", - "vs_76" => "Unfair Items", - "vs_77" => "Blue Madness", - "vs_78" => "Mush Dash", - "vs_79" => "Bumper Karts", - "vs_80" => "Item Rampage", - "vs_81" => "Item Rain", - "vs_82" => "Shell Break", - "vs_83" => "Riibalanced", - - // Luminous - "vs_666" => "Luminous", - - // OptPack - "vs_875" => "OP 150", - "vs_876" => "OP TT", - "vs_877" => "OP R1", - "vs_878" => "OP R2", - "vs_879" => "OP R3", - "vs_880" => "OP R4", - - - // WTP - "vs_1312" => "WTP 150CC", - "vs_1313" => "WTP 200CC", - "vs_1314" => "WTP Time Trial", - - // Generic - "vs_751" => "Versus", - "vs_-1" => "Regular", - "vs" => "Regular", - _ => IsPublic ? "Unknown Mode" : "Private Room", - }; + public string GameMode => + Rk switch + { + //Max Size:"----------------------" + // Retro Rewind + "vs_10" => "RR 150CC", + "vs_11" => "RR Time Tr", + "vs_12" => "RR 200CC", + "vs_20" => "RR 150CC CTs", + "vs_21" => "RR TT CTs", + "vs_22" => "RR 200CC CTs", + + // CTGP + "vs_668" => "CTGP-C", + + // Insane Kart Wii + "vs_69" => "Insane Kart", + "vs_70" => "Ultras VS", + "vs_71" => "Crazy Items", + "vs_72" => "Bob-omb Blast", + "vs_73" => "Inf Accel", + "vs_74" => "Banan Slip", + "vs_75" => "Rand Items", + "vs_76" => "Unfair Items", + "vs_77" => "Blue Madness", + "vs_78" => "Mush Dash", + "vs_79" => "Bumper Karts", + "vs_80" => "Item Rampage", + "vs_81" => "Item Rain", + "vs_82" => "Shell Break", + "vs_83" => "Riibalanced", + + // Luminous + "vs_666" => "Luminous", + + // OptPack + "vs_875" => "OP 150", + "vs_876" => "OP TT", + "vs_877" => "OP R1", + "vs_878" => "OP R2", + "vs_879" => "OP R3", + "vs_880" => "OP R4", + + // WTP + "vs_1312" => "WTP 150CC", + "vs_1313" => "WTP 200CC", + "vs_1314" => "WTP Time Trial", + + // Generic + "vs_751" => "Versus", + "vs_-1" => "Regular", + "vs" => "Regular", + _ => IsPublic ? "Unknown Mode" : "Private Room", + }; public int AverageVr => PlayerCount == 0 ? 0 : Players.Sum(p => p.Value.Vr) / PlayerCount; diff --git a/WheelWizard/Services/WiiManagement/SaveData/BigEndianBinaryReader.cs b/WheelWizard/Services/WiiManagement/SaveData/BigEndianBinaryReader.cs index 6b8e5a9d..9114dc7d 100644 --- a/WheelWizard/Services/WiiManagement/SaveData/BigEndianBinaryReader.cs +++ b/WheelWizard/Services/WiiManagement/SaveData/BigEndianBinaryReader.cs @@ -39,6 +39,15 @@ public static void WriteUInt32BigEndian(byte[] data, int offset, uint value) data[offset + 3] = (byte)(value & 0xFF); } + public static float BufferToFloat(byte[] data, int offset) + { + // for a big-endian float before converting. + var bytes = new byte[4]; + Array.Copy(data, offset, bytes, 0, 4); + Array.Reverse(bytes); + return BitConverter.ToSingle(bytes, 0); + } + public static void WriteUInt16BigEndian(byte[] data, int offset, ushort value) { data[offset] = (byte)(value >> 8); From 615c6376195445ebf773e4ccc8ed00e6a37ced1f Mon Sep 17 00:00:00 2001 From: Issam ben massoud Date: Tue, 10 Jun 2025 11:27:07 +0200 Subject: [PATCH 3/7] Fixed race count finally --- .../Features/WiiManagement/StatisticsSerializer.cs | 11 +++++++---- WheelWizard/Models/GameData/Statistics.cs | 13 ++++++++++++- WheelWizard/Views/Pages/UserProfilePage.axaml.cs | 4 ++-- 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs b/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs index b37bac98..14e30029 100644 --- a/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs +++ b/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs @@ -8,6 +8,9 @@ namespace WheelWizard.WiiManagement; public static class StatisticsSerializer { private static readonly string[] CupNames = { "Mushroom", "Flower", "Star", "Special", "Shell", "Banana", "Leaf", "Lightning" }; + + // On retro rewind these are different, but for the original game these are the engine classes. + // So whenever we display we should check if the game is retro rewind or not. private static readonly string[] EngineClasses = { "50cc", "100cc", "150cc", "Mirror" }; public static Statistics ParseStatistics(byte[] rksysData, int rkpdOffset) @@ -28,7 +31,7 @@ private static RaceTotals ParseRaceTotals(byte[] rksysData, int rkpdOffset) { return new() { - Races = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xB4), + AllRacesCount = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xB4), BattleMatches = BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xB8), GhostChallengesSent = (int)BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xC8), GhostChallengesReceived = (int)BigEndianBinaryReader.BufferToUint32(rksysData, rkpdOffset + 0xCC), @@ -143,9 +146,9 @@ private static TrophyCabinet ParseTrophyCabinet(byte[] rksysData, int rkpdOffset const int cupBlockSize = 0x60; var cupIndex = 0; - for (var engineIdx = 0; engineIdx < EngineClasses.Length; engineIdx++) + foreach (var engineClass in EngineClasses) { - for (var nameIdx = 0; nameIdx < CupNames.Length; nameIdx++) + foreach (var cupName in CupNames) { var cupOffset = cupDataStartOffset + (cupIndex * cupBlockSize); @@ -160,7 +163,7 @@ private static TrophyCabinet ParseTrophyCabinet(byte[] rksysData, int rkpdOffset Completed = (completedByte & 0x01) == 1, }; - var key = $"{CupNames[nameIdx]} Cup ({EngineClasses[engineIdx]})"; + var key = $"{cupName} Cup ({engineClass})"; cabinet.PerCup[key] = info; cupIndex++; diff --git a/WheelWizard/Models/GameData/Statistics.cs b/WheelWizard/Models/GameData/Statistics.cs index 5efef4fd..f7103736 100644 --- a/WheelWizard/Models/GameData/Statistics.cs +++ b/WheelWizard/Models/GameData/Statistics.cs @@ -247,7 +247,18 @@ public enum DriftType public class RaceTotals { - public uint Races { get; set; } // total amount of races completed + public uint AllRacesCount { get; set; } // total amount of races completed + + public uint OnlineRacesCount + { + get + { + return WinsVsLosses.OnlineVs.Wins + + WinsVsLosses.OnlineVs.Losses + + WinsVsLosses.OnlineBattle.Wins + + WinsVsLosses.OnlineBattle.Losses; + } + } public uint BattleMatches { get; set; } // total amount of battle matches completed public WinsVsLosses WinsVsLosses { get; set; } = new(); public int GhostChallengesSent { get; set; } // total amount of ghost challenges sent diff --git a/WheelWizard/Views/Pages/UserProfilePage.axaml.cs b/WheelWizard/Views/Pages/UserProfilePage.axaml.cs index eb06cd32..8dfcfb8e 100644 --- a/WheelWizard/Views/Pages/UserProfilePage.axaml.cs +++ b/WheelWizard/Views/Pages/UserProfilePage.axaml.cs @@ -136,8 +136,8 @@ private void UpdatePage() if (IsOnline) CurrentUserProfile.Classes.Add("Online"); - ProfileAttribTotalRaces.Text = currentPlayer.TotalRaceCount.ToString(); - ProfileAttribTotalWins.Text = currentPlayer.TotalWinCount.ToString(); + ProfileAttribTotalRaces.Text = currentPlayer.Statistics.RaceTotals.OnlineRacesCount.ToString(); + ProfileAttribTotalWins.Text = currentPlayer.Statistics.RaceTotals.WinsVsLosses.OnlineVs.Wins.ToString(); BadgeContainer.Children.Clear(); var badges = BadgeService.GetBadges(currentPlayer.FriendCode).Select(variant => new Badge { Variant = variant }); From c066c304d816d77e4429df10a5e8396170e1da52 Mon Sep 17 00:00:00 2001 From: Issam ben massoud <64382339+patchzyy@users.noreply.github.com> Date: Tue, 10 Jun 2025 14:29:07 +0200 Subject: [PATCH 4/7] Fav Char --- .../WiiManagement/StatisticsSerializer.cs | 2 +- WheelWizard/Models/GameData/Statistics.cs | 20 ++++++++++++++++++- WheelWizard/Views/Pages/UserProfilePage.axaml | 4 ++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs b/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs index 14e30029..306b19aa 100644 --- a/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs +++ b/WheelWizard/Features/WiiManagement/StatisticsSerializer.cs @@ -103,7 +103,7 @@ private static RaceCompletions ParseRaceCompletions(byte[] rksysData, int rkpdOf foreach (Character character in Enum.GetValues(typeof(Character))) { var offset = charBaseOffset + (int)character * 2; - completions.Character[character] = (int)BigEndianBinaryReader.BufferToUint16(rksysData, offset); + completions.CharacterCompletions[character] = (int)BigEndianBinaryReader.BufferToUint16(rksysData, offset); } // Favorite Vehicle (0x11E, 36 entries) diff --git a/WheelWizard/Models/GameData/Statistics.cs b/WheelWizard/Models/GameData/Statistics.cs index f7103736..eb4f08a7 100644 --- a/WheelWizard/Models/GameData/Statistics.cs +++ b/WheelWizard/Models/GameData/Statistics.cs @@ -76,7 +76,25 @@ public enum Stage : byte public class RaceCompletions { - public Dictionary Character { get; set; } = new(); + public Dictionary CharacterCompletions { get; set; } = new(); + + public Character FavoriteCharacter + { + get + { + var currentFavorite = Character.Mario; // Default to Mario + var maxCompletions = 0; + foreach (var kvp in CharacterCompletions) + { + if (kvp.Value > maxCompletions) + { + maxCompletions = kvp.Value; + currentFavorite = kvp.Key; + } + } + return currentFavorite; + } + } /// /// Key = Vehicle enum, Value = races completed count. diff --git a/WheelWizard/Views/Pages/UserProfilePage.axaml b/WheelWizard/Views/Pages/UserProfilePage.axaml index 2443cadc..56d429e1 100644 --- a/WheelWizard/Views/Pages/UserProfilePage.axaml +++ b/WheelWizard/Views/Pages/UserProfilePage.axaml @@ -63,7 +63,7 @@ Background="{StaticResource Neutral900}" BorderThickness="1" CornerRadius="{StaticResource GlobalCornerRadius}" BorderBrush="{StaticResource Neutral900}" x:Name="CurrentUserProfile"> - + - +