Skip to content

Commit

Permalink
Merge pull request #7 from HoLLy-HaCKeR/v2.0
Browse files Browse the repository at this point in the history
V2.0
  • Loading branch information
holly-hacker authored Aug 12, 2017
2 parents 67386a2 + 80bd4b0 commit 9bcfdc6
Show file tree
Hide file tree
Showing 33 changed files with 897 additions and 265 deletions.
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
[![CodeFactor](https://www.codefactor.io/repository/github/holly-hacker/osu-database-reader/badge)](https://www.codefactor.io/repository/github/holly-hacker/osu-database-reader)
# osu-database-reader
Allows for parsing/reading osu!'s database files

In the future, this will read more file formats, and likely write them too.

NOTE: No parsing for storyboards, use [osuElements](https://github.com/JasperDeSutter/osuElements) for that.

### Currently finished:
* reading osu!.db
* reading collection.db
* reading scores.db
* reading presence.db
* reading replays
* reading osu! beatmaps

### Planned:
* reading osu! beatmaps (maybe?)
* writing osu!.db
* writing collection.db
* writing scores.db
Expand Down
64 changes: 64 additions & 0 deletions UnitTestProject/SharedCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTestProject
{
public static class SharedCode
{
public static readonly string PathOsu;

static SharedCode()
{
//find osu! installation
if (!Directory.Exists(PathOsu = $@"C:\Users\{Environment.UserName}\AppData\Local\osu!\") //current install dir
&& !Directory.Exists(PathOsu = @"C:\Program Files (x86)\osu!\") //old install dir (x64-based)
&& !Directory.Exists(PathOsu = @"C:\Program Files\osu!\")) { //old install dir (x86-based)
PathOsu = string.Empty; //if none of the previous paths exist
}
//TODO: allow dev to create file with custom location
}

public static void PreTestCheck()
{
if (string.IsNullOrEmpty(PathOsu))
Assert.Inconclusive("osu! installation directory not found.");
}

public static string GetRelativeFile(string rel, bool shouldError = false)
{
string absolute = Path.Combine(PathOsu, rel);
if (!File.Exists(absolute)) {
if (shouldError)
Assert.Fail($"File does not exist: {absolute}");
else
Assert.Inconclusive($"File does not exist: {absolute}");
}
return absolute;
}

public static string GetRelativeDirectory(string rel, bool shouldError = false)
{
string absolute = Path.Combine(PathOsu, rel);
if (!Directory.Exists(absolute))
{
if (shouldError)
Assert.Fail($"Directory does not exist: {absolute}");
else
Assert.Inconclusive($"Directory does not exist: {absolute}");
}
return absolute;
}

public static bool VerifyFileChecksum(string path, string md5)
{
string hash = string.Join(string.Empty, MD5.Create().ComputeHash(File.OpenRead(path)).Select(a => a.ToString("X2")));
return hash == md5.ToUpper();
}
}
}
99 changes: 99 additions & 0 deletions UnitTestProject/TestsBinary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using osu_database_reader.BinaryFiles;
using osu_database_reader.Components.Player;

namespace UnitTestProject
{
[TestClass]
public class TestsBinary
{
[TestInitialize]
public void Init()
{
SharedCode.PreTestCheck();
}

[TestMethod]
public void ReadOsuDb()
{
OsuDb db = OsuDb.Read(SharedCode.GetRelativeFile("osu!.db"));
Debug.WriteLine("Version: " + db.OsuVersion);
Debug.WriteLine("Amount of beatmaps: " + db.Beatmaps.Count);
Debug.WriteLine($"Account name: \"{db.AccountName}\" (account {(db.AccountUnlocked ? "not locked" : "locked, unlocked at " + db.AccountUnlockDate)})");
Debug.WriteLine("Account rank: " + db.AccountRank);
for (int i = 0; i < Math.Min(10, db.Beatmaps.Count); i++) { //print 10 at most
var b = db.Beatmaps[i];
Debug.WriteLine($"{b.Artist} - {b.Title} [{b.Version}]");
}
}

[TestMethod]
public void ReadCollectionDb()
{
CollectionDb db = CollectionDb.Read(SharedCode.GetRelativeFile("collection.db"));
Debug.WriteLine("Version: " + db.OsuVersion);
Debug.WriteLine("Amount of collections: " + db.Collections.Count);
foreach (var c in db.Collections) {
Debug.WriteLine($" - Collection {c.Name} has {c.BeatmapHashes.Count} item" + (c.BeatmapHashes.Count == 1 ? "" : "s"));
}
}

[TestMethod]
public void ReadScoresDb()
{
ScoresDb db = ScoresDb.Read(SharedCode.GetRelativeFile("scores.db"));
Debug.WriteLine("Version: " + db.OsuVersion);
Debug.WriteLine("Amount of beatmaps: " + db.Beatmaps.Count);
Debug.WriteLine("Amount of scores: " + db.Scores.Count());

string[] keys = db.Beatmaps.Keys.ToArray();
for (int i = 0; i < Math.Min(25, keys.Length); i++) { //print 25 at most
string md5 = keys[i];
List<Replay> replays = db.Beatmaps[md5];

Debug.WriteLine($"Beatmap with md5 {md5} has replays:");
for (int j = 0; j < Math.Min(5, replays.Count); j++) { //again, 5 at most
var r = replays[j];
Debug.WriteLine($"\tReplay by {r.PlayerName}, for {r.Score} score with {r.Combo}x combo. Played at {r.TimePlayed}");
}
}
}

[TestMethod]
public void ReadPresenceDb()
{
var db = PresenceDb.Read(SharedCode.GetRelativeFile("presence.db"));
Debug.WriteLine("Version: " + db.OsuVersion);
Debug.WriteLine("Amount of scores: " + db.Players.Count);

for (int i = 0; i < Math.Min(db.Players.Count, 10); i++) { //10 at most
var p = db.Players[i];
Debug.WriteLine($"Player {p.PlayerName} lives at long {p.Longitude} and lat {p.Latitude}. Some DateTime: {p.Unknown1}. Rank: {p.PlayerRank}. {p.GameMode}, #{p.GlobalRank}, id {p.PlayerId}");
}
}

[TestMethod]
public void ReadReplay()
{
//get random file
string path = SharedCode.GetRelativeDirectory("Replays");
string[] files = Directory.GetFiles(path);

if (files.Length == 0)
Assert.Inconclusive("No replays in your replay folder!");

for (int i = 0; i < Math.Min(files.Length, 10); i++) { //10 at most
var r = Replay.Read(files[i]);
Debug.WriteLine("Version: " + r.OsuVersion);
Debug.WriteLine("Beatmap hash: " + r.BeatmapHash);
Debug.WriteLine($"Replay by {r.PlayerName}, for {r.Score} score with {r.Combo}x combo. Played at {r.TimePlayed}");
Debug.WriteLine("");
}
}
}
}
45 changes: 45 additions & 0 deletions UnitTestProject/TestsCombined.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using System;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using osu_database_reader.BinaryFiles;
using osu_database_reader.TextFiles;

namespace UnitTestProject
{
[TestClass]
public class TestsCombined
{
[TestInitialize]
public void Init()
{
SharedCode.PreTestCheck();
}

[TestMethod]
public void CheckBeatmapsAgainstDb()
{
OsuDb db = OsuDb.Read(SharedCode.GetRelativeFile("osu!.db"));

for (var i = 0; i < Math.Min(db.Beatmaps.Count, 50); i++) {
var entry = db.Beatmaps[i];

Debug.WriteLine($"Going to read beatmap at /{entry.FolderName}/{entry.BeatmapFileName}");

//read beatmap
BeatmapFile bm = BeatmapFile.Read(SharedCode.GetRelativeFile($"Songs/{entry.FolderName}/{entry.BeatmapFileName}", true));
//BUG: this can still fail when maps use the hold note (used in some mania maps?)

Assert.IsTrue(bm.SectionGeneral.Count >= 2); //disco prince only has 2
Assert.IsTrue(bm.SectionMetadata.Count >= 4); //disco prince only has 4
Assert.IsTrue(bm.SectionDifficulty.Count >= 5); //disco prince only has 5

Assert.AreEqual(entry.Artist, bm.Artist);
Assert.AreEqual(entry.Version, bm.Version);
Assert.AreEqual(entry.Creator, bm.Creator);
Assert.AreEqual(entry.Title, bm.Title);

//TODO: more, but check if the entries are present in the beatmap
}
}
}
}
108 changes: 108 additions & 0 deletions UnitTestProject/TestsText.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
using System;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using osu_database_reader;
using osu_database_reader.Components.HitObjects;
using osu_database_reader.TextFiles;

namespace UnitTestProject
{
[TestClass]
public class TestsText
{
[TestInitialize]
public void Init()
{
SharedCode.PreTestCheck();
}

[TestMethod]
public void VerifyBigBlack()
{
//most people should have this map
string beatmap = SharedCode.GetRelativeFile(@"Songs\41823 The Quick Brown Fox - The Big Black\The Quick Brown Fox - The Big Black (Blue Dragon) [WHO'S AFRAID OF THE BIG BLACK].osu");

if (!File.Exists(beatmap))
Assert.Inconclusive("Hardcoded path does not exist: " + beatmap);
if (!SharedCode.VerifyFileChecksum(beatmap, "2D687E5EE79F3862AD0C60651471CDCC"))
Assert.Inconclusive("Beatmap was modified.");

var bm = BeatmapFile.Read(beatmap);

Assert.AreEqual(bm.FileFormatVersion, 9);

//check General
Assert.AreEqual(bm.SectionGeneral["AudioFilename"], "02 The Big Black.mp3");
Assert.AreEqual(bm.SectionGeneral["AudioLeadIn"], "0");
Assert.AreEqual(bm.SectionGeneral["PreviewTime"], "18957");

//check Metadata
Assert.AreEqual(bm.Title, "The Big Black");
Assert.AreEqual(bm.Artist, "The Quick Brown Fox");
Assert.AreEqual(bm.Creator, "Blue Dragon");
Assert.AreEqual(bm.Version, "WHO'S AFRAID OF THE BIG BLACK");
Assert.AreEqual(bm.Source, string.Empty);
Assert.IsTrue(Enumerable.SequenceEqual(bm.Tags, new[] { "Onosakihito", "speedcore", "renard", "lapfox" }));

//check Difficulty
Assert.AreEqual(bm.HPDrainRate, 5f);
Assert.AreEqual(bm.CircleSize, 4f);
Assert.AreEqual(bm.OverallDifficulty, 7f);
Assert.AreEqual(bm.ApproachRate, 10f);
Assert.AreEqual(bm.SliderMultiplier, 1.8f);
Assert.AreEqual(bm.SliderTickRate, 2f);

//check Events
//TODO

//check TimingPoints
Assert.AreEqual(bm.TimingPoints.Count, 5);
Assert.AreEqual(bm.TimingPoints[0].Time, 6966);
Assert.AreEqual(bm.TimingPoints[1].Kiai, true);
Assert.AreEqual(bm.TimingPoints[2].MsPerQuarter, -100); //means no timing change
Assert.AreEqual(bm.TimingPoints[3].TimingChange, false);

//check Colours
//Combo1 : 249,91,9
//(...)
//Combo5 : 255,255,128
Assert.AreEqual(bm.SectionColours["Combo1"], "249,91,91");
Assert.AreEqual(bm.SectionColours["Combo5"], "255,255,128");
Assert.AreEqual(bm.SectionColours.Count, 5);

//check HitObjects
Assert.AreEqual(bm.HitObjects.Count, 746);
Assert.AreEqual(bm.HitObjects.Count(a => a is HitObjectCircle), 410);
Assert.AreEqual(bm.HitObjects.Count(a => a is HitObjectSlider), 334);
Assert.AreEqual(bm.HitObjects.Count(a => a is HitObjectSpinner), 2);

//56,56,6966,1,4
HitObjectCircle firstCircle = (HitObjectCircle) bm.HitObjects.First(a => a.Type.HasFlag(HitObjectType.Normal));
Assert.AreEqual(firstCircle.X, 56);
Assert.AreEqual(firstCircle.Y, 56);
Assert.AreEqual(firstCircle.Time, 6966);
Assert.AreEqual(firstCircle.HitSound, HitSound.Finish);

//178,50,7299,2,0,B|210:0|300:0|332:50,1,180,2|0
HitObjectSlider firstSlider = (HitObjectSlider)bm.HitObjects.First(a => a.Type.HasFlag(HitObjectType.Slider));
Assert.AreEqual(firstSlider.X, 178);
Assert.AreEqual(firstSlider.Y, 50);
Assert.AreEqual(firstSlider.Time, 7299);
Assert.AreEqual(firstSlider.HitSound, HitSound.None);
Assert.AreEqual(firstSlider.CurveType, CurveType.Bezier);
Assert.AreEqual(firstSlider.Points.Count, 3);
Assert.AreEqual(firstSlider.RepeatCount, 1);
Assert.AreEqual(firstSlider.Length, 180);

//256,192,60254,12,4,61587
HitObjectSpinner firstSpinner = (HitObjectSpinner)bm.HitObjects.First(a => a.Type.HasFlag(HitObjectType.Spinner));
Assert.AreEqual(firstSpinner.X, 256);
Assert.AreEqual(firstSpinner.Y, 192);
Assert.AreEqual(firstSpinner.Time, 60254);
Assert.AreEqual(firstSpinner.HitSound, HitSound.Finish);
Assert.AreEqual(firstSpinner.EndTime, 61587);
Assert.IsTrue(firstSpinner.IsNewCombo);
}
}
}
Loading

0 comments on commit 9bcfdc6

Please sign in to comment.