A .NET library for reading and writing Trackmania master server score files.
Score files are cached leaderboards and medal ranks used between TrackMania Forever and ManiaPlanet 3 versions of Trackmania (2008-2015).
| Type | Description |
|---|---|
CampaignScores |
Per-zone or per-group campaign scores, including per-map challenge scores and medal leagues |
ChallengeScores |
Per-league scores for an individual map |
GeneralScores |
Overall skillpoints and high score leaderboards per league |
LadderScores |
Ladder rankings and player counts per league |
Install the NuGet package:
dotnet add package TmScores
All scores types expose a static Deserialize method that accepts a file path or stream. Files are expected to be gzip-compressed (the standard stored format).
Campaign scores:
using TmScores;
var scores = CampaignScores.Deserialize("UnitedRace104085.gz");Challenge scores (single map):
var scores = ChallengeScores.Deserialize("BeySZdnfuSh4nHY5xztiXLmlrXe.gz");General scores:
var scores = GeneralScores.Deserialize("General104085.gz");Ladder scores:
var scores = LadderScores.Deserialize("Multi104085.gz");Use DeserializeRaw to read from an uncompressed stream:
var scores = CampaignScores.DeserializeRaw("UnitedRace104085");All scores types support round-trip serialization back to the compressed binary format:
using var ms = new MemoryStream();
scores.Serialize(ms);Or to a file:
scores.Serialize("output.gz");Use SerializeRaw to write uncompressed.
All types support collection initializer syntax:
var scores = new CampaignScores
{
new CampaignLeague
{
Name = "World|Czech republic|Jihoceský kraj",
ChallengeScores =
[
new CampaignChallengeScores("4kQ_0mHdJtnSdsQty9ZX0W8SDb1")
{
new Scores
{
LeagueName = "World",
Skillpoints = [new RecordUnit<int>(22060, 1)],
HighScores = [new HighScore(1, 22060, "jack1998-1998", "$i$f00ғ$c00ฟ$900๏.$fffRollin $f00¬ $c00Law")]
}
}
],
MedalLeagues =
[
new CampaignMedalLeague("World")
{
new CampaignMedalPlayMode(PlayMode.Race, [new RecordUnit<int>(22060, 1)])
}
]
}
};var scores = new LadderScores
{
new LadderLeague { Name = "World", PlayerCount = 9782572, Points = [(1, 100), (2, 90)] },
new LadderLeague { Name = "World|Czech republic", PlayerCount = 385442, Points = [(1, 80), (2, 70)] }
};var scores = new ChallengeScores
{
new Scores
{
LeagueName = "World",
Skillpoints = [new RecordUnit<int>(1112, 1)],
HighScores = [new HighScore(1, 1112, "bigbang1112", "BigBang1112")]
}
};When deserializing from a gzip stream, the modification timestamp embedded in the gzip header is exposed via the Timestamp property:
var scores = CampaignScores.Deserialize("UnitedRace104085.gz");
Console.WriteLine(scores.Timestamp); // DateTimeOffset, or null if unavailableCampaignScores (ICollection<CampaignLeague>)
└── CampaignLeague
├── ChallengeScores[] (CampaignChallengeScores per map)
│ └── Scores[] (per league)
│ ├── Skillpoints (RecordUnit<int>[])
│ └── HighScores (HighScore[])
└── MedalLeagues[] (CampaignMedalLeague per league)
└── CampaignMedalPlayMode[] (per PlayMode)
└── RecordUnit<int>[]
GeneralScores (ICollection<Scores>)
└── Scores
├── Skillpoints (RecordUnit<int>[])
└── HighScores (HighScore[])
ChallengeScores (ICollection<Scores>)
└── Scores
├── Skillpoints (RecordUnit<int>[])
└── HighScores (HighScore[])
LadderScores (ICollection<LadderLeague>)
└── LadderLeague
├── PlayerCount (int)
└── Points ((int Rank, int Points)[])
| Type | Description |
|---|---|
RecordUnit<T> |
A score value (Score) paired with a player count (Count) |
HighScore |
A leaderboard entry: Rank, Score, Login, Nickname, optional FilePath and GhostUrl |
PlayMode |
Race, Puzzle, Platform, Stunts |
Simple scores viewer and editor is available here.
Goes to Ondra, Mystixor, Mebe, and Zai for sending me their old ManiaPlanet 3 score files, as they are very rare now due to their location in the hidden ProgramData folder. These archives helped to resolve some parsing issues too!