├── UnitTestProject
├── Data
│ ├── Collection
│ │ ├── 20200811_empty.db
│ │ └── 20210316.db
│ ├── osu!
│ │ ├── 20210316.db
│ │ └── 20250108.db
│ ├── Scores
│ │ └── 20210316.db
│ ├── Presence
│ │ ├── 20210316.db
│ │ └── 20210316_empty.db
│ ├── Replays
│ │ └── 20210316.osr
│ └── Beatmaps
│ │ ├── 128.osu
│ │ ├── beatmap_v14.osu
│ │ └── 4615284.osu
├── UnitTestProject.csproj
├── ReadWriteMatchTests.cs
├── TestsText.cs
└── TestsBinary.cs
├── osu-database-reader
├── packages.config
├── Components
│ ├── HitObjects
│ │ ├── HitObjectHold.cs
│ │ ├── HitObjectCircle.cs
│ │ ├── HitObjectSpinner.cs
│ │ ├── HitObjectSlider.cs
│ │ └── HitObject.cs
│ ├── Vector2.cs
│ ├── Events
│ │ ├── BackgroundEvent.cs
│ │ ├── VideoEvent.cs
│ │ ├── BreakEvent.cs
│ │ ├── SampleEvent.cs
│ │ ├── SpriteEvent.cs
│ │ ├── AnimationEvent.cs
│ │ └── EventBase.cs
│ ├── Beatmaps
│ │ ├── Collection.cs
│ │ ├── TimingPoint.cs
│ │ └── BeatmapEntry.cs
│ └── Player
│ │ ├── ReplayFrame.cs
│ │ ├── PlayerPresence.cs
│ │ └── Replay.cs
├── Constants.cs
├── osu-database-reader.csproj.DotSettings
├── 7zip
│ ├── Compress
│ │ ├── LZ
│ │ │ ├── IMatchFinder.cs
│ │ │ ├── LzOutWindow.cs
│ │ │ ├── LzInWindow.cs
│ │ │ └── LzBinTree.cs
│ │ ├── RangeCoder
│ │ │ ├── Decoder.cs
│ │ │ ├── Encoder.cs
│ │ │ ├── RangeCoderBit.cs
│ │ │ └── RangeCoderBitTree.cs
│ │ └── LZMA
│ │ │ ├── LzmaBase.cs
│ │ │ └── LzmaDecoder.cs
│ ├── Common
│ │ └── CRC.cs
│ ├── LZMACoder.cs
│ └── ICoder.cs
├── Enums.cs
├── OsuVersions.cs
├── BinaryFiles
│ ├── PresenceDb.cs
│ ├── CollectionDb.cs
│ ├── ScoresDb.cs
│ └── OsuDb.cs
├── osu-database-reader.csproj
├── TextFiles
│ └── BeatmapFile.cs
└── Extensions.cs
├── osu-database-reader.SourceGenerator
├── osu-database-reader.SourceGenerator.csproj
├── Data
│ └── BeatmapProperties.toml
└── BeatmapPropertyGenerator.cs
├── osu-database-reader.sln.DotSettings
├── LICENSE
├── README.md
├── osu-database-reader.sln
├── .gitattributes
└── .gitignore
/UnitTestProject/Data/Collection/20200811_empty.db:
--------------------------------------------------------------------------------
1 | k=4
--------------------------------------------------------------------------------
/UnitTestProject/Data/osu!/20210316.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/holly-hacker/osu-database-reader/HEAD/UnitTestProject/Data/osu!/20210316.db
--------------------------------------------------------------------------------
/UnitTestProject/Data/osu!/20250108.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/holly-hacker/osu-database-reader/HEAD/UnitTestProject/Data/osu!/20250108.db
--------------------------------------------------------------------------------
/UnitTestProject/Data/Scores/20210316.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/holly-hacker/osu-database-reader/HEAD/UnitTestProject/Data/Scores/20210316.db
--------------------------------------------------------------------------------
/UnitTestProject/Data/Presence/20210316.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/holly-hacker/osu-database-reader/HEAD/UnitTestProject/Data/Presence/20210316.db
--------------------------------------------------------------------------------
/UnitTestProject/Data/Replays/20210316.osr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/holly-hacker/osu-database-reader/HEAD/UnitTestProject/Data/Replays/20210316.osr
--------------------------------------------------------------------------------
/UnitTestProject/Data/Collection/20210316.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/holly-hacker/osu-database-reader/HEAD/UnitTestProject/Data/Collection/20210316.db
--------------------------------------------------------------------------------
/UnitTestProject/Data/Presence/20210316_empty.db:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/holly-hacker/osu-database-reader/HEAD/UnitTestProject/Data/Presence/20210316_empty.db
--------------------------------------------------------------------------------
/osu-database-reader/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/HitObjects/HitObjectHold.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader.Components.HitObjects
2 | {
3 | public class HitObjectHold : HitObject
4 | {
5 | public int EndTime;
6 | public string SoundSampleData;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/HitObjects/HitObjectCircle.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader.Components.HitObjects
2 | {
3 | public class HitObjectCircle : HitObject
4 | {
5 | public string SoundSampleData; //TODO: parse all sound sample data
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/HitObjects/HitObjectSpinner.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader.Components.HitObjects
2 | {
3 | public class HitObjectSpinner : HitObject
4 | {
5 | public int EndTime;
6 | public string SoundSampleData;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/Vector2.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader.Components
2 | {
3 | public struct Vector2
4 | {
5 | public int X, Y;
6 |
7 | public Vector2(int x, int y)
8 | {
9 | X = x;
10 | Y = y;
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/Events/BackgroundEvent.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader.Components.Events
2 | {
3 | public class BackgroundEvent : EventBase
4 | {
5 | internal BackgroundEvent()
6 | {
7 | }
8 |
9 | public string Path { get; internal set; }
10 | }
11 | }
--------------------------------------------------------------------------------
/osu-database-reader/Constants.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace osu_database_reader
4 | {
5 | internal static class Constants
6 | {
7 | //nfi so parsing works on all cultures
8 | public static readonly NumberFormatInfo NumberFormat = new CultureInfo(@"en-US", false).NumberFormat;
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/Events/VideoEvent.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader.Components.Events
2 | {
3 | public class VideoEvent : EventBase
4 | {
5 | internal VideoEvent()
6 | {
7 | }
8 |
9 | public int Offset { get; internal set; }
10 | public string Path { get; internal set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/osu-database-reader/Components/Events/BreakEvent.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader.Components.Events
2 | {
3 | public class BreakEvent : EventBase
4 | {
5 | internal BreakEvent()
6 | {
7 | }
8 |
9 | public double StartTime { get; internal set; }
10 | public double EndTime { get; internal set; }
11 |
12 | public double BreakTime => EndTime - StartTime;
13 | }
14 | }
--------------------------------------------------------------------------------
/osu-database-reader/Components/Events/SampleEvent.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader.Components.Events
2 | {
3 | public class SampleEvent : EventBase
4 | {
5 | internal SampleEvent()
6 | {
7 | }
8 |
9 | public double Time { get; internal set; }
10 | public string Layer { get; internal set; }
11 | public string Path { get; internal set; }
12 | public float? Volume { get; internal set; }
13 | }
14 | }
--------------------------------------------------------------------------------
/osu-database-reader/osu-database-reader.csproj.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
--------------------------------------------------------------------------------
/osu-database-reader/Components/Events/SpriteEvent.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader.Components.Events
2 | {
3 | public class SpriteEvent : EventBase
4 | {
5 | internal SpriteEvent()
6 | {
7 | }
8 |
9 | public string Layer { get; internal set; }
10 | public string Origin { get; internal set; }
11 | public string Path { get; internal set; }
12 | public float X { get; internal set; }
13 | public float Y { get; internal set; }
14 | }
15 | }
--------------------------------------------------------------------------------
/osu-database-reader/Components/Events/AnimationEvent.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader.Components.Events
2 | {
3 | public class AnimationEvent : EventBase
4 | {
5 | internal AnimationEvent()
6 | {
7 | }
8 |
9 | public string Layer { get; internal set; }
10 | public string Origin { get; internal set; }
11 | public string Path { get; internal set; }
12 | public float X { get; internal set; }
13 | public float Y { get; internal set; }
14 | public int FrameCount { get; internal set; }
15 | public double FrameDelay { get; internal set; }
16 | public string LoopType { get; internal set; }
17 | }
18 | }
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Compress/LZ/IMatchFinder.cs:
--------------------------------------------------------------------------------
1 | // IMatchFinder.cs
2 |
3 | using System;
4 |
5 | namespace SevenZip.Compression.LZ
6 | {
7 | interface IInWindowStream
8 | {
9 | void SetStream(System.IO.Stream inStream);
10 | void Init();
11 | void ReleaseStream();
12 | Byte GetIndexByte(Int32 index);
13 | UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit);
14 | UInt32 GetNumAvailableBytes();
15 | }
16 |
17 | interface IMatchFinder : IInWindowStream
18 | {
19 | void Create(UInt32 historySize, UInt32 keepAddBufferBefore,
20 | UInt32 matchMaxLen, UInt32 keepAddBufferAfter);
21 | UInt32 GetMatches(UInt32[] distances);
22 | void Skip(UInt32 num);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/osu-database-reader/Enums.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace osu_database_reader
4 | {
5 | public enum CurveType
6 | {
7 | Linear,
8 | Catmull,
9 | Bezier,
10 | Perfect,
11 | }
12 |
13 | public enum BeatmapSection
14 | {
15 | General,
16 | Editor,
17 | Metadata,
18 | Difficulty,
19 | Events,
20 | TimingPoints,
21 | Colours,
22 | HitObjects,
23 | }
24 |
25 | [Flags]
26 | public enum Keys
27 | {
28 | None = 0,
29 | M1 = (1 << 0),
30 | M2 = (1 << 1),
31 | K1 = (1 << 2) + M1,
32 | K2 = (1 << 3) + M2,
33 | Smoke = 16,
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/osu-database-reader.SourceGenerator/osu-database-reader.SourceGenerator.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0
5 | osu_database_reader.SourceGenerator
6 | 9
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/osu-database-reader.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | AR
3 | CS
4 | HP
5 | OD
6 | True
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Common/CRC.cs:
--------------------------------------------------------------------------------
1 | // Common/CRC.cs
2 |
3 | namespace SevenZip
4 | {
5 | class CRC
6 | {
7 | public static readonly uint[] Table;
8 |
9 | static CRC()
10 | {
11 | Table = new uint[256];
12 | const uint kPoly = 0xEDB88320;
13 | for (uint i = 0; i < 256; i++)
14 | {
15 | uint r = i;
16 | for (int j = 0; j < 8; j++)
17 | if ((r & 1) != 0)
18 | r = (r >> 1) ^ kPoly;
19 | else
20 | r >>= 1;
21 | Table[i] = r;
22 | }
23 | }
24 |
25 | uint _value = 0xFFFFFFFF;
26 |
27 | public void Init() { _value = 0xFFFFFFFF; }
28 |
29 | public void UpdateByte(byte b)
30 | {
31 | _value = Table[(((byte)(_value)) ^ b)] ^ (_value >> 8);
32 | }
33 |
34 | public void Update(byte[] data, uint offset, uint size)
35 | {
36 | for (uint i = 0; i < size; i++)
37 | _value = Table[(((byte)(_value)) ^ data[offset + i])] ^ (_value >> 8);
38 | }
39 |
40 | public uint GetDigest() { return _value ^ 0xFFFFFFFF; }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/Beatmaps/Collection.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using osu.Shared.Serialization;
3 |
4 | namespace osu_database_reader.Components.Beatmaps
5 | {
6 | public class Collection : ISerializable
7 | {
8 | public string Name;
9 | public List BeatmapHashes;
10 |
11 | public void ReadFromStream(SerializationReader r)
12 | {
13 | Name = r.ReadString();
14 |
15 | BeatmapHashes = new List();
16 | int amount = r.ReadInt32();
17 | for (int j = 0; j < amount; j++)
18 | BeatmapHashes.Add(r.ReadString());
19 | }
20 |
21 | public void WriteToStream(SerializationWriter w)
22 | {
23 | w.Write(Name);
24 | w.Write(BeatmapHashes.Count);
25 |
26 | foreach (string beatmapHash in BeatmapHashes)
27 | {
28 | w.Write(beatmapHash);
29 | }
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 HoLLy
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/osu-database-reader/OsuVersions.cs:
--------------------------------------------------------------------------------
1 | namespace osu_database_reader
2 | {
3 | internal static class OsuVersions
4 | {
5 | public const int EntryLengthInOsuDbMin = 20160408;
6 | public const int EntryLengthInOsuDbMax = 20191107;
7 |
8 | ///
9 | /// First version where osu!.db stores cached star rating as 32-bit floats instead of 64-bit doubles
10 | ///
11 | public const int ReducedOsuDbSize = 20250108;
12 |
13 | ///
14 | /// First version where osu!.db:
15 | /// - uses floats for difficulty values
16 | /// - contains pre-calculated difficulty ratings
17 | ///
18 | public const int FloatDifficultyValues = 20140609;
19 |
20 | ///
21 | /// First version where:
22 | /// - Replays contain a 32-bit score id
23 | /// - osu!.db contains ArtistUnicode and TitleUnicode
24 | ///
25 | public const int FirstOsz2 = 20121008;
26 |
27 | ///
28 | /// First version where replays contain a 64-bit score id, instead of 32-bit
29 | ///
30 | public const int ReplayScoreId64Bit = 20140721;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/UnitTestProject/UnitTestProject.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | net8.0
4 |
5 |
6 |
7 |
8 |
9 |
10 | all
11 | runtime; build; native; contentfiles; analyzers; buildtransitive
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.codefactor.io/repository/github/holly-hacker/osu-database-reader)
2 | [](https://www.nuget.org/packages/HoLLy.osu.DatabaseReader)
3 | # osu-database-reader
4 | Allows for parsing/reading osu!'s database files
5 |
6 | ## Features
7 | - Read/Write .db files
8 | - Read/Write replay files (excluding score checksum calculation)
9 | - Read beatmap files
10 |
11 | For parsing storyboards, use [osuElements](https://github.com/JasperDeSutter/osuElements) or wait for a future release.
12 |
13 | ## Usage
14 | Add to your project and include the osu_database_reader namespace.
15 | See unit tests for more detailed usage.
16 |
17 | ## Installation
18 | I recommend installing it through NuGet. You can use the built-in package manager in Visual Studio 2017 or use the package manager console:
19 |
20 | > Install-Package HoLLy.osu.DatabaseReader
21 |
22 | Or, if you use .NET Core (2.0+):
23 |
24 | > dotnet add package HoLLy.osu.DatabaseReader
25 |
26 | ## Credits
27 | This project includes the 7zip LZMA SDK, which is released in the public domain.
28 | [Tomlyn](https://github.com/dezhidki/Tommy) is used during code generation, and is licensed under the MIT license.
29 |
--------------------------------------------------------------------------------
/osu-database-reader.SourceGenerator/Data/BeatmapProperties.toml:
--------------------------------------------------------------------------------
1 | # this file contains a definition for all common properties in a .osu file
2 | # source code gets generated from this
3 | # taken from https://github.com/ppy/osu/blob/53ae24db9b40895c08fed45e1fb2650533ae96c6/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs
4 |
5 | namespace = "osu_database_reader.TextFiles"
6 | class = "BeatmapFile"
7 |
8 | [General]
9 | AudioFilename = "string"
10 | AudioLeadIn = "int"
11 | PreviewTime = "int"
12 | Countdown = "intbool"
13 | SampleSet = "string" # TODO: "enum:SampleSet"
14 | SampleVolume = "int"
15 | StackLeniency = "float"
16 | Mode = "int" # TODO: cast int to enum?
17 | LetterboxInBreaks = "intbool"
18 | SpecialStyle = "intbool"
19 | WidescreenStoryboard = "intbool"
20 | EpilepsyWarning = "intbool"
21 |
22 | [Editor]
23 | Bookmarks = "int[]"
24 | DistanceSpacing = "double"
25 | BeatDivisor = "int"
26 | GridSize = "int"
27 | TimelineZoom = "double"
28 |
29 | [Metadata]
30 | Title = "string"
31 | TitleUnicode = "string"
32 | Artist = "string"
33 | ArtistUnicode = "string"
34 | Creator = "string"
35 | Version = "string"
36 | Source = "string"
37 | Tags = "string"
38 | BeatmapID = "int"
39 | BeatmapSetID = "int"
40 |
41 | [Difficulty]
42 | HPDrainRate = "float"
43 | CircleSize = "float"
44 | OverallDifficulty = "float"
45 | ApproachRate = "float"
46 | SliderMultiplier = "double"
47 | SliderTickRate = "double"
48 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/Player/ReplayFrame.cs:
--------------------------------------------------------------------------------
1 | using System.Globalization;
2 |
3 | namespace osu_database_reader.Components.Player
4 | {
5 | public struct ReplayFrame
6 | {
7 | public float X;
8 | public float Y;
9 | public int TimeDiff;
10 | public int TimeAbs;
11 | public Keys Keys;
12 |
13 | internal ReplayFrame(string line, ref int offset)
14 | {
15 | var splitted = line.Split('|');
16 |
17 | TimeDiff = int.Parse(splitted[0], CultureInfo.InvariantCulture);
18 | TimeAbs = offset += TimeDiff;
19 | X = float.Parse(splitted[1], CultureInfo.InvariantCulture);
20 | Y = float.Parse(splitted[2], CultureInfo.InvariantCulture);
21 | Keys = (Keys)int.Parse(splitted[3]);
22 | }
23 |
24 | public override string ToString() => $"{TimeDiff}|{X.ToString(CultureInfo.InvariantCulture)}|{Y.ToString(CultureInfo.InvariantCulture)}|{(int)Keys}";
25 |
26 | internal static ReplayFrame[] FromStrings(ref string str)
27 | {
28 | var splitted = str.Split(',');
29 | var arr = new ReplayFrame[splitted.Length];
30 |
31 | int lastOffset = 0;
32 | for (int i = 0; i < splitted.Length; i++)
33 | if (!string.IsNullOrEmpty(splitted[i]))
34 | arr[i] = new ReplayFrame(splitted[i], ref lastOffset);
35 |
36 | return arr;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/osu-database-reader/BinaryFiles/PresenceDb.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using osu.Shared.Serialization;
4 | using osu_database_reader.Components.Player;
5 |
6 | namespace osu_database_reader.BinaryFiles
7 | {
8 | public class PresenceDb : ISerializable
9 | {
10 | public int OsuVersion;
11 | public readonly List Players = new();
12 |
13 | public static PresenceDb Read(string path)
14 | {
15 | using var stream = File.OpenRead(path);
16 | return Read(stream);
17 | }
18 |
19 | public static PresenceDb Read(Stream stream)
20 | {
21 | var db = new PresenceDb();
22 | using var r = new SerializationReader(stream);
23 | db.ReadFromStream(r);
24 | return db;
25 | }
26 |
27 | public void ReadFromStream(SerializationReader r)
28 | {
29 | OsuVersion = r.ReadInt32();
30 | int amount = r.ReadInt32();
31 |
32 | for (int i = 0; i < amount; i++)
33 | Players.Add(PlayerPresence.ReadFromReader(r));
34 | }
35 |
36 | public void WriteToStream(SerializationWriter w)
37 | {
38 | w.Write(OsuVersion);
39 | w.Write(Players.Count);
40 |
41 | foreach (var playerPresence in Players)
42 | {
43 | playerPresence.WriteToStream(w);
44 | }
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/osu-database-reader/BinaryFiles/CollectionDb.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using osu.Shared.Serialization;
4 | using osu_database_reader.Components.Beatmaps;
5 |
6 | namespace osu_database_reader.BinaryFiles
7 | {
8 | public class CollectionDb : ISerializable
9 | {
10 | public int OsuVersion;
11 | public readonly List Collections = new();
12 |
13 | public static CollectionDb Read(string path)
14 | {
15 | using var stream = File.OpenRead(path);
16 | return Read(stream);
17 | }
18 |
19 | public static CollectionDb Read(Stream stream)
20 | {
21 | var db = new CollectionDb();
22 | using var r = new SerializationReader(stream);
23 | db.ReadFromStream(r);
24 | return db;
25 | }
26 |
27 | public void ReadFromStream(SerializationReader r)
28 | {
29 | OsuVersion = r.ReadInt32();
30 | int amount = r.ReadInt32();
31 |
32 | for (int i = 0; i < amount; i++) {
33 | var c = new Collection();
34 | c.ReadFromStream(r);
35 | Collections.Add(c);
36 | }
37 | }
38 |
39 | public void WriteToStream(SerializationWriter w)
40 | {
41 | w.Write(OsuVersion);
42 | w.Write(Collections.Count);
43 |
44 | foreach (var collection in Collections)
45 | {
46 | collection.WriteToStream(w);
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/HitObjects/HitObjectSlider.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.Diagnostics;
3 |
4 | namespace osu_database_reader.Components.HitObjects
5 | {
6 | public class HitObjectSlider : HitObject
7 | {
8 | public CurveType CurveType;
9 | /// The points on this slider's curve, excluding the starting points.
10 | public List Points = new();
11 | public int RepeatCount;
12 | /// Length of the slider in osu!pixels.
13 | public double Length;
14 |
15 | public void ParseSliderSegments(string sliderString)
16 | {
17 | string[] split = sliderString.Split('|');
18 | foreach (var s in split) {
19 | if (s.Length == 1)
20 | {
21 | //curve type
22 | CurveType = s[0] switch
23 | {
24 | 'L' => CurveType.Linear,
25 | 'C' => CurveType.Catmull,
26 | 'P' => CurveType.Perfect,
27 | 'B' => CurveType.Bezier,
28 | _ => CurveType,
29 | };
30 | continue;
31 | }
32 | string[] split2 = s.Split(':');
33 | Debug.Assert(split2.Length == 2);
34 |
35 | Points.Add(new Vector2(
36 | (int)double.Parse(split2[0]),
37 | (int)double.Parse(split2[1])));
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/osu-database-reader/osu-database-reader.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | 9
4 | netstandard2.0
5 | true
6 |
7 | 3.3.0
8 | 3.3.0.0
9 | 3.3.0.0
10 | 3.3.0
11 |
12 | osu! Database Reader
13 | osu! Database Reader
14 | HoLLy
15 | A library to read almost all of osu!'s data files.
16 | Copyright © HoLLy
17 | HoLLy.osu.DatabaseReader
18 |
19 | Git
20 | https://github.com/holly-hacker/osu-database-reader/
21 | https://github.com/holly-hacker/osu-database-reader/
22 | https://github.com/HoLLy-HaCKeR/osu-database-reader/blob/master/LICENSE
23 | MIT
24 |
25 | osu! osu database replay beatmap
26 |
27 | - Parse hold notes for osu!mania maps
28 | - Fix written osu.db files not being readable by osu!
29 | - Improve compatibility with poorly formatted beatmaps
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/osu-database-reader/BinaryFiles/ScoresDb.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 | using System.IO;
3 | using System.Linq;
4 | using osu.Shared.Serialization;
5 | using osu_database_reader.Components.Player;
6 |
7 | namespace osu_database_reader.BinaryFiles
8 | {
9 | public class ScoresDb : ISerializable
10 | {
11 | public int OsuVersion;
12 | public readonly Dictionary> Beatmaps = new();
13 | public IEnumerable Scores => Beatmaps.SelectMany(a => a.Value);
14 |
15 | public static ScoresDb Read(string path)
16 | {
17 | using var stream = File.OpenRead(path);
18 | return Read(stream);
19 | }
20 |
21 | public static ScoresDb Read(Stream stream)
22 | {
23 | var db = new ScoresDb();
24 | using var r = new SerializationReader(stream);
25 | db.ReadFromStream(r);
26 |
27 | return db;
28 | }
29 |
30 | public void ReadFromStream(SerializationReader r)
31 | {
32 | OsuVersion = r.ReadInt32();
33 | int amount = r.ReadInt32();
34 |
35 | for (int i = 0; i < amount; i++)
36 | {
37 | string md5 = r.ReadString();
38 |
39 | var list = new List();
40 |
41 | int amount2 = r.ReadInt32();
42 | for (int j = 0; j < amount2; j++)
43 | list.Add(Replay.ReadFromReader(r));
44 |
45 | Beatmaps.Add(md5, list);
46 | }
47 | }
48 |
49 | public void WriteToStream(SerializationWriter w)
50 | {
51 | w.Write(OsuVersion);
52 | w.Write(Beatmaps.Count);
53 |
54 | foreach (var kvp in Beatmaps)
55 | {
56 | w.Write(kvp.Key);
57 | w.Write(kvp.Value.Count);
58 |
59 | foreach (var replay in kvp.Value)
60 | {
61 | replay.WriteToStream(w);
62 | }
63 | }
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/Player/PlayerPresence.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using osu.Shared;
4 | using osu.Shared.Serialization;
5 |
6 | namespace osu_database_reader.Components.Player
7 | {
8 | public class PlayerPresence : ISerializable
9 | {
10 | public int PlayerId;
11 | public string PlayerName;
12 | public byte UtcOffset; //need to substract 24 from this to be usable
13 | public byte CountryByte;
14 | public PlayerRank PlayerRank;
15 | public GameMode GameMode;
16 | public float Longitude, Latitude; //position in the world
17 | public int GlobalRank;
18 | public DateTime LastUpdate;
19 |
20 | public static PlayerPresence ReadFromReader(SerializationReader r) {
21 | var p = new PlayerPresence();
22 | p.ReadFromStream(r);
23 | return p;
24 | }
25 |
26 | public void ReadFromStream(SerializationReader r)
27 | {
28 | PlayerId = r.ReadInt32();
29 | PlayerName = r.ReadString();
30 | UtcOffset = r.ReadByte();
31 | CountryByte = r.ReadByte(); //TODO: create Country enum
32 |
33 | byte b = r.ReadByte();
34 | PlayerRank = (PlayerRank)(b & 0b0001_1111);
35 | GameMode = (GameMode)((b & 0b1110_0000) >> 5);
36 | Debug.Assert((byte)GameMode <= 3, $"GameMode is byte {(byte)GameMode}, should be between 0 and 3");
37 |
38 | Longitude = r.ReadSingle();
39 | Latitude = r.ReadSingle();
40 | GlobalRank = r.ReadInt32();
41 | LastUpdate = r.ReadDateTime();
42 | }
43 |
44 | public void WriteToStream(SerializationWriter w)
45 | {
46 | w.Write(PlayerId);
47 | w.Write(PlayerName);
48 | w.Write(UtcOffset);
49 | w.Write(CountryByte);
50 |
51 | int rankPart = (byte)PlayerRank & 0b0001_1111;
52 | int modePart = ((byte)GameMode << 5) & 0b1110_0000;
53 | w.Write((byte)(rankPart | modePart));
54 |
55 | w.Write(Longitude);
56 | w.Write(Latitude);
57 | w.Write(GlobalRank);
58 | w.Write(LastUpdate);
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/osu-database-reader.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26730.16
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu-database-reader", "osu-database-reader\osu-database-reader.csproj", "{C3078606-8055-4122-845D-7F9A0154883D}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UnitTestProject", "UnitTestProject\UnitTestProject.csproj", "{3F44AD82-C508-4D86-B022-DD1D40A9B88C}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu-database-reader.SourceGenerator", "osu-database-reader.SourceGenerator\osu-database-reader.SourceGenerator.csproj", "{0FAF0917-F419-4160-9594-EDFDF30636A4}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {C3078606-8055-4122-845D-7F9A0154883D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {C3078606-8055-4122-845D-7F9A0154883D}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {C3078606-8055-4122-845D-7F9A0154883D}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {C3078606-8055-4122-845D-7F9A0154883D}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {3F44AD82-C508-4D86-B022-DD1D40A9B88C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {3F44AD82-C508-4D86-B022-DD1D40A9B88C}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {3F44AD82-C508-4D86-B022-DD1D40A9B88C}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {3F44AD82-C508-4D86-B022-DD1D40A9B88C}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {0FAF0917-F419-4160-9594-EDFDF30636A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {0FAF0917-F419-4160-9594-EDFDF30636A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {0FAF0917-F419-4160-9594-EDFDF30636A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {0FAF0917-F419-4160-9594-EDFDF30636A4}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {9AD9F3C5-8A38-4189-B5EB-74CF6DD29C72}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/Beatmaps/TimingPoint.cs:
--------------------------------------------------------------------------------
1 | using osu.Shared.Serialization;
2 |
3 | namespace osu_database_reader.Components.Beatmaps
4 | {
5 | public class TimingPoint : ISerializable
6 | {
7 | public double Time, MsPerQuarter;
8 | public bool TimingChange;
9 |
10 | //only in .osu files
11 | public int? TimingSignature; // x/4 (eg. 4/4, 3/4)
12 | public int? SampleSet;
13 | public int? CustomSampleSet;
14 | public int? SampleVolume;
15 | public bool? Kiai;
16 |
17 | public static TimingPoint FromString(string line)
18 | {
19 | TimingPoint t = new TimingPoint();
20 |
21 | string[] splitted = line.Split(',');
22 |
23 | t.Time = double.Parse(splitted[0], Constants.NumberFormat);
24 | t.MsPerQuarter = double.Parse(splitted[1], Constants.NumberFormat);
25 |
26 | int temp;
27 |
28 | //lots of checks that are probably not needed. idc you're not my real dad.
29 | if (splitted.Length > 2) t.TimingSignature = (temp = int.Parse(splitted[2])) == 0 ? 4 : temp;
30 | if (splitted.Length > 3) t.SampleSet = int.Parse(splitted[3]);
31 | if (splitted.Length > 4) t.CustomSampleSet = int.Parse(splitted[4]);
32 | if (splitted.Length > 5) t.SampleVolume = int.Parse(splitted[5]);
33 | if (splitted.Length > 6) t.TimingChange = int.Parse(splitted[6]) == 1;
34 | if (splitted.Length > 7)
35 | {
36 | temp = int.Parse(splitted[7]);
37 | t.Kiai = (temp & 1) != 0;
38 | }
39 |
40 | return t;
41 | }
42 |
43 | public static TimingPoint ReadFromReader(SerializationReader r)
44 | {
45 | var t = new TimingPoint();
46 | t.ReadFromStream(r);
47 | return t;
48 | }
49 |
50 | public void ReadFromStream(SerializationReader r)
51 | {
52 | MsPerQuarter = r.ReadDouble();
53 | Time = r.ReadDouble();
54 | TimingChange = r.ReadBoolean();
55 | }
56 |
57 | public void WriteToStream(SerializationWriter w)
58 | {
59 | w.Write(MsPerQuarter);
60 | w.Write(Time);
61 | w.Write(TimingChange);
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Compress/RangeCoder/Decoder.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 |
3 | namespace SevenZip.Compression.RangeCoder
4 | {
5 | class Decoder
6 | {
7 | public const uint kTopValue = (1 << 24);
8 | public uint Range;
9 | public uint Code;
10 | public Stream Stream;
11 |
12 | public void Init(System.IO.Stream stream)
13 | {
14 | // Stream.Init(stream);
15 | Stream = stream;
16 |
17 | Code = 0;
18 | Range = 0xFFFFFFFF;
19 | for (int i = 0; i < 5; i++)
20 | Code = (Code << 8) | (byte)Stream.ReadByte();
21 | }
22 |
23 | public void ReleaseStream()
24 | {
25 | // Stream.ReleaseStream();
26 | Stream = null;
27 | }
28 |
29 | public void CloseStream()
30 | {
31 | Stream.Close();
32 | }
33 |
34 | public void Normalize()
35 | {
36 | while (Range < kTopValue)
37 | {
38 | Code = (Code << 8) | (byte)Stream.ReadByte();
39 | Range <<= 8;
40 | }
41 | }
42 |
43 | public void Normalize2()
44 | {
45 | if (Range < kTopValue)
46 | {
47 | Code = (Code << 8) | (byte)Stream.ReadByte();
48 | Range <<= 8;
49 | }
50 | }
51 |
52 | public uint GetThreshold(uint total)
53 | {
54 | return Code / (Range /= total);
55 | }
56 |
57 | public void Decode(uint start, uint size, uint total)
58 | {
59 | Code -= start * Range;
60 | Range *= size;
61 | Normalize();
62 | }
63 |
64 | public uint DecodeDirectBits(int numTotalBits)
65 | {
66 | uint range = Range;
67 | uint code = Code;
68 | uint result = 0;
69 | for (int i = numTotalBits; i > 0; i--)
70 | {
71 | range >>= 1;
72 | /*
73 | result <<= 1;
74 | if (code >= range)
75 | {
76 | code -= range;
77 | result |= 1;
78 | }
79 | */
80 | uint t = (code - range) >> 31;
81 | code -= range & (t - 1);
82 | result = (result << 1) | (1 - t);
83 |
84 | if (range < kTopValue)
85 | {
86 | code = (code << 8) | (byte)Stream.ReadByte();
87 | range <<= 8;
88 | }
89 | }
90 | Range = range;
91 | Code = code;
92 | return result;
93 | }
94 |
95 | public uint DecodeBit(uint size0, int numTotalBits)
96 | {
97 | uint newBound = (Range >> numTotalBits) * size0;
98 | uint symbol;
99 | if (Code < newBound)
100 | {
101 | symbol = 0;
102 | Range = newBound;
103 | }
104 | else
105 | {
106 | symbol = 1;
107 | Code -= newBound;
108 | Range -= newBound;
109 | }
110 | Normalize();
111 | return symbol;
112 | }
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Compress/RangeCoder/Encoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SevenZip.Compression.RangeCoder
4 | {
5 | class Encoder
6 | {
7 | public const uint kTopValue = (1 << 24);
8 |
9 | System.IO.Stream Stream;
10 |
11 | public UInt64 Low;
12 | public uint Range;
13 | uint _cacheSize;
14 | byte _cache;
15 |
16 | long StartPosition;
17 |
18 | public void SetStream(System.IO.Stream stream)
19 | {
20 | Stream = stream;
21 | }
22 |
23 | public void ReleaseStream()
24 | {
25 | Stream = null;
26 | }
27 |
28 | public void Init()
29 | {
30 | StartPosition = Stream.Position;
31 |
32 | Low = 0;
33 | Range = 0xFFFFFFFF;
34 | _cacheSize = 1;
35 | _cache = 0;
36 | }
37 |
38 | public void FlushData()
39 | {
40 | for (int i = 0; i < 5; i++)
41 | ShiftLow();
42 | }
43 |
44 | public void FlushStream()
45 | {
46 | Stream.Flush();
47 | }
48 |
49 | public void CloseStream()
50 | {
51 | Stream.Close();
52 | }
53 |
54 | public void Encode(uint start, uint size, uint total)
55 | {
56 | Low += start * (Range /= total);
57 | Range *= size;
58 | while (Range < kTopValue)
59 | {
60 | Range <<= 8;
61 | ShiftLow();
62 | }
63 | }
64 |
65 | public void ShiftLow()
66 | {
67 | if ((uint)Low < (uint)0xFF000000 || (uint)(Low >> 32) == 1)
68 | {
69 | byte temp = _cache;
70 | do
71 | {
72 | Stream.WriteByte((byte)(temp + (Low >> 32)));
73 | temp = 0xFF;
74 | }
75 | while (--_cacheSize != 0);
76 | _cache = (byte)(((uint)Low) >> 24);
77 | }
78 | _cacheSize++;
79 | Low = ((uint)Low) << 8;
80 | }
81 |
82 | public void EncodeDirectBits(uint v, int numTotalBits)
83 | {
84 | for (int i = numTotalBits - 1; i >= 0; i--)
85 | {
86 | Range >>= 1;
87 | if (((v >> i) & 1) == 1)
88 | Low += Range;
89 | if (Range < kTopValue)
90 | {
91 | Range <<= 8;
92 | ShiftLow();
93 | }
94 | }
95 | }
96 |
97 | public void EncodeBit(uint size0, int numTotalBits, uint symbol)
98 | {
99 | uint newBound = (Range >> numTotalBits) * size0;
100 | if (symbol == 0)
101 | Range = newBound;
102 | else
103 | {
104 | Low += newBound;
105 | Range -= newBound;
106 | }
107 | while (Range < kTopValue)
108 | {
109 | Range <<= 8;
110 | ShiftLow();
111 | }
112 | }
113 |
114 | public long GetProcessedSizeAdd()
115 | {
116 | return _cacheSize +
117 | Stream.Position - StartPosition + 4;
118 | // (long)Stream.GetProcessedSize();
119 | }
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Compress/LZ/LzOutWindow.cs:
--------------------------------------------------------------------------------
1 | // LzOutWindow.cs
2 |
3 | namespace SevenZip.Compression.LZ
4 | {
5 | public class OutWindow
6 | {
7 | byte[] _buffer = null;
8 | uint _pos;
9 | uint _windowSize = 0;
10 | uint _streamPos;
11 | System.IO.Stream _stream;
12 |
13 | public uint TrainSize = 0;
14 |
15 | public void Create(uint windowSize)
16 | {
17 | if (_windowSize != windowSize)
18 | {
19 | // System.GC.Collect();
20 | _buffer = new byte[windowSize];
21 | }
22 | _windowSize = windowSize;
23 | _pos = 0;
24 | _streamPos = 0;
25 | }
26 |
27 | public void Init(System.IO.Stream stream, bool solid)
28 | {
29 | ReleaseStream();
30 | _stream = stream;
31 | if (!solid)
32 | {
33 | _streamPos = 0;
34 | _pos = 0;
35 | TrainSize = 0;
36 | }
37 | }
38 |
39 | public bool Train(System.IO.Stream stream)
40 | {
41 | long len = stream.Length;
42 | uint size = (len < _windowSize) ? (uint)len : _windowSize;
43 | TrainSize = size;
44 | stream.Position = len - size;
45 | _streamPos = _pos = 0;
46 | while (size > 0)
47 | {
48 | uint curSize = _windowSize - _pos;
49 | if (size < curSize)
50 | curSize = size;
51 | int numReadBytes = stream.Read(_buffer, (int)_pos, (int)curSize);
52 | if (numReadBytes == 0)
53 | return false;
54 | size -= (uint)numReadBytes;
55 | _pos += (uint)numReadBytes;
56 | _streamPos += (uint)numReadBytes;
57 | if (_pos == _windowSize)
58 | _streamPos = _pos = 0;
59 | }
60 | return true;
61 | }
62 |
63 | public void ReleaseStream()
64 | {
65 | Flush();
66 | _stream = null;
67 | }
68 |
69 | public void Flush()
70 | {
71 | uint size = _pos - _streamPos;
72 | if (size == 0)
73 | return;
74 | _stream.Write(_buffer, (int)_streamPos, (int)size);
75 | if (_pos >= _windowSize)
76 | _pos = 0;
77 | _streamPos = _pos;
78 | }
79 |
80 | public void CopyBlock(uint distance, uint len)
81 | {
82 | uint pos = _pos - distance - 1;
83 | if (pos >= _windowSize)
84 | pos += _windowSize;
85 | for (; len > 0; len--)
86 | {
87 | if (pos >= _windowSize)
88 | pos = 0;
89 | _buffer[_pos++] = _buffer[pos++];
90 | if (_pos >= _windowSize)
91 | Flush();
92 | }
93 | }
94 |
95 | public void PutByte(byte b)
96 | {
97 | _buffer[_pos++] = b;
98 | if (_pos >= _windowSize)
99 | Flush();
100 | }
101 |
102 | public byte GetByte(uint distance)
103 | {
104 | uint pos = _pos - distance - 1;
105 | if (pos >= _windowSize)
106 | pos += _windowSize;
107 | return _buffer[pos];
108 | }
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/osu-database-reader/7zip/LZMACoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using SevenZip;
4 | using SevenZip.Compression.LZMA;
5 |
6 | namespace osu_database_reader
7 | {
8 | public class LZMACoder
9 | {
10 | public static MemoryStream Compress(Stream inStream)
11 | {
12 | inStream.Position = 0;
13 |
14 | CoderPropID[] propIDs = {
15 | CoderPropID.DictionarySize,
16 | CoderPropID.PosStateBits,
17 | CoderPropID.LitContextBits,
18 | CoderPropID.LitPosBits,
19 | CoderPropID.Algorithm,
20 | };
21 |
22 | object[] properties = {
23 | 0x10000,
24 | 2,
25 | 3,
26 | 0,
27 | 2,
28 | };
29 |
30 | var outStream = new MemoryStream();
31 | Encoder encoder = new Encoder();
32 | encoder.SetCoderProperties(propIDs, properties);
33 | encoder.WriteCoderProperties(outStream);
34 | for (int i = 0; i < 8; i++)
35 | outStream.WriteByte((byte)(inStream.Length >> (8 * i)));
36 | encoder.Code(inStream, outStream, -1, -1, null);
37 | outStream.Flush();
38 | outStream.Position = 0;
39 |
40 | return outStream;
41 | }
42 |
43 | public static MemoryStream Decompress(Stream inStream)
44 | {
45 | var decoder = new Decoder();
46 |
47 | byte[] properties = new byte[5];
48 | if (inStream.Read(properties, 0, 5) != 5)
49 | throw new Exception("input .lzma is too short");
50 | decoder.SetDecoderProperties(properties);
51 |
52 | long outSize = 0;
53 | for (int i = 0; i < 8; i++) {
54 | int v = inStream.ReadByte();
55 | if (v < 0)
56 | break;
57 | outSize |= ((long)(byte)v) << (8 * i);
58 | }
59 | long compressedSize = inStream.Length - inStream.Position;
60 |
61 | var outStream = new MemoryStream();
62 | decoder.Code(inStream, outStream, compressedSize, outSize, null);
63 | outStream.Flush();
64 | outStream.Position = 0;
65 | return outStream;
66 | }
67 |
68 | public static byte[] Compress(byte[] inBytes)
69 | {
70 | using (var ms = new MemoryStream(inBytes, false))
71 | return Compress(ms).ToArray();
72 | }
73 |
74 | public static byte[] Decompress(byte[] inBytes)
75 | {
76 | using (var ms = new MemoryStream(inBytes, false))
77 | return Decompress(ms).ToArray();
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/UnitTestProject/Data/Beatmaps/128.osu:
--------------------------------------------------------------------------------
1 | osu file format v3
2 |
3 | [General]
4 | AudioFilename: Aishiteru.mp3
5 | AudioHash: b3f0cc0fb0c2b7b5ce44b4e90c95e853
6 |
7 | [Metadata]
8 | Title:Aishiteru
9 | Artist:Hinoi Team
10 | Creator:MCXD
11 | Version:Crusin
12 | BeatmapID:128
13 | BeatmapSetID:46
14 |
15 | [Difficulty]
16 | HPDrainRate:4
17 | CircleSize:5
18 | OverallDifficulty:4
19 | SliderMultiplier: 1.1
20 | SliderTickRate: 1
21 |
22 | [Events]
23 |
24 | [TimingPoints]
25 | 7035,444.444444444444
26 |
27 | [HitObjects]
28 | 96,64,7035,1,0,
29 | 160,128,7923,1,0,
30 | 352,128,8812,1,0,
31 | 416,64,9701,1,0,
32 | 416,320,10590,5,0,
33 | 352,256,11479,1,0,
34 | 160,256,12368,1,0,
35 | 96,320,13257,1,0,
36 | 32,192,14146,6,0,C|32:192|480:192,3,440
37 | 448,96,19923,1,0,
38 | 352,96,20146,1,0,
39 | 64,320,21257,5,0,
40 | 64,224,21701,1,0,
41 | 64,128,22146,2,0,C|64:128|192:128,1,110
42 | 256,320,23034,1,0,
43 | 256,224,23479,1,0,
44 | 256,128,23923,2,0,C|256:128|384:128,1,110
45 | 448,128,24812,6,0,B|448:128|448:256|448:320,2,165
46 | 448,32,26590,5,0,
47 | 320,32,27257,1,0,
48 | 448,320,28368,5,0,
49 | 448,224,28812,1,0,
50 | 448,128,29257,2,0,C|448:128|320:128,1,110
51 | 256,320,30145,1,0,
52 | 256,224,30590,1,0,
53 | 256,128,31034,2,0,C|256:128|128:128,1,110
54 | 64,128,31923,6,0,B|64:128|64:256|160:352,3,220
55 | 256,96,35479,5,0,
56 | 256,288,36368,1,0,
57 | 128,192,37257,5,0,
58 | 256,192,37702,1,0,
59 | 384,192,38146,1,0,
60 | 384,320,39034,5,0,
61 | 320,256,39479,1,0,
62 | 256,192,39923,1,0,
63 | 128,64,40812,2,0,B|128:64|64:160|64:256,2,165
64 | 256,96,42590,5,0,
65 | 256,288,43479,1,0,
66 | 384,192,44368,5,0,
67 | 256,192,44812,1,0,
68 | 128,192,45257,1,0,
69 | 128,320,46145,5,0,
70 | 384,320,47034,1,0,
71 | 256,256,47923,2,0,B|256:256|256:32,1,220
72 | 64,256,49701,6,0,B|64:256|64:64|192:64,2,220
73 | 160,256,51923,2,0,B|160:256|288:256,1,110
74 | 416,256,53257,5,0,
75 | 416,160,53701,1,0,
76 | 416,64,54146,1,0,
77 | 320,64,54590,1,0,
78 | 224,64,55034,1,0,
79 | 128,160,55923,5,0,
80 | 448,256,56812,6,0,B|448:256|448:64|320:64,2,220
81 | 352,256,59034,2,0,B|352:256|224:256,1,110
82 | 96,256,60368,5,0,
83 | 96,160,60812,1,0,
84 | 96,64,61257,1,0,
85 | 192,64,61701,1,0,
86 | 288,64,62145,1,0,
87 | 384,160,63034,5,0,
88 | 448,320,63923,6,0,B|448:320|320:320|288:288|96:288,1,330
89 | 64,224,65701,2,0,B|64:224|224:192|320:192,1,220
90 | 448,192,67479,5,0,
91 | 448,96,67923,1,0,
92 | 352,32,68368,1,0,
93 | 256,32,68812,1,0,
94 | 160,32,69257,1,0,
95 | 64,224,70146,5,0,
96 | 64.10876,320.0044,71034,6,0,B|64:320|192:320|224:288|392:288,1,330
97 | 447.8766,223.9743,72812,2,0,B|447:223|288:192|230:193,1,220
98 | 64,192,74590,5,0,
99 | 64,96,75034,1,0,
100 | 160,32,75479,1,0,
101 | 256,32,75923,1,0,
102 | 352,32,76368,1,0,
103 | 416,160,77257,6,0,B|416:160|64:160,2,330
104 |
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Compress/LZMA/LzmaBase.cs:
--------------------------------------------------------------------------------
1 | // LzmaBase.cs
2 |
3 | namespace SevenZip.Compression.LZMA
4 | {
5 | internal abstract class Base
6 | {
7 | public const uint kNumRepDistances = 4;
8 | public const uint kNumStates = 12;
9 |
10 | // static byte []kLiteralNextStates = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5};
11 | // static byte []kMatchNextStates = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10};
12 | // static byte []kRepNextStates = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11};
13 | // static byte []kShortRepNextStates = {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11};
14 |
15 | public struct State
16 | {
17 | public uint Index;
18 | public void Init() { Index = 0; }
19 | public void UpdateChar()
20 | {
21 | if (Index < 4) Index = 0;
22 | else if (Index < 10) Index -= 3;
23 | else Index -= 6;
24 | }
25 | public void UpdateMatch() { Index = (uint)(Index < 7 ? 7 : 10); }
26 | public void UpdateRep() { Index = (uint)(Index < 7 ? 8 : 11); }
27 | public void UpdateShortRep() { Index = (uint)(Index < 7 ? 9 : 11); }
28 | public bool IsCharState() { return Index < 7; }
29 | }
30 |
31 | public const int kNumPosSlotBits = 6;
32 | public const int kDicLogSizeMin = 0;
33 | // public const int kDicLogSizeMax = 30;
34 | // public const uint kDistTableSizeMax = kDicLogSizeMax * 2;
35 |
36 | public const int kNumLenToPosStatesBits = 2; // it's for speed optimization
37 | public const uint kNumLenToPosStates = 1 << kNumLenToPosStatesBits;
38 |
39 | public const uint kMatchMinLen = 2;
40 |
41 | public static uint GetLenToPosState(uint len)
42 | {
43 | len -= kMatchMinLen;
44 | if (len < kNumLenToPosStates)
45 | return len;
46 | return (uint)(kNumLenToPosStates - 1);
47 | }
48 |
49 | public const int kNumAlignBits = 4;
50 | public const uint kAlignTableSize = 1 << kNumAlignBits;
51 | public const uint kAlignMask = (kAlignTableSize - 1);
52 |
53 | public const uint kStartPosModelIndex = 4;
54 | public const uint kEndPosModelIndex = 14;
55 | public const uint kNumPosModels = kEndPosModelIndex - kStartPosModelIndex;
56 |
57 | public const uint kNumFullDistances = 1 << ((int)kEndPosModelIndex / 2);
58 |
59 | public const uint kNumLitPosStatesBitsEncodingMax = 4;
60 | public const uint kNumLitContextBitsMax = 8;
61 |
62 | public const int kNumPosStatesBitsMax = 4;
63 | public const uint kNumPosStatesMax = (1 << kNumPosStatesBitsMax);
64 | public const int kNumPosStatesBitsEncodingMax = 4;
65 | public const uint kNumPosStatesEncodingMax = (1 << kNumPosStatesBitsEncodingMax);
66 |
67 | public const int kNumLowLenBits = 3;
68 | public const int kNumMidLenBits = 3;
69 | public const int kNumHighLenBits = 8;
70 | public const uint kNumLowLenSymbols = 1 << kNumLowLenBits;
71 | public const uint kNumMidLenSymbols = 1 << kNumMidLenBits;
72 | public const uint kNumLenSymbols = kNumLowLenSymbols + kNumMidLenSymbols +
73 | (1 << kNumHighLenBits);
74 | public const uint kMatchMaxLen = kMatchMinLen + kNumLenSymbols - 1;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/osu-database-reader/TextFiles/BeatmapFile.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using osu_database_reader.Components.Beatmaps;
5 | using osu_database_reader.Components.Events;
6 | using osu_database_reader.Components.HitObjects;
7 |
8 | namespace osu_database_reader.TextFiles
9 | {
10 | public partial class BeatmapFile
11 | {
12 | public int FileFormatVersion;
13 |
14 | public Dictionary SectionGeneral;
15 | public Dictionary SectionEditor;
16 | public Dictionary SectionMetadata;
17 | public Dictionary SectionDifficulty;
18 | public Dictionary SectionColours;
19 |
20 | public readonly List Events = new();
21 | public readonly List TimingPoints = new();
22 | public readonly List HitObjects = new();
23 |
24 | public static BeatmapFile Read(string path)
25 | {
26 | using var fs = File.OpenRead(path);
27 | return Read(fs);
28 | }
29 |
30 | public static BeatmapFile Read(Stream stream)
31 | {
32 | var file = new BeatmapFile();
33 |
34 | using var r = new StreamReader(stream);
35 | if (!int.TryParse(r.ReadLine()?.Replace("osu file format v", string.Empty), out file.FileFormatVersion))
36 | throw new Exception("Not a valid beatmap"); //very simple check, could be better
37 |
38 | BeatmapSection? bs;
39 | while ((bs = r.ReadUntilSectionStart()) != null) {
40 | switch (bs.Value) {
41 | case BeatmapSection.General:
42 | file.SectionGeneral = r.ReadBasicSection();
43 | break;
44 | case BeatmapSection.Editor:
45 | file.SectionEditor = r.ReadBasicSection();
46 | break;
47 | case BeatmapSection.Metadata:
48 | file.SectionMetadata = r.ReadBasicSection(false);
49 | break;
50 | case BeatmapSection.Difficulty:
51 | file.SectionDifficulty = r.ReadBasicSection(false);
52 | break;
53 | case BeatmapSection.Events:
54 | file.Events.AddRange(r.ReadEvents());
55 | break;
56 | case BeatmapSection.TimingPoints:
57 | file.TimingPoints.AddRange(r.ReadTimingPoints());
58 | break;
59 | case BeatmapSection.Colours:
60 | file.SectionColours = r.ReadBasicSection(true, true);
61 | break;
62 | case BeatmapSection.HitObjects:
63 | file.HitObjects.AddRange(r.ReadHitObjects());
64 | break;
65 | default:
66 | throw new ArgumentOutOfRangeException();
67 | }
68 | }
69 |
70 | return file;
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/HitObjects/HitObject.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using osu.Shared;
3 |
4 | namespace osu_database_reader.Components.HitObjects
5 | {
6 | public abstract class HitObject
7 | {
8 | /// The X coordinate, based on a 512x384 field.
9 | public int X;
10 | /// The y coordinate, based on a 512x384 field.
11 | public int Y;
12 | public int Time;
13 | public HitObjectType Type;
14 | public HitSound HitSound;
15 |
16 | public bool IsNewCombo => Type.HasFlag(HitObjectType.NewCombo);
17 |
18 | public static HitObject FromString(string s)
19 | {
20 | string[] split = s.Split(',');
21 | var t = (HitObjectType) int.Parse(split[3], Constants.NumberFormat);
22 |
23 | HitObject h;
24 | switch (t & (HitObjectType)0b1000_1011) {
25 | case HitObjectType.Normal:
26 | h = new HitObjectCircle();
27 | if (split.Length > 5)
28 | ((HitObjectCircle) h).SoundSampleData = split[5];
29 | break;
30 | case HitObjectType.Slider:
31 | h = new HitObjectSlider();
32 | ((HitObjectSlider) h).ParseSliderSegments(split[5]);
33 | ((HitObjectSlider) h).RepeatCount = int.Parse(split[6], Constants.NumberFormat);
34 | if (split.Length > 7)
35 | ((HitObjectSlider) h).Length = double.Parse(split[7], Constants.NumberFormat);
36 | //if (split.Length > 8)
37 | // (h as HitObjectSlider).HitSoundData = split[8];
38 | //if (split.Length > 9)
39 | // (h as HitObjectSlider).SoundSampleData = split[9];
40 | //if (split.Length > 10)
41 | // (h as HitObjectSlider).MoreSoundSampleData = split[10];
42 | break;
43 | case HitObjectType.Spinner:
44 | h = new HitObjectSpinner();
45 | ((HitObjectSpinner) h).EndTime = int.Parse(split[5]);
46 | if (split.Length > 6)
47 | ((HitObjectSpinner) h).SoundSampleData = split[6];
48 | break;
49 | case HitObjectType.Hold:
50 | h = new HitObjectHold();
51 | var sampleDataFirstColon = split[5].IndexOf(':');
52 | ((HitObjectHold) h).EndTime = int.Parse(split[5].Substring(0, sampleDataFirstColon));
53 | ((HitObjectHold) h).SoundSampleData = split[5].Substring(sampleDataFirstColon + 1);
54 | break;
55 | default:
56 | throw new ArgumentOutOfRangeException(nameof(t), "Bad hitobject type");
57 | }
58 |
59 | // parsed as decimal but cast to int to match osu! behavior
60 | h.X = (int)double.Parse(split[0], Constants.NumberFormat);
61 | h.Y = (int)double.Parse(split[1], Constants.NumberFormat);
62 | h.Time = (int)double.Parse(split[2], Constants.NumberFormat);
63 | h.Type = t;
64 | h.HitSound = (HitSound)int.Parse(split[4]);
65 |
66 | return h;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/UnitTestProject/ReadWriteMatchTests.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.Reflection;
3 | using FluentAssertions;
4 | using osu.Shared.Serialization;
5 | using osu_database_reader.BinaryFiles;
6 | using osu_database_reader.Components.Player;
7 | using Xunit;
8 |
9 | namespace UnitTestProject
10 | {
11 | public class ReadWriteMatchTests
12 | {
13 | [Fact]
14 | public void ReadWriteCollectionDb()
15 | {
16 | CollectionDb db;
17 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Collection.20210316.db"))
18 | db = CollectionDb.Read(stream);
19 |
20 | using var ms = new MemoryStream();
21 |
22 | using var sw = new SerializationWriter(ms);
23 | db.WriteToStream(sw);
24 |
25 | ms.Position = 0;
26 | var read = CollectionDb.Read(ms);
27 |
28 | db.Should().BeEquivalentTo(read);
29 | }
30 |
31 | [Fact]
32 | public void ReadWriteOsuDb()
33 | {
34 | OsuDb db;
35 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.osu_.20210316.db"))
36 | db = OsuDb.Read(stream);
37 |
38 | using var ms = new MemoryStream();
39 |
40 | using var sw = new SerializationWriter(ms);
41 | db.WriteToStream(sw);
42 |
43 | ms.Position = 0;
44 | var read = OsuDb.Read(ms);
45 |
46 | db.Should().BeEquivalentTo(read);
47 | }
48 |
49 | [Fact]
50 | public void ReadWritePresenceDb()
51 | {
52 | PresenceDb db;
53 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Presence.20210316.db"))
54 | db = PresenceDb.Read(stream);
55 |
56 | using var ms = new MemoryStream();
57 |
58 | using var sw = new SerializationWriter(ms);
59 | db.WriteToStream(sw);
60 |
61 | ms.Position = 0;
62 | var read = PresenceDb.Read(ms);
63 |
64 | db.Should().BeEquivalentTo(read);
65 | }
66 |
67 | [Fact]
68 | public void ReadWriteScoresDb()
69 | {
70 | ScoresDb db;
71 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Scores.20210316.db"))
72 | db = ScoresDb.Read(stream);
73 |
74 | using var ms = new MemoryStream();
75 |
76 | using var sw = new SerializationWriter(ms);
77 | db.WriteToStream(sw);
78 |
79 | ms.Position = 0;
80 | var read = ScoresDb.Read(ms);
81 |
82 | db.Should().BeEquivalentTo(read);
83 | }
84 |
85 | [Fact]
86 | public void ReadWriteReplay()
87 | {
88 | Replay db;
89 | using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Replays.20210316.osr"))
90 | db = Replay.Read(stream);
91 |
92 | using var ms = new MemoryStream();
93 |
94 | using var sw = new SerializationWriter(ms);
95 | db.WriteToStream(sw);
96 |
97 | ms.Position = 0;
98 | var read = Replay.Read(ms);
99 |
100 | db.Should().BeEquivalentTo(read);
101 | }
102 | }
103 | }
--------------------------------------------------------------------------------
/osu-database-reader/Extensions.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using osu_database_reader.Components.Beatmaps;
6 | using osu_database_reader.Components.Events;
7 | using osu_database_reader.Components.HitObjects;
8 |
9 | namespace osu_database_reader
10 | {
11 | internal static class Extensions
12 | {
13 | public static string GetValueOrNull(this Dictionary dic, string key)
14 | {
15 | return dic.TryGetValue(key, out var value)
16 | ? value
17 | : null;
18 | }
19 |
20 | //for StreamReader
21 | public static BeatmapSection? ReadUntilSectionStart(this StreamReader sr)
22 | {
23 | while (!sr.EndOfStream)
24 | {
25 | string str = sr.ReadLine();
26 | if (string.IsNullOrWhiteSpace(str)) continue;
27 | if (!str.StartsWith("[")) continue; // some beatmaps contain garbage data (eg. 3718961)
28 |
29 | string stripped = str.TrimStart('[').TrimEnd(']');
30 | if (!Enum.TryParse(stripped, out BeatmapSection a))
31 | throw new Exception("Unrecognized beatmap section: " + stripped);
32 | return a;
33 | }
34 |
35 | //we reached an end of stream
36 | return null;
37 | }
38 |
39 | public static Dictionary ReadBasicSection(this StreamReader sr, bool extraSpaceAfterColon = true, bool extraSpaceBeforeColon = false)
40 | {
41 | var dic = new Dictionary();
42 |
43 | string line;
44 | while (!string.IsNullOrWhiteSpace(line = sr.ReadLine()))
45 | {
46 | // don't throw on invalid lines because they occur in ranked beatmaps, e.g. 1391940
47 | if (!line.Contains(':'))
48 | continue;
49 |
50 | int i = line.IndexOf(':');
51 |
52 | string key = line.Substring(0, i);
53 | string value = line.Substring(i + 1);
54 |
55 | //This is just so we can recreate files properly in the future.
56 | //It is very likely not needed at all, but it makes me sleep
57 | //better at night knowing everything is 100% correct.
58 | if (extraSpaceBeforeColon && key.EndsWith(" ")) key = key.Substring(0, key.Length - 1);
59 | if (extraSpaceAfterColon && value.StartsWith(" ")) value = value.Substring(1);
60 |
61 | dic.Add(key, value);
62 | }
63 |
64 | return dic;
65 | }
66 |
67 | public static IEnumerable ReadHitObjects(this StreamReader sr)
68 | {
69 | string line;
70 | while (!string.IsNullOrEmpty(line = sr.ReadLine()))
71 | yield return HitObject.FromString(line);
72 | }
73 |
74 | public static IEnumerable ReadTimingPoints(this StreamReader sr)
75 | {
76 | string line;
77 | while (!string.IsNullOrEmpty(line = sr.ReadLine()))
78 | yield return TimingPoint.FromString(line);
79 | }
80 |
81 | public static IEnumerable ReadEvents(this StreamReader sr)
82 | {
83 | string line;
84 | while (!string.IsNullOrEmpty(line = sr.ReadLine()))
85 | if (!line.StartsWith("//"))
86 | yield return EventBase.FromString(line);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Compress/RangeCoder/RangeCoderBit.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SevenZip.Compression.RangeCoder
4 | {
5 | struct BitEncoder
6 | {
7 | public const int kNumBitModelTotalBits = 11;
8 | public const uint kBitModelTotal = (1 << kNumBitModelTotalBits);
9 | const int kNumMoveBits = 5;
10 | const int kNumMoveReducingBits = 2;
11 | public const int kNumBitPriceShiftBits = 6;
12 |
13 | uint Prob;
14 |
15 | public void Init() { Prob = kBitModelTotal >> 1; }
16 |
17 | public void UpdateModel(uint symbol)
18 | {
19 | if (symbol == 0)
20 | Prob += (kBitModelTotal - Prob) >> kNumMoveBits;
21 | else
22 | Prob -= (Prob) >> kNumMoveBits;
23 | }
24 |
25 | public void Encode(Encoder encoder, uint symbol)
26 | {
27 | // encoder.EncodeBit(Prob, kNumBitModelTotalBits, symbol);
28 | // UpdateModel(symbol);
29 | uint newBound = (encoder.Range >> kNumBitModelTotalBits) * Prob;
30 | if (symbol == 0)
31 | {
32 | encoder.Range = newBound;
33 | Prob += (kBitModelTotal - Prob) >> kNumMoveBits;
34 | }
35 | else
36 | {
37 | encoder.Low += newBound;
38 | encoder.Range -= newBound;
39 | Prob -= (Prob) >> kNumMoveBits;
40 | }
41 | if (encoder.Range < Encoder.kTopValue)
42 | {
43 | encoder.Range <<= 8;
44 | encoder.ShiftLow();
45 | }
46 | }
47 |
48 | private static UInt32[] ProbPrices = new UInt32[kBitModelTotal >> kNumMoveReducingBits];
49 |
50 | static BitEncoder()
51 | {
52 | const int kNumBits = (kNumBitModelTotalBits - kNumMoveReducingBits);
53 | for (int i = kNumBits - 1; i >= 0; i--)
54 | {
55 | UInt32 start = (UInt32)1 << (kNumBits - i - 1);
56 | UInt32 end = (UInt32)1 << (kNumBits - i);
57 | for (UInt32 j = start; j < end; j++)
58 | ProbPrices[j] = ((UInt32)i << kNumBitPriceShiftBits) +
59 | (((end - j) << kNumBitPriceShiftBits) >> (kNumBits - i - 1));
60 | }
61 | }
62 |
63 | public uint GetPrice(uint symbol)
64 | {
65 | return ProbPrices[(((Prob - symbol) ^ ((-(int)symbol))) & (kBitModelTotal - 1)) >> kNumMoveReducingBits];
66 | }
67 | public uint GetPrice0() { return ProbPrices[Prob >> kNumMoveReducingBits]; }
68 | public uint GetPrice1() { return ProbPrices[(kBitModelTotal - Prob) >> kNumMoveReducingBits]; }
69 | }
70 |
71 | struct BitDecoder
72 | {
73 | public const int kNumBitModelTotalBits = 11;
74 | public const uint kBitModelTotal = (1 << kNumBitModelTotalBits);
75 | const int kNumMoveBits = 5;
76 |
77 | uint Prob;
78 |
79 | public void UpdateModel(int numMoveBits, uint symbol)
80 | {
81 | if (symbol == 0)
82 | Prob += (kBitModelTotal - Prob) >> numMoveBits;
83 | else
84 | Prob -= (Prob) >> numMoveBits;
85 | }
86 |
87 | public void Init() { Prob = kBitModelTotal >> 1; }
88 |
89 | public uint Decode(RangeCoder.Decoder rangeDecoder)
90 | {
91 | uint newBound = (uint)(rangeDecoder.Range >> kNumBitModelTotalBits) * (uint)Prob;
92 | if (rangeDecoder.Code < newBound)
93 | {
94 | rangeDecoder.Range = newBound;
95 | Prob += (kBitModelTotal - Prob) >> kNumMoveBits;
96 | if (rangeDecoder.Range < Decoder.kTopValue)
97 | {
98 | rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte();
99 | rangeDecoder.Range <<= 8;
100 | }
101 | return 0;
102 | }
103 | else
104 | {
105 | rangeDecoder.Range -= newBound;
106 | rangeDecoder.Code -= newBound;
107 | Prob -= (Prob) >> kNumMoveBits;
108 | if (rangeDecoder.Range < Decoder.kTopValue)
109 | {
110 | rangeDecoder.Code = (rangeDecoder.Code << 8) | (byte)rangeDecoder.Stream.ReadByte();
111 | rangeDecoder.Range <<= 8;
112 | }
113 | return 1;
114 | }
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/osu-database-reader/BinaryFiles/OsuDb.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Diagnostics;
4 | using System.IO;
5 | using osu.Shared;
6 | using osu.Shared.Serialization;
7 | using osu_database_reader.Components.Beatmaps;
8 |
9 | namespace osu_database_reader.BinaryFiles
10 | {
11 | public class OsuDb : ISerializable
12 | {
13 | public int OsuVersion;
14 | public int FolderCount;
15 | public bool AccountUnlocked;
16 | public DateTime AccountUnlockDate;
17 | public string AccountName;
18 | public List Beatmaps;
19 | public PlayerRank AccountRank;
20 |
21 | public static OsuDb Read(string path)
22 | {
23 | using var stream = File.OpenRead(path);
24 | return Read(stream);
25 | }
26 |
27 | public static OsuDb Read(Stream stream) {
28 | var db = new OsuDb();
29 | using var r = new SerializationReader(stream);
30 | db.ReadFromStream(r);
31 | return db;
32 | }
33 |
34 | public void ReadFromStream(SerializationReader r)
35 | {
36 | bool hasEntryLength = OsuVersion
37 | is >= OsuVersions.EntryLengthInOsuDbMin
38 | and < OsuVersions.EntryLengthInOsuDbMax;
39 |
40 | OsuVersion = r.ReadInt32();
41 | FolderCount = r.ReadInt32();
42 | AccountUnlocked = r.ReadBoolean();
43 | AccountUnlockDate = r.ReadDateTime();
44 | AccountName = r.ReadString();
45 |
46 | Beatmaps = new List();
47 |
48 | int length = r.ReadInt32();
49 |
50 | for (int i = 0; i < length; i++) {
51 | int currentIndex = (int)r.BaseStream.Position;
52 | int entryLength = 0;
53 |
54 | if (hasEntryLength)
55 | entryLength = r.ReadInt32();
56 |
57 | Beatmaps.Add(BeatmapEntry.ReadFromReader(r, OsuVersion));
58 |
59 | if (OsuVersion < OsuVersions.EntryLengthInOsuDbMax && r.BaseStream.Position != currentIndex + entryLength + 4) {
60 | Debug.Fail($"Length doesn't match, {r.BaseStream.Position} instead of expected {currentIndex + entryLength + 4}");
61 | }
62 | }
63 |
64 | // NOTE: this may be an int instead?
65 | AccountRank = (PlayerRank)r.ReadByte();
66 | }
67 |
68 | public void WriteToStream(SerializationWriter w)
69 | {
70 | bool hasEntryLength = OsuVersion
71 | is >= OsuVersions.EntryLengthInOsuDbMin
72 | and < OsuVersions.EntryLengthInOsuDbMax;
73 |
74 | w.Write(OsuVersion);
75 | w.Write(FolderCount);
76 | w.Write(AccountUnlocked);
77 | w.Write(AccountUnlockDate);
78 | w.Write(AccountName);
79 |
80 | w.Write(Beatmaps.Count);
81 |
82 | foreach (var beatmap in Beatmaps)
83 | {
84 | if (hasEntryLength)
85 | {
86 | using var ms = new MemoryStream();
87 | using var w2 = new SerializationWriter(ms);
88 | beatmap.WriteToStream(w2);
89 |
90 | var bytes = ms.ToArray();
91 | w.Write(bytes.Length);
92 | w.WriteRaw(bytes);
93 | }
94 | else
95 | {
96 | beatmap.WriteToStream(w);
97 | }
98 | }
99 |
100 | // TODO: figure out if/when this changed from byte to int
101 | w.Write((int)AccountRank);
102 | }
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Compress/RangeCoder/RangeCoderBitTree.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SevenZip.Compression.RangeCoder
4 | {
5 | struct BitTreeEncoder
6 | {
7 | BitEncoder[] Models;
8 | int NumBitLevels;
9 |
10 | public BitTreeEncoder(int numBitLevels)
11 | {
12 | NumBitLevels = numBitLevels;
13 | Models = new BitEncoder[1 << numBitLevels];
14 | }
15 |
16 | public void Init()
17 | {
18 | for (uint i = 1; i < (1 << NumBitLevels); i++)
19 | Models[i].Init();
20 | }
21 |
22 | public void Encode(Encoder rangeEncoder, UInt32 symbol)
23 | {
24 | UInt32 m = 1;
25 | for (int bitIndex = NumBitLevels; bitIndex > 0; )
26 | {
27 | bitIndex--;
28 | UInt32 bit = (symbol >> bitIndex) & 1;
29 | Models[m].Encode(rangeEncoder, bit);
30 | m = (m << 1) | bit;
31 | }
32 | }
33 |
34 | public void ReverseEncode(Encoder rangeEncoder, UInt32 symbol)
35 | {
36 | UInt32 m = 1;
37 | for (UInt32 i = 0; i < NumBitLevels; i++)
38 | {
39 | UInt32 bit = symbol & 1;
40 | Models[m].Encode(rangeEncoder, bit);
41 | m = (m << 1) | bit;
42 | symbol >>= 1;
43 | }
44 | }
45 |
46 | public UInt32 GetPrice(UInt32 symbol)
47 | {
48 | UInt32 price = 0;
49 | UInt32 m = 1;
50 | for (int bitIndex = NumBitLevels; bitIndex > 0; )
51 | {
52 | bitIndex--;
53 | UInt32 bit = (symbol >> bitIndex) & 1;
54 | price += Models[m].GetPrice(bit);
55 | m = (m << 1) + bit;
56 | }
57 | return price;
58 | }
59 |
60 | public UInt32 ReverseGetPrice(UInt32 symbol)
61 | {
62 | UInt32 price = 0;
63 | UInt32 m = 1;
64 | for (int i = NumBitLevels; i > 0; i--)
65 | {
66 | UInt32 bit = symbol & 1;
67 | symbol >>= 1;
68 | price += Models[m].GetPrice(bit);
69 | m = (m << 1) | bit;
70 | }
71 | return price;
72 | }
73 |
74 | public static UInt32 ReverseGetPrice(BitEncoder[] Models, UInt32 startIndex,
75 | int NumBitLevels, UInt32 symbol)
76 | {
77 | UInt32 price = 0;
78 | UInt32 m = 1;
79 | for (int i = NumBitLevels; i > 0; i--)
80 | {
81 | UInt32 bit = symbol & 1;
82 | symbol >>= 1;
83 | price += Models[startIndex + m].GetPrice(bit);
84 | m = (m << 1) | bit;
85 | }
86 | return price;
87 | }
88 |
89 | public static void ReverseEncode(BitEncoder[] Models, UInt32 startIndex,
90 | Encoder rangeEncoder, int NumBitLevels, UInt32 symbol)
91 | {
92 | UInt32 m = 1;
93 | for (int i = 0; i < NumBitLevels; i++)
94 | {
95 | UInt32 bit = symbol & 1;
96 | Models[startIndex + m].Encode(rangeEncoder, bit);
97 | m = (m << 1) | bit;
98 | symbol >>= 1;
99 | }
100 | }
101 | }
102 |
103 | struct BitTreeDecoder
104 | {
105 | BitDecoder[] Models;
106 | int NumBitLevels;
107 |
108 | public BitTreeDecoder(int numBitLevels)
109 | {
110 | NumBitLevels = numBitLevels;
111 | Models = new BitDecoder[1 << numBitLevels];
112 | }
113 |
114 | public void Init()
115 | {
116 | for (uint i = 1; i < (1 << NumBitLevels); i++)
117 | Models[i].Init();
118 | }
119 |
120 | public uint Decode(RangeCoder.Decoder rangeDecoder)
121 | {
122 | uint m = 1;
123 | for (int bitIndex = NumBitLevels; bitIndex > 0; bitIndex--)
124 | m = (m << 1) + Models[m].Decode(rangeDecoder);
125 | return m - ((uint)1 << NumBitLevels);
126 | }
127 |
128 | public uint ReverseDecode(RangeCoder.Decoder rangeDecoder)
129 | {
130 | uint m = 1;
131 | uint symbol = 0;
132 | for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++)
133 | {
134 | uint bit = Models[m].Decode(rangeDecoder);
135 | m <<= 1;
136 | m += bit;
137 | symbol |= (bit << bitIndex);
138 | }
139 | return symbol;
140 | }
141 |
142 | public static uint ReverseDecode(BitDecoder[] Models, UInt32 startIndex,
143 | RangeCoder.Decoder rangeDecoder, int NumBitLevels)
144 | {
145 | uint m = 1;
146 | uint symbol = 0;
147 | for (int bitIndex = 0; bitIndex < NumBitLevels; bitIndex++)
148 | {
149 | uint bit = Models[startIndex + m].Decode(rangeDecoder);
150 | m <<= 1;
151 | m += bit;
152 | symbol |= (bit << bitIndex);
153 | }
154 | return symbol;
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/osu-database-reader.SourceGenerator/BeatmapPropertyGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Reflection;
5 | using System.Text;
6 | using Microsoft.CodeAnalysis;
7 | using Microsoft.CodeAnalysis.Text;
8 | using Tommy;
9 |
10 | namespace osu_database_reader.SourceGenerator
11 | {
12 | [Generator]
13 | public class BeatmapPropertyGenerator : ISourceGenerator
14 | {
15 | private TomlTable document;
16 |
17 | public void Initialize(GeneratorInitializationContext context)
18 | {
19 | var asm = Assembly.GetExecutingAssembly();
20 | using var stream = asm.GetManifestResourceStream("osu_database_reader.SourceGenerator.Data.BeatmapProperties.toml");
21 | using var sr = new StreamReader(stream!);
22 | document = TOML.Parse(sr);
23 | }
24 |
25 | public void Execute(GeneratorExecutionContext context)
26 | {
27 | var sb = new StringBuilder();
28 |
29 | sb.AppendLine("using System;");
30 | sb.AppendLine("using System.Collections.Generic;");
31 | sb.AppendLine("using System.Linq;");
32 | sb.AppendLine("using osu_database_reader;");
33 | sb.AppendLine($"namespace {document["namespace"]} {{ ");
34 | sb.AppendLine($"partial class {document["class"]} {{ ");
35 |
36 | foreach (var kvp in document.RawTable.Where(x => x.Value is TomlTable))
37 | {
38 | var section = kvp.Key;
39 | var items = (TomlTable)kvp.Value;
40 |
41 | sb.AppendLine($"\t// Section {section}");
42 | foreach (var kvpInner in items.RawTable)
43 | {
44 | var propName = kvpInner.Key;
45 | var propType = kvpInner.Value.AsString.ToString();
46 |
47 | var (type, getter) = GenerateSourceParts(section, propName, propType);
48 | sb.AppendLine("\t" + $"public {type} {propName} => {getter};");
49 | }
50 |
51 | sb.AppendLine();
52 | }
53 |
54 | sb.AppendLine("}");
55 | sb.AppendLine("}");
56 |
57 | context.AddSource("BeatmapProperties.g.cs", SourceText.From(sb.ToString(), Encoding.UTF8));
58 | }
59 |
60 | private static (string type, string getter) GenerateSourceParts(string section, string propName, string propType)
61 | {
62 | string type;
63 | string getter;
64 | string dictionaryName = "Section" + section;
65 |
66 | switch (propType)
67 | {
68 | case "string":
69 | type = "string";
70 | getter = $"{dictionaryName}.GetValueOrNull(\"{propName}\")";
71 | break;
72 | case "int":
73 | type = "int";
74 | getter = $"int.Parse({dictionaryName}.GetValueOrNull(\"{propName}\"), Constants.NumberFormat)";
75 | break;
76 | case "float":
77 | type = "float";
78 | getter = $"float.Parse({dictionaryName}.GetValueOrNull(\"{propName}\"), Constants.NumberFormat)";
79 | break;
80 | case "double":
81 | type = "double";
82 | getter = $"double.Parse({dictionaryName}.GetValueOrNull(\"{propName}\"), Constants.NumberFormat)";
83 | break;
84 | case "intbool":
85 | type = "bool";
86 | getter = $"int.Parse({dictionaryName}.GetValueOrNull(\"{propName}\"), Constants.NumberFormat) == 1";
87 | break;
88 | case "int[]":
89 | type = "int[]";
90 | getter = $"{dictionaryName}.GetValueOrNull(\"{propName}\")" +
91 | ".Split(',')" +
92 | ".Select(x => int.Parse(x, Constants.NumberFormat))" +
93 | ".ToArray()";
94 | break;
95 | default:
96 | throw new NotImplementedException("Unknown beatmap property type: " + propType);
97 | }
98 |
99 | return (type, getter);
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Compress/LZ/LzInWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SevenZip.Compression.LZ
4 | {
5 | public class InWindow
6 | {
7 | public Byte[] _bufferBase = null; // pointer to buffer with data
8 | System.IO.Stream _stream;
9 | UInt32 _posLimit; // offset (from _buffer) of first byte when new block reading must be done
10 | bool _streamEndWasReached; // if (true) then _streamPos shows real end of stream
11 |
12 | UInt32 _pointerToLastSafePosition;
13 |
14 | public UInt32 _bufferOffset;
15 |
16 | public UInt32 _blockSize; // Size of Allocated memory block
17 | public UInt32 _pos; // offset (from _buffer) of curent byte
18 | UInt32 _keepSizeBefore; // how many BYTEs must be kept in buffer before _pos
19 | UInt32 _keepSizeAfter; // how many BYTEs must be kept buffer after _pos
20 | public UInt32 _streamPos; // offset (from _buffer) of first not read byte from Stream
21 |
22 | public void MoveBlock()
23 | {
24 | UInt32 offset = (UInt32)(_bufferOffset) + _pos - _keepSizeBefore;
25 | // we need one additional byte, since MovePos moves on 1 byte.
26 | if (offset > 0)
27 | offset--;
28 |
29 | UInt32 numBytes = (UInt32)(_bufferOffset) + _streamPos - offset;
30 |
31 | // check negative offset ????
32 | for (UInt32 i = 0; i < numBytes; i++)
33 | _bufferBase[i] = _bufferBase[offset + i];
34 | _bufferOffset -= offset;
35 | }
36 |
37 | public virtual void ReadBlock()
38 | {
39 | if (_streamEndWasReached)
40 | return;
41 | while (true)
42 | {
43 | int size = (int)((0 - _bufferOffset) + _blockSize - _streamPos);
44 | if (size == 0)
45 | return;
46 | int numReadBytes = _stream.Read(_bufferBase, (int)(_bufferOffset + _streamPos), size);
47 | if (numReadBytes == 0)
48 | {
49 | _posLimit = _streamPos;
50 | UInt32 pointerToPostion = _bufferOffset + _posLimit;
51 | if (pointerToPostion > _pointerToLastSafePosition)
52 | _posLimit = (UInt32)(_pointerToLastSafePosition - _bufferOffset);
53 |
54 | _streamEndWasReached = true;
55 | return;
56 | }
57 | _streamPos += (UInt32)numReadBytes;
58 | if (_streamPos >= _pos + _keepSizeAfter)
59 | _posLimit = _streamPos - _keepSizeAfter;
60 | }
61 | }
62 |
63 | void Free() { _bufferBase = null; }
64 |
65 | public void Create(UInt32 keepSizeBefore, UInt32 keepSizeAfter, UInt32 keepSizeReserv)
66 | {
67 | _keepSizeBefore = keepSizeBefore;
68 | _keepSizeAfter = keepSizeAfter;
69 | UInt32 blockSize = keepSizeBefore + keepSizeAfter + keepSizeReserv;
70 | if (_bufferBase == null || _blockSize != blockSize)
71 | {
72 | Free();
73 | _blockSize = blockSize;
74 | _bufferBase = new Byte[_blockSize];
75 | }
76 | _pointerToLastSafePosition = _blockSize - keepSizeAfter;
77 | }
78 |
79 | public void SetStream(System.IO.Stream stream) { _stream = stream; }
80 | public void ReleaseStream() { _stream = null; }
81 |
82 | public void Init()
83 | {
84 | _bufferOffset = 0;
85 | _pos = 0;
86 | _streamPos = 0;
87 | _streamEndWasReached = false;
88 | ReadBlock();
89 | }
90 |
91 | public void MovePos()
92 | {
93 | _pos++;
94 | if (_pos > _posLimit)
95 | {
96 | UInt32 pointerToPostion = _bufferOffset + _pos;
97 | if (pointerToPostion > _pointerToLastSafePosition)
98 | MoveBlock();
99 | ReadBlock();
100 | }
101 | }
102 |
103 | public Byte GetIndexByte(Int32 index) { return _bufferBase[_bufferOffset + _pos + index]; }
104 |
105 | // index + limit have not to exceed _keepSizeAfter;
106 | public UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit)
107 | {
108 | if (_streamEndWasReached)
109 | if ((_pos + index) + limit > _streamPos)
110 | limit = _streamPos - (UInt32)(_pos + index);
111 | distance++;
112 | // Byte *pby = _buffer + (size_t)_pos + index;
113 | UInt32 pby = _bufferOffset + _pos + (UInt32)index;
114 |
115 | UInt32 i;
116 | for (i = 0; i < limit && _bufferBase[pby + i] == _bufferBase[pby + i - distance]; i++) { }
117 | return i;
118 | }
119 |
120 | public UInt32 GetNumAvailableBytes() { return _streamPos - _pos; }
121 |
122 | public void ReduceOffsets(Int32 subValue)
123 | {
124 | _bufferOffset += (UInt32)subValue;
125 | _posLimit -= (UInt32)subValue;
126 | _pos -= (UInt32)subValue;
127 | _streamPos -= (UInt32)subValue;
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/osu-database-reader/7zip/ICoder.cs:
--------------------------------------------------------------------------------
1 | // ICoder.h
2 |
3 | using System;
4 |
5 | namespace SevenZip
6 | {
7 | ///
8 | /// The exception that is thrown when an error in input stream occurs during decoding.
9 | ///
10 | class DataErrorException : ApplicationException
11 | {
12 | public DataErrorException(): base("Data Error") { }
13 | }
14 |
15 | ///
16 | /// The exception that is thrown when the value of an argument is outside the allowable range.
17 | ///
18 | class InvalidParamException : ApplicationException
19 | {
20 | public InvalidParamException(): base("Invalid Parameter") { }
21 | }
22 |
23 | public interface ICodeProgress
24 | {
25 | ///
26 | /// Callback progress.
27 | ///
28 | ///
29 | /// input size. -1 if unknown.
30 | ///
31 | ///
32 | /// output size. -1 if unknown.
33 | ///
34 | void SetProgress(Int64 inSize, Int64 outSize);
35 | }
36 |
37 | public interface ICoder
38 | {
39 | ///
40 | /// Codes streams.
41 | ///
42 | ///
43 | /// input Stream.
44 | ///
45 | ///
46 | /// output Stream.
47 | ///
48 | ///
49 | /// input Size. -1 if unknown.
50 | ///
51 | ///
52 | /// output Size. -1 if unknown.
53 | ///
54 | ///
55 | /// callback progress reference.
56 | ///
57 | ///
58 | /// if input stream is not valid
59 | ///
60 | void Code(System.IO.Stream inStream, System.IO.Stream outStream,
61 | Int64 inSize, Int64 outSize, ICodeProgress progress);
62 | }
63 |
64 | /*
65 | public interface ICoder2
66 | {
67 | void Code(ISequentialInStream []inStreams,
68 | const UInt64 []inSizes,
69 | ISequentialOutStream []outStreams,
70 | UInt64 []outSizes,
71 | ICodeProgress progress);
72 | };
73 | */
74 |
75 | ///
76 | /// Provides the fields that represent properties idenitifiers for compressing.
77 | ///
78 | public enum CoderPropID
79 | {
80 | ///
81 | /// Specifies default property.
82 | ///
83 | DefaultProp = 0,
84 | ///
85 | /// Specifies size of dictionary.
86 | ///
87 | DictionarySize,
88 | ///
89 | /// Specifies size of memory for PPM*.
90 | ///
91 | UsedMemorySize,
92 | ///
93 | /// Specifies order for PPM methods.
94 | ///
95 | Order,
96 | ///
97 | /// Specifies Block Size.
98 | ///
99 | BlockSize,
100 | ///
101 | /// Specifies number of postion state bits for LZMA (0 <= x <= 4).
102 | ///
103 | PosStateBits,
104 | ///
105 | /// Specifies number of literal context bits for LZMA (0 <= x <= 8).
106 | ///
107 | LitContextBits,
108 | ///
109 | /// Specifies number of literal position bits for LZMA (0 <= x <= 4).
110 | ///
111 | LitPosBits,
112 | ///
113 | /// Specifies number of fast bytes for LZ*.
114 | ///
115 | NumFastBytes,
116 | ///
117 | /// Specifies match finder. LZMA: "BT2", "BT4" or "BT4B".
118 | ///
119 | MatchFinder,
120 | ///
121 | /// Specifies the number of match finder cyckes.
122 | ///
123 | MatchFinderCycles,
124 | ///
125 | /// Specifies number of passes.
126 | ///
127 | NumPasses,
128 | ///
129 | /// Specifies number of algorithm.
130 | ///
131 | Algorithm,
132 | ///
133 | /// Specifies the number of threads.
134 | ///
135 | NumThreads,
136 | ///
137 | /// Specifies mode with end marker.
138 | ///
139 | EndMarker
140 | }
141 |
142 | public interface ISetCoderProperties
143 | {
144 | void SetCoderProperties(CoderPropID[] propIDs, object[] properties);
145 | }
146 |
147 | public interface IWriteCoderProperties
148 | {
149 | void WriteCoderProperties(System.IO.Stream outStream);
150 | }
151 |
152 | public interface ISetDecoderProperties
153 | {
154 | void SetDecoderProperties(byte[] properties);
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | bld/
21 | [Bb]in/
22 | [Oo]bj/
23 | [Ll]og/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 | # Uncomment if you have tasks that create the project's static files in wwwroot
28 | #wwwroot/
29 |
30 | # MSTest test Results
31 | [Tt]est[Rr]esult*/
32 | [Bb]uild[Ll]og.*
33 |
34 | # NUNIT
35 | *.VisualState.xml
36 | TestResult.xml
37 |
38 | # Build Results of an ATL Project
39 | [Dd]ebugPS/
40 | [Rr]eleasePS/
41 | dlldata.c
42 |
43 | # DNX
44 | project.lock.json
45 | artifacts/
46 |
47 | *_i.c
48 | *_p.c
49 | *_i.h
50 | *.ilk
51 | *.meta
52 | *.obj
53 | *.pch
54 | *.pdb
55 | *.pgc
56 | *.pgd
57 | *.rsp
58 | *.sbr
59 | *.tlb
60 | *.tli
61 | *.tlh
62 | *.tmp
63 | *.tmp_proj
64 | *.log
65 | *.vspscc
66 | *.vssscc
67 | .builds
68 | *.pidb
69 | *.svclog
70 | *.scc
71 |
72 | # Chutzpah Test files
73 | _Chutzpah*
74 |
75 | # Visual C++ cache files
76 | ipch/
77 | *.aps
78 | *.ncb
79 | *.opendb
80 | *.opensdf
81 | *.sdf
82 | *.cachefile
83 | *.VC.db
84 | *.VC.VC.opendb
85 |
86 | # Visual Studio profiler
87 | *.psess
88 | *.vsp
89 | *.vspx
90 | *.sap
91 |
92 | # TFS 2012 Local Workspace
93 | $tf/
94 |
95 | # Guidance Automation Toolkit
96 | *.gpState
97 |
98 | # ReSharper is a .NET coding add-in
99 | _ReSharper*/
100 | *.[Rr]e[Ss]harper
101 | *.DotSettings.user
102 |
103 | # JustCode is a .NET coding add-in
104 | .JustCode
105 |
106 | # TeamCity is a build add-in
107 | _TeamCity*
108 |
109 | # DotCover is a Code Coverage Tool
110 | *.dotCover
111 |
112 | # NCrunch
113 | _NCrunch_*
114 | .*crunch*.local.xml
115 | nCrunchTemp_*
116 |
117 | # MightyMoose
118 | *.mm.*
119 | AutoTest.Net/
120 |
121 | # Web workbench (sass)
122 | .sass-cache/
123 |
124 | # Installshield output folder
125 | [Ee]xpress/
126 |
127 | # DocProject is a documentation generator add-in
128 | DocProject/buildhelp/
129 | DocProject/Help/*.HxT
130 | DocProject/Help/*.HxC
131 | DocProject/Help/*.hhc
132 | DocProject/Help/*.hhk
133 | DocProject/Help/*.hhp
134 | DocProject/Help/Html2
135 | DocProject/Help/html
136 |
137 | # Click-Once directory
138 | publish/
139 |
140 | # Publish Web Output
141 | *.[Pp]ublish.xml
142 | *.azurePubxml
143 | # TODO: Comment the next line if you want to checkin your web deploy settings
144 | # but database connection strings (with potential passwords) will be unencrypted
145 | *.pubxml
146 | *.publishproj
147 |
148 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
149 | # checkin your Azure Web App publish settings, but sensitive information contained
150 | # in these scripts will be unencrypted
151 | PublishScripts/
152 |
153 | # NuGet Packages
154 | *.nupkg
155 | # The packages folder can be ignored because of Package Restore
156 | **/packages/*
157 | # except build/, which is used as an MSBuild target.
158 | !**/packages/build/
159 | # Uncomment if necessary however generally it will be regenerated when needed
160 | #!**/packages/repositories.config
161 | # NuGet v3's project.json files produces more ignoreable files
162 | *.nuget.props
163 | *.nuget.targets
164 |
165 | # Microsoft Azure Build Output
166 | csx/
167 | *.build.csdef
168 |
169 | # Microsoft Azure Emulator
170 | ecf/
171 | rcf/
172 |
173 | # Windows Store app package directories and files
174 | AppPackages/
175 | BundleArtifacts/
176 | Package.StoreAssociation.xml
177 | _pkginfo.txt
178 |
179 | # Visual Studio cache files
180 | # files ending in .cache can be ignored
181 | *.[Cc]ache
182 | # but keep track of directories ending in .cache
183 | !*.[Cc]ache/
184 |
185 | # Others
186 | ClientBin/
187 | ~$*
188 | *~
189 | *.dbmdl
190 | *.dbproj.schemaview
191 | *.pfx
192 | *.publishsettings
193 | node_modules/
194 | orleans.codegen.cs
195 |
196 | # Since there are multiple workflows, uncomment next line to ignore bower_components
197 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
198 | #bower_components/
199 |
200 | # RIA/Silverlight projects
201 | Generated_Code/
202 |
203 | # Backup & report files from converting an old project file
204 | # to a newer Visual Studio version. Backup files are not needed,
205 | # because we have git ;-)
206 | _UpgradeReport_Files/
207 | Backup*/
208 | UpgradeLog*.XML
209 | UpgradeLog*.htm
210 |
211 | # SQL Server files
212 | *.mdf
213 | *.ldf
214 |
215 | # Business Intelligence projects
216 | *.rdl.data
217 | *.bim.layout
218 | *.bim_*.settings
219 |
220 | # Microsoft Fakes
221 | FakesAssemblies/
222 |
223 | # GhostDoc plugin setting file
224 | *.GhostDoc.xml
225 |
226 | # Node.js Tools for Visual Studio
227 | .ntvs_analysis.dat
228 |
229 | # Visual Studio 6 build log
230 | *.plg
231 |
232 | # Visual Studio 6 workspace options file
233 | *.opt
234 |
235 | # Visual Studio LightSwitch build output
236 | **/*.HTMLClient/GeneratedArtifacts
237 | **/*.DesktopClient/GeneratedArtifacts
238 | **/*.DesktopClient/ModelManifest.xml
239 | **/*.Server/GeneratedArtifacts
240 | **/*.Server/ModelManifest.xml
241 | _Pvt_Extensions
242 |
243 | # Paket dependency manager
244 | .paket/paket.exe
245 | paket-files/
246 |
247 | # FAKE - F# Make
248 | .fake/
249 |
250 | # JetBrains Rider
251 | .idea/
252 | *.sln.iml
253 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/Player/Replay.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using System.Security.Cryptography;
5 | using System.Text;
6 | using osu.Shared;
7 | using osu.Shared.Serialization;
8 |
9 | namespace osu_database_reader.Components.Player
10 | {
11 | public class Replay : ISerializable //used for both scores.db and .osr files
12 | {
13 | public GameMode GameMode;
14 | public int OsuVersion;
15 | public string BeatmapHash, PlayerName, ReplayHash;
16 | public ushort Count300, Count100, Count50, CountGeki, CountKatu, CountMiss;
17 | public int Score;
18 | public ushort Combo;
19 | public bool FullCombo;
20 | public Mods Mods;
21 | public string LifeGraphData; //null in scores.db, TODO: parse this when implementing .osr
22 | public DateTime TimePlayed;
23 |
24 | public byte[] ReplayData
25 | {
26 | get => _replayData;
27 | set {
28 | if (_replayData != value) {
29 | _frames = null;
30 | _replayData = value;
31 | }
32 | }
33 | }
34 |
35 | public ReplayFrame[] ReplayFrames
36 | {
37 | get {
38 | if (_replayData == null) return null;
39 | if (_frames != null)
40 | return _frames;
41 | else {
42 | byte[] decomp = LZMACoder.Decompress(_replayData);
43 | string str = Encoding.ASCII.GetString(decomp); //ascii should be faster than UTF8, though not that it matters
44 | return _frames = ReplayFrame.FromStrings(ref str);
45 | }
46 | }
47 | set {
48 | _frames = value;
49 | var clearFrames = string.Join(";", value.Select(x => x.ToString()));
50 | var decomp = Encoding.ASCII.GetBytes(clearFrames);
51 | _replayData = LZMACoder.Compress(decomp);
52 | }
53 | }
54 |
55 | public long? ScoreId;
56 |
57 | private byte[] _replayData; //null in scores.db
58 | private ReplayFrame[] _frames;
59 |
60 | public static Replay Read(string path)
61 | {
62 | using var stream = File.OpenRead(path);
63 | return Read(stream);
64 | }
65 |
66 | public static Replay Read(Stream stream) {
67 | using var r = new SerializationReader(stream);
68 | return ReadFromReader(r);
69 | }
70 |
71 | public static Replay ReadFromReader(SerializationReader r) {
72 | var replay = new Replay();
73 | replay.ReadFromStream(r);
74 | return replay;
75 | }
76 |
77 | ///
78 | /// Set the replay hash for a given rank
79 | ///
80 | /// A rank such as SS, S, A, etc
81 | ///
82 | public void SetReplayHash(Ranking rank)
83 | {
84 | byte[] bytes = MD5.Create().ComputeHash(Encoding.ASCII.GetBytes($"{Combo}osu{PlayerName}{BeatmapHash}{Score}{rank}"));
85 | ReplayHash = string.Join(string.Empty, bytes.Select(x => x.ToString("X2"))).ToLower();
86 | }
87 |
88 | public void ReadFromStream(SerializationReader r)
89 | {
90 | GameMode = (GameMode) r.ReadByte();
91 | OsuVersion = r.ReadInt32();
92 | BeatmapHash = r.ReadString();
93 | PlayerName = r.ReadString();
94 | ReplayHash = r.ReadString();
95 |
96 | Count300 = r.ReadUInt16();
97 | Count100 = r.ReadUInt16();
98 | Count50 = r.ReadUInt16();
99 | CountGeki = r.ReadUInt16();
100 | CountKatu = r.ReadUInt16();
101 | CountMiss = r.ReadUInt16();
102 |
103 | Score = r.ReadInt32();
104 | Combo = r.ReadUInt16();
105 | FullCombo = r.ReadBoolean();
106 | Mods = (Mods) r.ReadInt32();
107 | LifeGraphData = r.ReadString();
108 | TimePlayed = r.ReadDateTime();
109 | _replayData = r.ReadBytes();
110 |
111 | ScoreId = OsuVersion switch
112 | {
113 | >= OsuVersions.ReplayScoreId64Bit => r.ReadInt64(),
114 | >= OsuVersions.FirstOsz2 => r.ReadInt32(),
115 | _ => null,
116 | };
117 | }
118 |
119 | public void WriteToStream(SerializationWriter w)
120 | {
121 | w.Write((byte)GameMode);
122 | w.Write(OsuVersion);
123 | w.Write(BeatmapHash);
124 | w.Write(PlayerName);
125 | w.Write(ReplayHash);
126 |
127 | w.Write(Count300);
128 | w.Write(Count100);
129 | w.Write(Count50);
130 | w.Write(CountGeki);
131 | w.Write(CountKatu);
132 | w.Write(CountMiss);
133 |
134 | w.Write(Score);
135 | w.Write(Combo);
136 | w.Write(FullCombo);
137 | w.Write((int)Mods);
138 | w.Write(LifeGraphData);
139 | w.Write(TimePlayed);
140 | w.Write(_replayData);
141 |
142 | switch (OsuVersion)
143 | {
144 | case >= OsuVersions.ReplayScoreId64Bit:
145 | w.Write(ScoreId ?? 0L);
146 | break;
147 | case >= OsuVersions.FirstOsz2:
148 | w.Write((int) (ScoreId ?? 0));
149 | break;
150 | }
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/Events/EventBase.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace osu_database_reader.Components.Events
4 | {
5 | public abstract class EventBase
6 | {
7 | public string Line { get; internal set; }
8 |
9 | public static EventBase FromString(string line)
10 | {
11 | if (string.IsNullOrEmpty(line))
12 | throw new ArgumentException(line);
13 |
14 | try
15 | {
16 | var split = line.Split(',');
17 |
18 | // https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs#L103
19 | // https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs#L301
20 | switch (split[0])
21 | {
22 | case "0":
23 | case "Background":
24 | {
25 | // fields 1, 3 and 4 are unknown
26 | var path = split[2].Trim('"');
27 |
28 | return new BackgroundEvent
29 | {
30 | Path = path,
31 | Line = line,
32 | };
33 | }
34 | case "1":
35 | case "Video":
36 | {
37 | var offset = int.Parse(split[1], Constants.NumberFormat);
38 | var path = split[2].Trim('"');
39 |
40 | return new VideoEvent
41 | {
42 | Offset = offset,
43 | Path = path,
44 | Line = line,
45 | };
46 | }
47 | case "2":
48 | case "Break":
49 | {
50 | var start = double.Parse(split[1], Constants.NumberFormat);
51 | var end = double.Parse(split[2], Constants.NumberFormat);
52 |
53 | return new BreakEvent
54 | {
55 | StartTime = start,
56 | EndTime = end,
57 | Line = line,
58 | };
59 | }
60 | /*
61 | case "3":
62 | case "Colour":
63 | {
64 | // TODO
65 | }
66 | */
67 | case "4":
68 | case "Sprite":
69 | {
70 | var layer = split[1];
71 | var origin = split[2];
72 | var path = split[3].Trim('"');
73 | var x = float.Parse(split[4], Constants.NumberFormat);
74 | var y = float.Parse(split[5], Constants.NumberFormat);
75 |
76 | return new SpriteEvent
77 | {
78 | Layer = layer,
79 | Origin = origin,
80 | Path = path,
81 | X = x,
82 | Y = y,
83 | Line = line,
84 | };
85 | }
86 | case "5":
87 | case "Sample":
88 | {
89 | var time = double.Parse(split[1], Constants.NumberFormat);
90 | var layer = split[2];
91 | var path = split[3];
92 |
93 | var volume = split.Length > 3 ? float.Parse(split[4], Constants.NumberFormat) : (float?) null;
94 |
95 | return new SampleEvent
96 | {
97 | Time = time,
98 | Layer = layer,
99 | Path = path,
100 | Volume = volume,
101 | Line = line,
102 | };
103 | }
104 | case "6":
105 | case "Animation":
106 | {
107 | var layer = split[1];
108 | var origin = split[2];
109 | var path = split[3].Trim('"');
110 | var x = float.Parse(split[4], Constants.NumberFormat);
111 | var y = float.Parse(split[5], Constants.NumberFormat);
112 | var frameCount = int.Parse(split[6], Constants.NumberFormat);
113 | var frameDelay = double.Parse(split[7], Constants.NumberFormat);
114 |
115 | var loopType = split.Length > 8 ? split[8] : null;
116 |
117 | return new AnimationEvent
118 | {
119 | Layer = layer,
120 | Origin = origin,
121 | Path = path,
122 | X = x,
123 | Y = y,
124 | FrameCount = frameCount,
125 | FrameDelay = frameDelay,
126 | LoopType = loopType,
127 | Line = line,
128 | };
129 | }
130 | default:
131 | {
132 | return new FallbackEvent
133 | {
134 | Line = line,
135 | };
136 | }
137 | }
138 | }
139 | catch
140 | {
141 | return new FallbackEvent
142 | {
143 | Line = line,
144 | };
145 | }
146 | }
147 |
148 | private class FallbackEvent : EventBase
149 | {
150 | }
151 | }
152 | }
--------------------------------------------------------------------------------
/UnitTestProject/TestsText.cs:
--------------------------------------------------------------------------------
1 | using System.Linq;
2 | using System.Reflection;
3 | using FluentAssertions;
4 | using osu_database_reader;
5 | using osu.Shared;
6 | using osu_database_reader.Components.Events;
7 | using osu_database_reader.Components.HitObjects;
8 | using osu_database_reader.TextFiles;
9 | using Xunit;
10 |
11 | namespace UnitTestProject
12 | {
13 | public class TestsText
14 | {
15 | [Fact]
16 | public void CheckBeatmapV14()
17 | {
18 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Beatmaps.beatmap_v14.osu");
19 | var bm = BeatmapFile.Read(stream);
20 |
21 | bm.FileFormatVersion.Should().Be(14);
22 |
23 | // general
24 | bm.AudioFilename.Should().Be("audio.mp3");
25 | bm.AudioLeadIn.Should().Be(0);
26 | bm.PreviewTime.Should().Be(64366);
27 | bm.Countdown.Should().BeTrue();
28 | // bm.SampleSet.Should().Be("audio.mp3");
29 | bm.StackLeniency.Should().Be(0.7f);
30 | bm.Mode.Should().Be(0);
31 | bm.LetterboxInBreaks.Should().BeFalse();
32 | bm.WidescreenStoryboard.Should().BeTrue();
33 |
34 | // editor
35 | bm.Bookmarks.Should().BeEquivalentTo(1500, 7614, 13729, 22901, 35130, 47359, 59589, 64175, 90162, 93219, 105449);
36 | bm.DistanceSpacing.Should().Be(0.3);
37 | bm.BeatDivisor.Should().Be(4);
38 | bm.GridSize.Should().Be(8);
39 | bm.TimelineZoom.Should().BeApproximately(1.950003f, 0.000001f);
40 |
41 | // metadata
42 | bm.Title.Should().Be("The Whole Rest");
43 | bm.TitleUnicode.Should().Be("The Whole Rest");
44 | bm.Artist.Should().Be("KIVA");
45 | bm.ArtistUnicode.Should().Be("KIVΛ");
46 | bm.Creator.Should().Be("Ilex");
47 | bm.Version.Should().Be("Insane v1");
48 | bm.Source.Should().Be("Cytus II");
49 | bm.Tags.Should().Be("Cytus II 2 OST");
50 | bm.BeatmapID.Should().Be(2026717);
51 | bm.BeatmapSetID.Should().Be(968597);
52 |
53 | // difficulty
54 | bm.HPDrainRate.Should().Be(6);
55 | bm.CircleSize.Should().Be(4);
56 | bm.OverallDifficulty.Should().Be(7.3f);
57 | bm.ApproachRate.Should().Be(9.4f);
58 | bm.SliderMultiplier.Should().Be(1.8);
59 | bm.SliderTickRate.Should().Be(1);
60 |
61 | // events
62 | bm.Events.Should().HaveCount(4);
63 |
64 | bm.Events[0].Should().BeOfType();
65 | ((BackgroundEvent) bm.Events[0]).Path.Should().Be("usedtobe.jpg");
66 | bm.Events[1].Should().BeOfType();
67 | ((VideoEvent) bm.Events[1]).Path.Should().Be("Cytus II Opening - The Whole Rest.mp4");
68 | ((VideoEvent) bm.Events[1]).Offset.Should().Be(0);
69 | bm.Events[2].Should().BeOfType();
70 | ((BreakEvent) bm.Events[2]).StartTime.Should().Be(13929);
71 | ((BreakEvent) bm.Events[2]).EndTime.Should().Be(22301);
72 | bm.Events[3].Should().BeOfType();
73 | ((BreakEvent) bm.Events[3]).StartTime.Should().Be(47559);
74 | ((BreakEvent) bm.Events[3]).EndTime.Should().Be(52874);
75 |
76 | // timing points
77 | bm.TimingPoints.Should().HaveCount(7);
78 | bm.TimingPoints[0].Time.Should().Be(1500);
79 | bm.TimingPoints[0].Kiai.Should().BeFalse();
80 | bm.TimingPoints[0].MsPerQuarter.Should().Be(382.165605095541);
81 | bm.TimingPoints[0].SampleSet.Should().Be(1);
82 | bm.TimingPoints[0].SampleVolume.Should().Be(100);
83 | bm.TimingPoints[0].CustomSampleSet.Should().Be(0);
84 | bm.TimingPoints[0].TimingChange.Should().BeTrue();
85 | bm.TimingPoints[0].TimingSignature.Should().Be(4);
86 |
87 | bm.TimingPoints[1].MsPerQuarter.Should().Be(-133.333333333333);
88 |
89 | // hit objects
90 | bm.HitObjects.Should().HaveCount(335);
91 | bm.HitObjects.Where(x => (x.Type & HitObjectType.Normal) != 0).Should().HaveCount(252);
92 | bm.HitObjects.Where(x => (x.Type & HitObjectType.Slider) != 0).Should().HaveCount(83);
93 |
94 | var firstNoteBase = bm.HitObjects[0];
95 | firstNoteBase.Should().BeOfType();
96 | var firstNote = (HitObjectCircle)firstNoteBase;
97 | firstNote.Time.Should().Be(1500);
98 | firstNote.X.Should().Be(136);
99 | firstNote.Y.Should().Be(87);
100 | firstNote.IsNewCombo.Should().BeTrue();
101 | firstNote.Type.Should().Be(HitObjectType.Normal | HitObjectType.NewCombo);
102 | firstNote.HitSound.Should().Be(HitSound.None);
103 | firstNote.SoundSampleData.Should().Be("0:0:0:0:");
104 | }
105 |
106 | [Fact]
107 | public void FloatingPointCoordinates()
108 | {
109 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Beatmaps.128.osu");
110 | var bm = BeatmapFile.Read(stream);
111 |
112 | bm.FileFormatVersion.Should().Be(3);
113 | bm.BeatmapID.Should().Be(128);
114 | bm.BeatmapSetID.Should().Be(46);
115 |
116 | // hit objects
117 | bm.HitObjects.Should().HaveCount(76);
118 |
119 | var noteWithFloat1 = bm.HitObjects[68];
120 | var note1 = noteWithFloat1.Should().BeOfType().Which;
121 | note1.Time.Should().Be(71034);
122 | note1.X.Should().Be(64);
123 | note1.Y.Should().Be(320);
124 |
125 | var noteWithFloat2 = bm.HitObjects[69];
126 | var note2 = noteWithFloat2.Should().BeOfType().Which;
127 | note2.Time.Should().Be(72812);
128 | note2.X.Should().Be(447);
129 | note2.Y.Should().Be(223);
130 | }
131 |
132 | [Fact]
133 | public void FloatingPointSliderCoordinates()
134 | {
135 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Beatmaps.4615284.osu");
136 | var bm = BeatmapFile.Read(stream);
137 |
138 | bm.FileFormatVersion.Should().Be(14);
139 | bm.BeatmapID.Should().Be(4615284);
140 | bm.BeatmapSetID.Should().Be(2182178);
141 |
142 | // hit objects
143 | bm.HitObjects.Should().HaveCount(389);
144 |
145 | var hitObject1 = bm.HitObjects[175];
146 | var slider1 = hitObject1.Should().BeOfType().Which;
147 | slider1.X.Should().Be(380);
148 | slider1.Y.Should().Be(108);
149 | slider1.Time.Should().Be(57137);
150 | slider1.CurveType.Should().Be(CurveType.Bezier);
151 | slider1.Points.Should().HaveCount(2);
152 | slider1.Points[0].X.Should().Be(362);
153 | slider1.Points[0].Y.Should().Be(151);
154 | slider1.Points[1].X.Should().Be(382);
155 | slider1.Points[1].Y.Should().Be(199);
156 | }
157 |
158 | [Fact]
159 | public void ManiaBeatmap()
160 | {
161 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Beatmaps.294291.osu");
162 | var bm = BeatmapFile.Read(stream);
163 |
164 | bm.FileFormatVersion.Should().Be(12);
165 | bm.BeatmapID.Should().Be(294291);
166 | bm.BeatmapSetID.Should().Be(113273);
167 |
168 | // hit objects
169 | bm.HitObjects.Should().HaveCount(1219);
170 |
171 | var hitObject1 = bm.HitObjects[10];
172 | var holdNote1 = hitObject1.Should().BeOfType().Which;
173 | holdNote1.X.Should().Be(329);
174 | holdNote1.Y.Should().Be(192);
175 | holdNote1.Time.Should().Be(1574);
176 | holdNote1.EndTime.Should().Be(1890);
177 | holdNote1.SoundSampleData.Should().Be("0:0:0:0:");
178 |
179 | var hitObject2 = bm.HitObjects[121];
180 | var holdNote2 = hitObject2.Should().BeOfType().Which;
181 | holdNote2.X.Should().Be(256);
182 | holdNote2.Y.Should().Be(192);
183 | holdNote2.Time.Should().Be(20363);
184 | holdNote2.EndTime.Should().Be(20679);
185 | holdNote2.SoundSampleData.Should().Be("0:0:0:70:normal-hitclap.wav");
186 | }
187 |
188 | [Fact]
189 | public void GarbageBetweenSections()
190 | {
191 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Beatmaps.3718961.osu");
192 | var bm = BeatmapFile.Read(stream);
193 |
194 | bm.FileFormatVersion.Should().Be(14);
195 | bm.BeatmapID.Should().Be(3718961);
196 | bm.BeatmapSetID.Should().Be(-1);
197 | }
198 |
199 | [Fact]
200 | public void InvalidMetadataLine()
201 | {
202 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Beatmaps.1391940.osu");
203 | var bm = BeatmapFile.Read(stream);
204 |
205 | bm.FileFormatVersion.Should().Be(14);
206 | bm.BeatmapID.Should().Be(1391940);
207 | bm.BeatmapSetID.Should().Be(654452);
208 | }
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Compress/LZ/LzBinTree.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SevenZip.Compression.LZ
4 | {
5 | public class BinTree : InWindow, IMatchFinder
6 | {
7 | UInt32 _cyclicBufferPos;
8 | UInt32 _cyclicBufferSize = 0;
9 | UInt32 _matchMaxLen;
10 |
11 | UInt32[] _son;
12 | UInt32[] _hash;
13 |
14 | UInt32 _cutValue = 0xFF;
15 | UInt32 _hashMask;
16 | UInt32 _hashSizeSum = 0;
17 |
18 | bool HASH_ARRAY = true;
19 |
20 | const UInt32 kHash2Size = 1 << 10;
21 | const UInt32 kHash3Size = 1 << 16;
22 | const UInt32 kBT2HashSize = 1 << 16;
23 | const UInt32 kStartMaxLen = 1;
24 | const UInt32 kHash3Offset = kHash2Size;
25 | const UInt32 kEmptyHashValue = 0;
26 | const UInt32 kMaxValForNormalize = ((UInt32)1 << 31) - 1;
27 |
28 | UInt32 kNumHashDirectBytes = 0;
29 | UInt32 kMinMatchCheck = 4;
30 | UInt32 kFixHashSize = kHash2Size + kHash3Size;
31 |
32 | public void SetType(int numHashBytes)
33 | {
34 | HASH_ARRAY = (numHashBytes > 2);
35 | if (HASH_ARRAY)
36 | {
37 | kNumHashDirectBytes = 0;
38 | kMinMatchCheck = 4;
39 | kFixHashSize = kHash2Size + kHash3Size;
40 | }
41 | else
42 | {
43 | kNumHashDirectBytes = 2;
44 | kMinMatchCheck = 2 + 1;
45 | kFixHashSize = 0;
46 | }
47 | }
48 |
49 | public new void SetStream(System.IO.Stream stream) { base.SetStream(stream); }
50 | public new void ReleaseStream() { base.ReleaseStream(); }
51 |
52 | public new void Init()
53 | {
54 | base.Init();
55 | for (UInt32 i = 0; i < _hashSizeSum; i++)
56 | _hash[i] = kEmptyHashValue;
57 | _cyclicBufferPos = 0;
58 | ReduceOffsets(-1);
59 | }
60 |
61 | public new void MovePos()
62 | {
63 | if (++_cyclicBufferPos >= _cyclicBufferSize)
64 | _cyclicBufferPos = 0;
65 | base.MovePos();
66 | if (_pos == kMaxValForNormalize)
67 | Normalize();
68 | }
69 |
70 | public new Byte GetIndexByte(Int32 index) { return base.GetIndexByte(index); }
71 |
72 | public new UInt32 GetMatchLen(Int32 index, UInt32 distance, UInt32 limit)
73 | { return base.GetMatchLen(index, distance, limit); }
74 |
75 | public new UInt32 GetNumAvailableBytes() { return base.GetNumAvailableBytes(); }
76 |
77 | public void Create(UInt32 historySize, UInt32 keepAddBufferBefore,
78 | UInt32 matchMaxLen, UInt32 keepAddBufferAfter)
79 | {
80 | if (historySize > kMaxValForNormalize - 256)
81 | throw new Exception();
82 | _cutValue = 16 + (matchMaxLen >> 1);
83 |
84 | UInt32 windowReservSize = ((historySize + keepAddBufferBefore + matchMaxLen + keepAddBufferAfter) / 2) + 256;
85 |
86 | base.Create(historySize + keepAddBufferBefore, matchMaxLen + keepAddBufferAfter, windowReservSize);
87 |
88 | _matchMaxLen = matchMaxLen;
89 |
90 | UInt32 cyclicBufferSize = historySize + 1;
91 | if (_cyclicBufferSize != cyclicBufferSize)
92 | _son = new UInt32[(_cyclicBufferSize = cyclicBufferSize) * 2];
93 |
94 | UInt32 hs = kBT2HashSize;
95 |
96 | if (HASH_ARRAY)
97 | {
98 | hs = historySize - 1;
99 | hs |= (hs >> 1);
100 | hs |= (hs >> 2);
101 | hs |= (hs >> 4);
102 | hs |= (hs >> 8);
103 | hs >>= 1;
104 | hs |= 0xFFFF;
105 | if (hs > (1 << 24))
106 | hs >>= 1;
107 | _hashMask = hs;
108 | hs++;
109 | hs += kFixHashSize;
110 | }
111 | if (hs != _hashSizeSum)
112 | _hash = new UInt32[_hashSizeSum = hs];
113 | }
114 |
115 | public UInt32 GetMatches(UInt32[] distances)
116 | {
117 | UInt32 lenLimit;
118 | if (_pos + _matchMaxLen <= _streamPos)
119 | lenLimit = _matchMaxLen;
120 | else
121 | {
122 | lenLimit = _streamPos - _pos;
123 | if (lenLimit < kMinMatchCheck)
124 | {
125 | MovePos();
126 | return 0;
127 | }
128 | }
129 |
130 | UInt32 offset = 0;
131 | UInt32 matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0;
132 | UInt32 cur = _bufferOffset + _pos;
133 | UInt32 maxLen = kStartMaxLen; // to avoid items for len < hashSize;
134 | UInt32 hashValue, hash2Value = 0, hash3Value = 0;
135 |
136 | if (HASH_ARRAY)
137 | {
138 | UInt32 temp = CRC.Table[_bufferBase[cur]] ^ _bufferBase[cur + 1];
139 | hash2Value = temp & (kHash2Size - 1);
140 | temp ^= ((UInt32)(_bufferBase[cur + 2]) << 8);
141 | hash3Value = temp & (kHash3Size - 1);
142 | hashValue = (temp ^ (CRC.Table[_bufferBase[cur + 3]] << 5)) & _hashMask;
143 | }
144 | else
145 | hashValue = _bufferBase[cur] ^ ((UInt32)(_bufferBase[cur + 1]) << 8);
146 |
147 | UInt32 curMatch = _hash[kFixHashSize + hashValue];
148 | if (HASH_ARRAY)
149 | {
150 | UInt32 curMatch2 = _hash[hash2Value];
151 | UInt32 curMatch3 = _hash[kHash3Offset + hash3Value];
152 | _hash[hash2Value] = _pos;
153 | _hash[kHash3Offset + hash3Value] = _pos;
154 | if (curMatch2 > matchMinPos)
155 | if (_bufferBase[_bufferOffset + curMatch2] == _bufferBase[cur])
156 | {
157 | distances[offset++] = maxLen = 2;
158 | distances[offset++] = _pos - curMatch2 - 1;
159 | }
160 | if (curMatch3 > matchMinPos)
161 | if (_bufferBase[_bufferOffset + curMatch3] == _bufferBase[cur])
162 | {
163 | if (curMatch3 == curMatch2)
164 | offset -= 2;
165 | distances[offset++] = maxLen = 3;
166 | distances[offset++] = _pos - curMatch3 - 1;
167 | curMatch2 = curMatch3;
168 | }
169 | if (offset != 0 && curMatch2 == curMatch)
170 | {
171 | offset -= 2;
172 | maxLen = kStartMaxLen;
173 | }
174 | }
175 |
176 | _hash[kFixHashSize + hashValue] = _pos;
177 |
178 | UInt32 ptr0 = (_cyclicBufferPos << 1) + 1;
179 | UInt32 ptr1 = (_cyclicBufferPos << 1);
180 |
181 | UInt32 len0, len1;
182 | len0 = len1 = kNumHashDirectBytes;
183 |
184 | if (kNumHashDirectBytes != 0)
185 | {
186 | if (curMatch > matchMinPos)
187 | {
188 | if (_bufferBase[_bufferOffset + curMatch + kNumHashDirectBytes] !=
189 | _bufferBase[cur + kNumHashDirectBytes])
190 | {
191 | distances[offset++] = maxLen = kNumHashDirectBytes;
192 | distances[offset++] = _pos - curMatch - 1;
193 | }
194 | }
195 | }
196 |
197 | UInt32 count = _cutValue;
198 |
199 | while(true)
200 | {
201 | if(curMatch <= matchMinPos || count-- == 0)
202 | {
203 | _son[ptr0] = _son[ptr1] = kEmptyHashValue;
204 | break;
205 | }
206 | UInt32 delta = _pos - curMatch;
207 | UInt32 cyclicPos = ((delta <= _cyclicBufferPos) ?
208 | (_cyclicBufferPos - delta) :
209 | (_cyclicBufferPos - delta + _cyclicBufferSize)) << 1;
210 |
211 | UInt32 pby1 = _bufferOffset + curMatch;
212 | UInt32 len = Math.Min(len0, len1);
213 | if (_bufferBase[pby1 + len] == _bufferBase[cur + len])
214 | {
215 | while(++len != lenLimit)
216 | if (_bufferBase[pby1 + len] != _bufferBase[cur + len])
217 | break;
218 | if (maxLen < len)
219 | {
220 | distances[offset++] = maxLen = len;
221 | distances[offset++] = delta - 1;
222 | if (len == lenLimit)
223 | {
224 | _son[ptr1] = _son[cyclicPos];
225 | _son[ptr0] = _son[cyclicPos + 1];
226 | break;
227 | }
228 | }
229 | }
230 | if (_bufferBase[pby1 + len] < _bufferBase[cur + len])
231 | {
232 | _son[ptr1] = curMatch;
233 | ptr1 = cyclicPos + 1;
234 | curMatch = _son[ptr1];
235 | len1 = len;
236 | }
237 | else
238 | {
239 | _son[ptr0] = curMatch;
240 | ptr0 = cyclicPos;
241 | curMatch = _son[ptr0];
242 | len0 = len;
243 | }
244 | }
245 | MovePos();
246 | return offset;
247 | }
248 |
249 | public void Skip(UInt32 num)
250 | {
251 | do
252 | {
253 | UInt32 lenLimit;
254 | if (_pos + _matchMaxLen <= _streamPos)
255 | lenLimit = _matchMaxLen;
256 | else
257 | {
258 | lenLimit = _streamPos - _pos;
259 | if (lenLimit < kMinMatchCheck)
260 | {
261 | MovePos();
262 | continue;
263 | }
264 | }
265 |
266 | UInt32 matchMinPos = (_pos > _cyclicBufferSize) ? (_pos - _cyclicBufferSize) : 0;
267 | UInt32 cur = _bufferOffset + _pos;
268 |
269 | UInt32 hashValue;
270 |
271 | if (HASH_ARRAY)
272 | {
273 | UInt32 temp = CRC.Table[_bufferBase[cur]] ^ _bufferBase[cur + 1];
274 | UInt32 hash2Value = temp & (kHash2Size - 1);
275 | _hash[hash2Value] = _pos;
276 | temp ^= ((UInt32)(_bufferBase[cur + 2]) << 8);
277 | UInt32 hash3Value = temp & (kHash3Size - 1);
278 | _hash[kHash3Offset + hash3Value] = _pos;
279 | hashValue = (temp ^ (CRC.Table[_bufferBase[cur + 3]] << 5)) & _hashMask;
280 | }
281 | else
282 | hashValue = _bufferBase[cur] ^ ((UInt32)(_bufferBase[cur + 1]) << 8);
283 |
284 | UInt32 curMatch = _hash[kFixHashSize + hashValue];
285 | _hash[kFixHashSize + hashValue] = _pos;
286 |
287 | UInt32 ptr0 = (_cyclicBufferPos << 1) + 1;
288 | UInt32 ptr1 = (_cyclicBufferPos << 1);
289 |
290 | UInt32 len0, len1;
291 | len0 = len1 = kNumHashDirectBytes;
292 |
293 | UInt32 count = _cutValue;
294 | while (true)
295 | {
296 | if (curMatch <= matchMinPos || count-- == 0)
297 | {
298 | _son[ptr0] = _son[ptr1] = kEmptyHashValue;
299 | break;
300 | }
301 |
302 | UInt32 delta = _pos - curMatch;
303 | UInt32 cyclicPos = ((delta <= _cyclicBufferPos) ?
304 | (_cyclicBufferPos - delta) :
305 | (_cyclicBufferPos - delta + _cyclicBufferSize)) << 1;
306 |
307 | UInt32 pby1 = _bufferOffset + curMatch;
308 | UInt32 len = Math.Min(len0, len1);
309 | if (_bufferBase[pby1 + len] == _bufferBase[cur + len])
310 | {
311 | while (++len != lenLimit)
312 | if (_bufferBase[pby1 + len] != _bufferBase[cur + len])
313 | break;
314 | if (len == lenLimit)
315 | {
316 | _son[ptr1] = _son[cyclicPos];
317 | _son[ptr0] = _son[cyclicPos + 1];
318 | break;
319 | }
320 | }
321 | if (_bufferBase[pby1 + len] < _bufferBase[cur + len])
322 | {
323 | _son[ptr1] = curMatch;
324 | ptr1 = cyclicPos + 1;
325 | curMatch = _son[ptr1];
326 | len1 = len;
327 | }
328 | else
329 | {
330 | _son[ptr0] = curMatch;
331 | ptr0 = cyclicPos;
332 | curMatch = _son[ptr0];
333 | len0 = len;
334 | }
335 | }
336 | MovePos();
337 | }
338 | while (--num != 0);
339 | }
340 |
341 | void NormalizeLinks(UInt32[] items, UInt32 numItems, UInt32 subValue)
342 | {
343 | for (UInt32 i = 0; i < numItems; i++)
344 | {
345 | UInt32 value = items[i];
346 | if (value <= subValue)
347 | value = kEmptyHashValue;
348 | else
349 | value -= subValue;
350 | items[i] = value;
351 | }
352 | }
353 |
354 | void Normalize()
355 | {
356 | UInt32 subValue = _pos - _cyclicBufferSize;
357 | NormalizeLinks(_son, _cyclicBufferSize * 2, subValue);
358 | NormalizeLinks(_hash, _hashSizeSum, subValue);
359 | ReduceOffsets((Int32)subValue);
360 | }
361 |
362 | public void SetCutValue(UInt32 cutValue) { _cutValue = cutValue; }
363 | }
364 | }
365 |
--------------------------------------------------------------------------------
/osu-database-reader/Components/Beatmaps/BeatmapEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using osu.Shared;
5 | using osu.Shared.Serialization;
6 | using osu_database_reader.BinaryFiles;
7 |
8 | namespace osu_database_reader.Components.Beatmaps
9 | {
10 | ///
11 | /// An entry in with information about a beatmap.
12 | ///
13 | public class BeatmapEntry : ISerializable
14 | {
15 | public string Artist, ArtistUnicode;
16 | public string Title, TitleUnicode;
17 | /// The mapper username
18 | public string Creator;
19 | /// The difficulty name
20 | public string Version;
21 | public string AudioFileName;
22 | public string BeatmapChecksum;
23 | public string BeatmapFileName;
24 | public SubmissionStatus RankedStatus;
25 | public ushort CountHitCircles, CountSliders, CountSpinners;
26 | public DateTime LastModifiedTime;
27 | public float ApproachRate, CircleSize, HPDrainRate, OveralDifficulty;
28 | public double SliderVelocity;
29 | public Dictionary DiffStarRatingStandard, DiffStarRatingTaiko, DiffStarRatingCtB, DiffStarRatingMania;
30 | public int DrainTimeSeconds; //NOTE: in s
31 | /// Total beatmap time in milliseconds
32 | public int TotalTime; //NOTE: in ms
33 | /// Point in the audio track to play in song select, in milliseconds
34 | public int AudioPreviewTime;
35 | public List TimingPoints;
36 | public int BeatmapId, BeatmapSetId, ThreadId;
37 | public Ranking GradeStandard, GradeTaiko, GradeCtB, GradeMania;
38 | public short OffsetLocal;
39 | public float StackLeniency;
40 | public GameMode GameMode;
41 | public string SongSource, SongTags;
42 | public short OffsetOnline;
43 | public string TitleFont;
44 | public bool Unplayed;
45 | public DateTime LastPlayed;
46 | public bool IsOsz2;
47 | public string FolderName;
48 | public DateTime LastCheckAgainstOsuRepo;
49 | public bool IgnoreBeatmapSounds, IgnoreBeatmapSkin, DisableStoryBoard, DisableVideo, VisualOverride;
50 | /// Unknown unused entry
51 | public short OldUnknown1;
52 | public int LastEditTime;
53 | public byte ManiaScrollSpeed;
54 |
55 | private int _version;
56 |
57 | public static BeatmapEntry ReadFromReader(SerializationReader r, int version) {
58 | var e = new BeatmapEntry {
59 | _version = version,
60 | };
61 |
62 | e.ReadFromStream(r);
63 |
64 | return e;
65 | }
66 |
67 | public void ReadFromStream(SerializationReader r)
68 | {
69 | Artist = r.ReadString();
70 | if (_version >= OsuVersions.FirstOsz2)
71 | ArtistUnicode = r.ReadString();
72 | Title = r.ReadString();
73 | if (_version >= OsuVersions.FirstOsz2)
74 | TitleUnicode = r.ReadString();
75 | Creator = r.ReadString();
76 | Version = r.ReadString();
77 | AudioFileName = r.ReadString();
78 | BeatmapChecksum = r.ReadString(); //always 32 in length, so the 2 preceding bytes in the file are practically wasting space
79 | BeatmapFileName = r.ReadString();
80 |
81 | RankedStatus = (SubmissionStatus)r.ReadByte();
82 | CountHitCircles = r.ReadUInt16();
83 | CountSliders = r.ReadUInt16();
84 | CountSpinners = r.ReadUInt16();
85 | LastModifiedTime = r.ReadDateTime();
86 |
87 | if (_version >= OsuVersions.FloatDifficultyValues)
88 | {
89 | ApproachRate = r.ReadSingle();
90 | CircleSize = r.ReadSingle();
91 | HPDrainRate = r.ReadSingle();
92 | OveralDifficulty = r.ReadSingle();
93 | }
94 | else
95 | {
96 | ApproachRate = r.ReadByte();
97 | CircleSize = r.ReadByte();
98 | HPDrainRate = r.ReadByte();
99 | OveralDifficulty = r.ReadByte();
100 | }
101 |
102 | SliderVelocity = r.ReadDouble();
103 |
104 | if (_version >= OsuVersions.ReducedOsuDbSize)
105 | {
106 | DiffStarRatingStandard = r.ReadDictionary().ToDictionary(kvp => kvp.Key, kvp => (double)kvp.Value);
107 | DiffStarRatingTaiko = r.ReadDictionary().ToDictionary(kvp => kvp.Key, kvp => (double)kvp.Value);
108 | DiffStarRatingCtB = r.ReadDictionary().ToDictionary(kvp => kvp.Key, kvp => (double)kvp.Value);
109 | DiffStarRatingMania = r.ReadDictionary().ToDictionary(kvp => kvp.Key, kvp => (double)kvp.Value);
110 | }
111 | else if (_version >= OsuVersions.FloatDifficultyValues)
112 | {
113 | DiffStarRatingStandard = r.ReadDictionary();
114 | DiffStarRatingTaiko = r.ReadDictionary();
115 | DiffStarRatingCtB = r.ReadDictionary();
116 | DiffStarRatingMania = r.ReadDictionary();
117 |
118 | // TODO: there may be different reading behavior for versions before 20190204, 20200916, 20200504 and 20191024 here.
119 | }
120 |
121 | DrainTimeSeconds = r.ReadInt32();
122 | TotalTime = r.ReadInt32();
123 | AudioPreviewTime = r.ReadInt32();
124 |
125 | TimingPoints = r.ReadSerializableList();
126 | BeatmapId = r.ReadInt32();
127 | BeatmapSetId = r.ReadInt32();
128 | ThreadId = r.ReadInt32();
129 |
130 | GradeStandard = (Ranking)r.ReadByte();
131 | GradeTaiko = (Ranking)r.ReadByte();
132 | GradeCtB = (Ranking)r.ReadByte();
133 | GradeMania = (Ranking)r.ReadByte();
134 |
135 | OffsetLocal = r.ReadInt16();
136 | StackLeniency = r.ReadSingle();
137 | GameMode = (GameMode)r.ReadByte();
138 |
139 | SongSource = r.ReadString();
140 | SongTags = r.ReadString();
141 | OffsetOnline = r.ReadInt16();
142 | TitleFont = r.ReadString();
143 | Unplayed = r.ReadBoolean();
144 | LastPlayed = r.ReadDateTime();
145 |
146 | IsOsz2 = r.ReadBoolean();
147 | FolderName = r.ReadString();
148 | LastCheckAgainstOsuRepo = r.ReadDateTime();
149 |
150 | IgnoreBeatmapSounds = r.ReadBoolean();
151 | IgnoreBeatmapSkin = r.ReadBoolean();
152 | DisableStoryBoard = r.ReadBoolean();
153 | DisableVideo = r.ReadBoolean();
154 | VisualOverride = r.ReadBoolean();
155 | if (_version < OsuVersions.FloatDifficultyValues)
156 | OldUnknown1 = r.ReadInt16();
157 | LastEditTime = r.ReadInt32();
158 | ManiaScrollSpeed = r.ReadByte();
159 | }
160 |
161 | public void WriteToStream(SerializationWriter w)
162 | {
163 | w.Write(Artist);
164 | if (_version >= OsuVersions.FirstOsz2)
165 | w.Write(ArtistUnicode);
166 | w.Write(Title);
167 | if (_version >= OsuVersions.FirstOsz2)
168 | w.Write(TitleUnicode);
169 | w.Write(Creator);
170 | w.Write(Version);
171 | w.Write(AudioFileName);
172 | w.Write(BeatmapChecksum);
173 | w.Write(BeatmapFileName);
174 |
175 | w.Write((byte)RankedStatus);
176 | w.Write(CountHitCircles);
177 | w.Write(CountSliders);
178 | w.Write(CountSpinners);
179 | w.Write(LastModifiedTime);
180 |
181 | if (_version >= OsuVersions.FloatDifficultyValues)
182 | {
183 | w.Write(ApproachRate);
184 | w.Write(CircleSize);
185 | w.Write(HPDrainRate);
186 | w.Write(OveralDifficulty);
187 | }
188 | else
189 | {
190 | w.Write((byte)ApproachRate);
191 | w.Write((byte)CircleSize);
192 | w.Write((byte)HPDrainRate);
193 | w.Write((byte)OveralDifficulty);
194 | }
195 |
196 | w.Write(SliderVelocity);
197 |
198 | if (_version >= OsuVersions.ReducedOsuDbSize)
199 | {
200 | static Dictionary ConvertToWritableDictionary(IDictionary dic)
201 | => dic.ToDictionary(pair => (int) pair.Key, pair => (float)pair.Value);
202 |
203 | w.Write(ConvertToWritableDictionary(DiffStarRatingStandard));
204 | w.Write(ConvertToWritableDictionary(DiffStarRatingTaiko));
205 | w.Write(ConvertToWritableDictionary(DiffStarRatingCtB));
206 | w.Write(ConvertToWritableDictionary(DiffStarRatingMania));
207 | }
208 | else if (_version >= OsuVersions.FloatDifficultyValues)
209 | {
210 | static Dictionary ConvertToWritableDictionary(IDictionary dic)
211 | => dic.ToDictionary(pair => (int) pair.Key, pair => pair.Value);
212 |
213 | w.Write(ConvertToWritableDictionary(DiffStarRatingStandard));
214 | w.Write(ConvertToWritableDictionary(DiffStarRatingTaiko));
215 | w.Write(ConvertToWritableDictionary(DiffStarRatingCtB));
216 | w.Write(ConvertToWritableDictionary(DiffStarRatingMania));
217 |
218 | // TODO: there may be different reading behavior for versions before 20190204, 20200916, 20200504 and 20191024 here.
219 | }
220 |
221 | w.Write(DrainTimeSeconds);
222 | w.Write(TotalTime);
223 | w.Write(AudioPreviewTime);
224 |
225 | w.WriteSerializableList(TimingPoints);
226 | w.Write(BeatmapId);
227 | w.Write(BeatmapSetId);
228 | w.Write(ThreadId);
229 |
230 | w.Write((byte)GradeStandard);
231 | w.Write((byte)GradeTaiko);
232 | w.Write((byte)GradeCtB);
233 | w.Write((byte)GradeMania);
234 |
235 | w.Write(OffsetLocal);
236 | w.Write(StackLeniency);
237 | w.Write((byte)GameMode);
238 |
239 | w.Write(SongSource);
240 | w.Write(SongTags);
241 | w.Write(OffsetOnline);
242 | w.Write(TitleFont);
243 | w.Write(Unplayed);
244 | w.Write(LastPlayed);
245 |
246 | w.Write(IsOsz2);
247 | w.Write(FolderName);
248 | w.Write(LastCheckAgainstOsuRepo);
249 |
250 | w.Write(IgnoreBeatmapSounds);
251 | w.Write(IgnoreBeatmapSkin);
252 | w.Write(DisableStoryBoard);
253 | w.Write(DisableVideo);
254 | w.Write(VisualOverride);
255 | if (_version < OsuVersions.FloatDifficultyValues)
256 | w.Write(OldUnknown1);
257 | w.Write(LastEditTime);
258 | w.Write(ManiaScrollSpeed);
259 | }
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/UnitTestProject/TestsBinary.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Reflection;
4 | using FluentAssertions;
5 | using osu.Shared;
6 | using osu_database_reader.BinaryFiles;
7 | using osu_database_reader.Components.Player;
8 | using Xunit;
9 |
10 | namespace UnitTestProject
11 | {
12 | public class TestsBinary
13 | {
14 | [Fact]
15 | public void ReadOsuDb()
16 | {
17 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.osu_.20210316.db");
18 | var db = OsuDb.Read(stream);
19 |
20 | db.OsuVersion.Should().Be(20210316);
21 | db.FolderCount.Should().Be(23);
22 | db.AccountUnlocked.Should().BeTrue();
23 | db.AccountUnlockDate.Should().Be(new DateTime(0, DateTimeKind.Utc));
24 | db.AccountName.Should().Be("Ilex");
25 |
26 | db.Beatmaps.Should().HaveCount(93);
27 | var bm = db.Beatmaps[31];
28 | bm.Artist.Should().Be("KIVA");
29 | bm.Title.Should().Be("The Whole Rest");
30 | bm.Creator.Should().Be("Ilex");
31 | bm.Version.Should().Be("Insane v1");
32 | bm.AudioFileName.Should().Be("audio.mp3");
33 | bm.BeatmapChecksum.Should().Be("f281f4cb1a1cf13f4456443a7725bff2");
34 | bm.BeatmapFileName.Should().Be("KIVA - The Whole Rest (Ilex) [Insane v1].osu");
35 | bm.RankedStatus.Should().Be(SubmissionStatus.NotSubmitted);
36 | bm.CountHitCircles.Should().Be(252);
37 | bm.CountSliders.Should().Be(83);
38 | bm.CountSpinners.Should().Be(0);
39 | bm.LastModifiedTime.Should().BeAfter(new DateTime(2021, 4, 10, 17, 41, 10, DateTimeKind.Utc))
40 | .And.BeBefore(new DateTime(2021, 4, 10, 17, 41, 11, DateTimeKind.Utc));
41 |
42 | bm.ApproachRate.Should().Be(9.4f);
43 | bm.CircleSize.Should().Be(4);
44 | bm.HPDrainRate.Should().Be(6);
45 | bm.OveralDifficulty.Should().Be(7.3f);
46 | bm.SliderVelocity.Should().Be(1.8);
47 |
48 | bm.DiffStarRatingStandard.Should().HaveCount(9);
49 | bm.DiffStarRatingStandard[Mods.None].Should().BeApproximately(5.20573182737814, 0.00000000000001);
50 | bm.DiffStarRatingStandard[Mods.DoubleTime].Should().BeApproximately(7.24497549018472, 0.00000000000001);
51 | bm.DiffStarRatingStandard[Mods.HalfTime].Should().BeApproximately(4.15562856331195, 0.00000000000001);
52 | bm.DiffStarRatingTaiko.Should().BeEmpty();
53 | bm.DiffStarRatingCtB.Should().BeEmpty();
54 | bm.DiffStarRatingMania.Should().BeEmpty();
55 |
56 | bm.DrainTimeSeconds.Should().Be(96);
57 | bm.TotalTime.Should().Be(111563);
58 | bm.AudioPreviewTime.Should().Be(64366);
59 |
60 | bm.TimingPoints.Should().HaveCount(7);
61 | bm.TimingPoints[0].MsPerQuarter.Should().Be(382.165605095541);
62 | bm.TimingPoints[0].Time.Should().Be(1500);
63 | bm.TimingPoints[0].TimingChange.Should().BeTrue();
64 | bm.TimingPoints[6].MsPerQuarter.Should().Be(-57.1428571428571);
65 | bm.TimingPoints[6].Time.Should().Be(82710);
66 | bm.TimingPoints[6].TimingChange.Should().BeFalse();
67 |
68 | bm.BeatmapId.Should().Be(2026717);
69 | bm.BeatmapSetId.Should().Be(968597);
70 | bm.ThreadId.Should().Be(0);
71 |
72 | bm.GradeStandard.Should().Be(Ranking.C);
73 | bm.GradeTaiko.Should().Be(Ranking.N);
74 | bm.GradeCtB.Should().Be(Ranking.N);
75 | bm.GradeMania.Should().Be(Ranking.N);
76 |
77 | bm.OffsetLocal.Should().Be(0);
78 | bm.StackLeniency.Should().Be(0.7f);
79 | bm.GameMode.Should().Be(GameMode.Standard);
80 | bm.SongSource.Should().Be("Cytus II");
81 | bm.SongTags.Should().Be("Cytus II 2 OST");
82 | bm.OffsetOnline.Should().Be(0);
83 | bm.TitleFont.Should().BeNullOrEmpty();
84 | bm.Unplayed.Should().BeTrue(); // not sure how this works
85 | bm.LastPlayed.Should().BeAfter(new DateTime(2021, 4, 10, 18, 13, 9, DateTimeKind.Utc))
86 | .And.BeBefore(new DateTime(2021, 4, 10, 18, 13, 10, DateTimeKind.Utc));
87 |
88 | bm.IsOsz2.Should().BeFalse();
89 | bm.FolderName.Should().Be("968597 KIVA - The Whole Rest");
90 | bm.LastCheckAgainstOsuRepo.Should().BeAfter(new DateTime(2021, 4, 10, 19, 41, 10, DateTimeKind.Utc))
91 | .And.BeBefore(new DateTime(2021, 4, 10, 19, 41, 11, DateTimeKind.Utc));
92 |
93 | bm.IgnoreBeatmapSounds.Should().BeFalse();
94 | bm.IgnoreBeatmapSkin.Should().BeFalse();
95 | bm.DisableStoryBoard.Should().BeFalse();
96 | bm.DisableVideo.Should().BeFalse();
97 | bm.VisualOverride.Should().BeFalse();
98 |
99 | bm.LastEditTime.Should().Be(0);
100 | bm.ManiaScrollSpeed.Should().Be(0);
101 | }
102 |
103 | [Fact]
104 | public void ReadOsuDb_20250108()
105 | {
106 | // test new osu!.db format introduced in v20250108.3
107 |
108 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.osu_.20250108.db");
109 | var db = OsuDb.Read(stream);
110 |
111 | db.Beatmaps.Should().HaveCount(71);
112 |
113 | var starRatings = db.Beatmaps.SelectMany(b => b.DiffStarRatingStandard).Select(b => b.Value).ToList();
114 | starRatings.Should().NotBeEmpty().And.NotContain(sr => sr <= 0);
115 | }
116 |
117 | [Fact]
118 | public void ReadCollectionDbEmpty()
119 | {
120 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Collection.20200811_empty.db");
121 | var db = CollectionDb.Read(stream);
122 |
123 | db.OsuVersion.Should().Be(20200811);
124 | db.Collections.Should().BeEmpty();
125 | }
126 |
127 | [Fact]
128 | public void ReadCollectionDb()
129 | {
130 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Collection.20210316.db");
131 | var db = CollectionDb.Read(stream);
132 |
133 | db.OsuVersion.Should().Be(20210316);
134 | db.Collections.Should().HaveCount(2);
135 |
136 | db.Collections[0].Name.Should().Be("Hard maps");
137 | db.Collections[0].BeatmapHashes.Should().HaveCount(1);
138 | db.Collections[0].BeatmapHashes[0].Should().Be("06b536749d5a59536983854be90504ee");
139 |
140 | db.Collections[1].Name.Should().Be("My Collection");
141 | db.Collections[1].BeatmapHashes.Should().HaveCount(2);
142 | db.Collections[1].BeatmapHashes[0].Should().Be("f281f4cb1a1cf13f4456443a7725bff2");
143 | db.Collections[1].BeatmapHashes[1].Should().Be("b0670c14ed8f9ac489941890ce9b212e");
144 | }
145 |
146 | [Fact]
147 | public void ReadScoresDb()
148 | {
149 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Scores.20210316.db");
150 | var db = ScoresDb.Read(stream);
151 |
152 | db.OsuVersion.Should().Be(20210316);
153 | db.Beatmaps.Should().HaveCount(8);
154 |
155 | db.Beatmaps.ContainsKey("f281f4cb1a1cf13f4456443a7725bff2").Should().BeTrue();
156 | db.Beatmaps.ContainsKey("a0f3d86d32caaf0f3ed7474365ef830d").Should().BeTrue();
157 | db.Beatmaps["f281f4cb1a1cf13f4456443a7725bff2"].Should().HaveCount(1);
158 | db.Beatmaps["a0f3d86d32caaf0f3ed7474365ef830d"].Should().HaveCount(2);
159 |
160 | var replay = db.Beatmaps["f281f4cb1a1cf13f4456443a7725bff2"][0];
161 |
162 | // Equivalent to replay header
163 | replay.GameMode.Should().Be(GameMode.Standard);
164 | replay.OsuVersion.Should().Be(20210316);
165 | replay.BeatmapHash.Should().Be("f281f4cb1a1cf13f4456443a7725bff2");
166 | replay.PlayerName.Should().Be("Ilex");
167 | replay.ReplayHash.Should().Be("cc94fbdcd78ad26ff14bf906bf62336c");
168 |
169 | replay.Count300.Should().Be(246);
170 | replay.Count100.Should().Be(66);
171 | replay.Count50.Should().Be(1);
172 | replay.CountGeki.Should().Be(49);
173 | replay.CountKatu.Should().Be(28);
174 | replay.CountMiss.Should().Be(22);
175 | replay.Combo.Should().Be(119);
176 | replay.Score.Should().Be(322376);
177 | replay.FullCombo.Should().BeFalse();
178 | replay.Mods.Should().Be(Mods.NoFail);
179 | replay.TimePlayed.Should().BeAfter(new DateTime(2021, 4, 10, 18, 15, 05, DateTimeKind.Utc))
180 | .And.BeBefore(new DateTime(2021, 4, 10, 18, 15, 06, DateTimeKind.Utc));
181 | replay.ScoreId.Should().Be(0);
182 |
183 | replay.ReplayData.Should().BeNull();
184 | }
185 |
186 | [Fact]
187 | public void ReadPresenceDbEmpty()
188 | {
189 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Presence.20210316_empty.db");
190 | var db = PresenceDb.Read(stream);
191 |
192 | db.OsuVersion.Should().Be(20210316);
193 | db.Players.Should().BeEmpty();
194 | }
195 |
196 | [Fact]
197 | public void ReadPresenceDb()
198 | {
199 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Presence.20210316.db");
200 | var db = PresenceDb.Read(stream);
201 |
202 | db.OsuVersion.Should().Be(20210316);
203 | db.Players.Should().HaveCount(7);
204 |
205 | db.Players[1].PlayerId.Should().Be(-3);
206 | db.Players[1].PlayerName.Should().Be("BanchoBot");
207 |
208 | db.Players[2].PlayerId.Should().Be(-2070907);
209 | db.Players[2].PlayerName.Should().Be("Tillerino");
210 | db.Players[2].UtcOffset.Should().Be(24);
211 | db.Players[2].CountryByte.Should().Be(56);
212 | db.Players[2].PlayerRank.Should().Be(PlayerRank.Default);
213 | db.Players[2].GameMode.Should().Be(GameMode.Standard);
214 | db.Players[2].Longitude.Should().BeApproximately(11.5850f, 0.0001f);
215 | db.Players[2].Latitude.Should().BeApproximately(48.1497f, 0.0001f);
216 | db.Players[2].GlobalRank.Should().Be(0);
217 | db.Players[2].LastUpdate.Should().BeAfter(new DateTime(2021, 04, 10, 20, 17, 17, DateTimeKind.Utc))
218 | .And.BeBefore(new DateTime(2021, 04, 10, 20, 17, 18, DateTimeKind.Utc));
219 |
220 | db.Players[5].GlobalRank.Should().Be(3873896);
221 | }
222 |
223 | [Fact]
224 | public void ReadReplay()
225 | {
226 | using var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream("UnitTestProject.Data.Replays.20210316.osr");
227 | var replay = Replay.Read(stream);
228 |
229 | replay.GameMode.Should().Be(GameMode.Standard);
230 | replay.OsuVersion.Should().Be(20210316);
231 | replay.BeatmapHash.Should().Be("f281f4cb1a1cf13f4456443a7725bff2");
232 | replay.PlayerName.Should().Be("Ilex");
233 | replay.ReplayHash.Should().Be("cc94fbdcd78ad26ff14bf906bf62336c");
234 |
235 | replay.Count300.Should().Be(246);
236 | replay.Count100.Should().Be(66);
237 | replay.Count50.Should().Be(1);
238 | replay.CountGeki.Should().Be(49);
239 | replay.CountKatu.Should().Be(28);
240 | replay.CountMiss.Should().Be(22);
241 | replay.Combo.Should().Be(119);
242 | replay.Score.Should().Be(322376);
243 | replay.FullCombo.Should().BeFalse();
244 | replay.Mods.Should().Be(Mods.NoFail);
245 |
246 | replay.LifeGraphData.Should().HaveLength(430);
247 | replay.TimePlayed.Should().BeAfter(new DateTime(2021, 4, 10, 18, 15, 05, DateTimeKind.Utc))
248 | .And.BeBefore(new DateTime(2021, 4, 10, 18, 15, 06, DateTimeKind.Utc));
249 | replay.ReplayData.Length.Should().Be(35350);
250 | replay.ScoreId.Should().Be(0);
251 | }
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/osu-database-reader/7zip/Compress/LZMA/LzmaDecoder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace SevenZip.Compression.LZMA
4 | {
5 | using RangeCoder;
6 |
7 | public class Decoder : ICoder, ISetDecoderProperties // ,System.IO.Stream
8 | {
9 | class LenDecoder
10 | {
11 | BitDecoder m_Choice = new BitDecoder();
12 | BitDecoder m_Choice2 = new BitDecoder();
13 | BitTreeDecoder[] m_LowCoder = new BitTreeDecoder[Base.kNumPosStatesMax];
14 | BitTreeDecoder[] m_MidCoder = new BitTreeDecoder[Base.kNumPosStatesMax];
15 | BitTreeDecoder m_HighCoder = new BitTreeDecoder(Base.kNumHighLenBits);
16 | uint m_NumPosStates = 0;
17 |
18 | public void Create(uint numPosStates)
19 | {
20 | for (uint posState = m_NumPosStates; posState < numPosStates; posState++)
21 | {
22 | m_LowCoder[posState] = new BitTreeDecoder(Base.kNumLowLenBits);
23 | m_MidCoder[posState] = new BitTreeDecoder(Base.kNumMidLenBits);
24 | }
25 | m_NumPosStates = numPosStates;
26 | }
27 |
28 | public void Init()
29 | {
30 | m_Choice.Init();
31 | for (uint posState = 0; posState < m_NumPosStates; posState++)
32 | {
33 | m_LowCoder[posState].Init();
34 | m_MidCoder[posState].Init();
35 | }
36 | m_Choice2.Init();
37 | m_HighCoder.Init();
38 | }
39 |
40 | public uint Decode(RangeCoder.Decoder rangeDecoder, uint posState)
41 | {
42 | if (m_Choice.Decode(rangeDecoder) == 0)
43 | return m_LowCoder[posState].Decode(rangeDecoder);
44 | else
45 | {
46 | uint symbol = Base.kNumLowLenSymbols;
47 | if (m_Choice2.Decode(rangeDecoder) == 0)
48 | symbol += m_MidCoder[posState].Decode(rangeDecoder);
49 | else
50 | {
51 | symbol += Base.kNumMidLenSymbols;
52 | symbol += m_HighCoder.Decode(rangeDecoder);
53 | }
54 | return symbol;
55 | }
56 | }
57 | }
58 |
59 | class LiteralDecoder
60 | {
61 | struct Decoder2
62 | {
63 | BitDecoder[] m_Decoders;
64 | public void Create() { m_Decoders = new BitDecoder[0x300]; }
65 | public void Init() { for (int i = 0; i < 0x300; i++) m_Decoders[i].Init(); }
66 |
67 | public byte DecodeNormal(RangeCoder.Decoder rangeDecoder)
68 | {
69 | uint symbol = 1;
70 | do
71 | symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder);
72 | while (symbol < 0x100);
73 | return (byte)symbol;
74 | }
75 |
76 | public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, byte matchByte)
77 | {
78 | uint symbol = 1;
79 | do
80 | {
81 | uint matchBit = (uint)(matchByte >> 7) & 1;
82 | matchByte <<= 1;
83 | uint bit = m_Decoders[((1 + matchBit) << 8) + symbol].Decode(rangeDecoder);
84 | symbol = (symbol << 1) | bit;
85 | if (matchBit != bit)
86 | {
87 | while (symbol < 0x100)
88 | symbol = (symbol << 1) | m_Decoders[symbol].Decode(rangeDecoder);
89 | break;
90 | }
91 | }
92 | while (symbol < 0x100);
93 | return (byte)symbol;
94 | }
95 | }
96 |
97 | Decoder2[] m_Coders;
98 | int m_NumPrevBits;
99 | int m_NumPosBits;
100 | uint m_PosMask;
101 |
102 | public void Create(int numPosBits, int numPrevBits)
103 | {
104 | if (m_Coders != null && m_NumPrevBits == numPrevBits &&
105 | m_NumPosBits == numPosBits)
106 | return;
107 | m_NumPosBits = numPosBits;
108 | m_PosMask = ((uint)1 << numPosBits) - 1;
109 | m_NumPrevBits = numPrevBits;
110 | uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits);
111 | m_Coders = new Decoder2[numStates];
112 | for (uint i = 0; i < numStates; i++)
113 | m_Coders[i].Create();
114 | }
115 |
116 | public void Init()
117 | {
118 | uint numStates = (uint)1 << (m_NumPrevBits + m_NumPosBits);
119 | for (uint i = 0; i < numStates; i++)
120 | m_Coders[i].Init();
121 | }
122 |
123 | uint GetState(uint pos, byte prevByte) => ((pos & m_PosMask) << m_NumPrevBits) + (uint)(prevByte >> (8 - m_NumPrevBits));
124 |
125 | public byte DecodeNormal(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte) => m_Coders[GetState(pos, prevByte)].DecodeNormal(rangeDecoder);
126 |
127 | public byte DecodeWithMatchByte(RangeCoder.Decoder rangeDecoder, uint pos, byte prevByte, byte matchByte) => m_Coders[GetState(pos, prevByte)].DecodeWithMatchByte(rangeDecoder, matchByte);
128 | }
129 |
130 | LZ.OutWindow m_OutWindow = new LZ.OutWindow();
131 | RangeCoder.Decoder m_RangeDecoder = new RangeCoder.Decoder();
132 |
133 | BitDecoder[] m_IsMatchDecoders = new BitDecoder[Base.kNumStates << Base.kNumPosStatesBitsMax];
134 | BitDecoder[] m_IsRepDecoders = new BitDecoder[Base.kNumStates];
135 | BitDecoder[] m_IsRepG0Decoders = new BitDecoder[Base.kNumStates];
136 | BitDecoder[] m_IsRepG1Decoders = new BitDecoder[Base.kNumStates];
137 | BitDecoder[] m_IsRepG2Decoders = new BitDecoder[Base.kNumStates];
138 | BitDecoder[] m_IsRep0LongDecoders = new BitDecoder[Base.kNumStates << Base.kNumPosStatesBitsMax];
139 |
140 | BitTreeDecoder[] m_PosSlotDecoder = new BitTreeDecoder[Base.kNumLenToPosStates];
141 | BitDecoder[] m_PosDecoders = new BitDecoder[Base.kNumFullDistances - Base.kEndPosModelIndex];
142 |
143 | BitTreeDecoder m_PosAlignDecoder = new BitTreeDecoder(Base.kNumAlignBits);
144 |
145 | LenDecoder m_LenDecoder = new LenDecoder();
146 | LenDecoder m_RepLenDecoder = new LenDecoder();
147 |
148 | LiteralDecoder m_LiteralDecoder = new LiteralDecoder();
149 |
150 | uint m_DictionarySize;
151 | uint m_DictionarySizeCheck;
152 |
153 | uint m_PosStateMask;
154 |
155 | public Decoder()
156 | {
157 | m_DictionarySize = 0xFFFFFFFF;
158 | for (int i = 0; i < Base.kNumLenToPosStates; i++)
159 | m_PosSlotDecoder[i] = new BitTreeDecoder(Base.kNumPosSlotBits);
160 | }
161 |
162 | void SetDictionarySize(uint dictionarySize)
163 | {
164 | if (m_DictionarySize != dictionarySize)
165 | {
166 | m_DictionarySize = dictionarySize;
167 | m_DictionarySizeCheck = Math.Max(m_DictionarySize, 1);
168 | uint blockSize = Math.Max(m_DictionarySizeCheck, (1 << 12));
169 | m_OutWindow.Create(blockSize);
170 | }
171 | }
172 |
173 | void SetLiteralProperties(int lp, int lc)
174 | {
175 | if (lp > 8)
176 | throw new InvalidParamException();
177 | if (lc > 8)
178 | throw new InvalidParamException();
179 | m_LiteralDecoder.Create(lp, lc);
180 | }
181 |
182 | void SetPosBitsProperties(int pb)
183 | {
184 | if (pb > Base.kNumPosStatesBitsMax)
185 | throw new InvalidParamException();
186 | uint numPosStates = (uint)1 << pb;
187 | m_LenDecoder.Create(numPosStates);
188 | m_RepLenDecoder.Create(numPosStates);
189 | m_PosStateMask = numPosStates - 1;
190 | }
191 |
192 | bool _solid = false;
193 | void Init(System.IO.Stream inStream, System.IO.Stream outStream)
194 | {
195 | m_RangeDecoder.Init(inStream);
196 | m_OutWindow.Init(outStream, _solid);
197 |
198 | uint i;
199 | for (i = 0; i < Base.kNumStates; i++)
200 | {
201 | for (uint j = 0; j <= m_PosStateMask; j++)
202 | {
203 | uint index = (i << Base.kNumPosStatesBitsMax) + j;
204 | m_IsMatchDecoders[index].Init();
205 | m_IsRep0LongDecoders[index].Init();
206 | }
207 | m_IsRepDecoders[i].Init();
208 | m_IsRepG0Decoders[i].Init();
209 | m_IsRepG1Decoders[i].Init();
210 | m_IsRepG2Decoders[i].Init();
211 | }
212 |
213 | m_LiteralDecoder.Init();
214 | for (i = 0; i < Base.kNumLenToPosStates; i++)
215 | m_PosSlotDecoder[i].Init();
216 | // m_PosSpecDecoder.Init();
217 | for (i = 0; i < Base.kNumFullDistances - Base.kEndPosModelIndex; i++)
218 | m_PosDecoders[i].Init();
219 |
220 | m_LenDecoder.Init();
221 | m_RepLenDecoder.Init();
222 | m_PosAlignDecoder.Init();
223 | }
224 |
225 | public void Code(System.IO.Stream inStream, System.IO.Stream outStream,
226 | Int64 inSize, Int64 outSize, ICodeProgress progress)
227 | {
228 | Init(inStream, outStream);
229 |
230 | Base.State state = new Base.State();
231 | state.Init();
232 | uint rep0 = 0, rep1 = 0, rep2 = 0, rep3 = 0;
233 |
234 | UInt64 nowPos64 = 0;
235 | UInt64 outSize64 = (UInt64)outSize;
236 | if (nowPos64 < outSize64)
237 | {
238 | if (m_IsMatchDecoders[state.Index << Base.kNumPosStatesBitsMax].Decode(m_RangeDecoder) != 0)
239 | throw new DataErrorException();
240 | state.UpdateChar();
241 | byte b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, 0, 0);
242 | m_OutWindow.PutByte(b);
243 | nowPos64++;
244 | }
245 | while (nowPos64 < outSize64)
246 | {
247 | // UInt64 next = Math.Min(nowPos64 + (1 << 18), outSize64);
248 | // while(nowPos64 < next)
249 | {
250 | uint posState = (uint)nowPos64 & m_PosStateMask;
251 | if (m_IsMatchDecoders[(state.Index << Base.kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0)
252 | {
253 | byte b;
254 | byte prevByte = m_OutWindow.GetByte(0);
255 | if (!state.IsCharState())
256 | b = m_LiteralDecoder.DecodeWithMatchByte(m_RangeDecoder,
257 | (uint)nowPos64, prevByte, m_OutWindow.GetByte(rep0));
258 | else
259 | b = m_LiteralDecoder.DecodeNormal(m_RangeDecoder, (uint)nowPos64, prevByte);
260 | m_OutWindow.PutByte(b);
261 | state.UpdateChar();
262 | nowPos64++;
263 | }
264 | else
265 | {
266 | uint len;
267 | if (m_IsRepDecoders[state.Index].Decode(m_RangeDecoder) == 1)
268 | {
269 | if (m_IsRepG0Decoders[state.Index].Decode(m_RangeDecoder) == 0)
270 | {
271 | if (m_IsRep0LongDecoders[(state.Index << Base.kNumPosStatesBitsMax) + posState].Decode(m_RangeDecoder) == 0)
272 | {
273 | state.UpdateShortRep();
274 | m_OutWindow.PutByte(m_OutWindow.GetByte(rep0));
275 | nowPos64++;
276 | continue;
277 | }
278 | }
279 | else
280 | {
281 | UInt32 distance;
282 | if (m_IsRepG1Decoders[state.Index].Decode(m_RangeDecoder) == 0)
283 | {
284 | distance = rep1;
285 | }
286 | else
287 | {
288 | if (m_IsRepG2Decoders[state.Index].Decode(m_RangeDecoder) == 0)
289 | distance = rep2;
290 | else
291 | {
292 | distance = rep3;
293 | rep3 = rep2;
294 | }
295 | rep2 = rep1;
296 | }
297 | rep1 = rep0;
298 | rep0 = distance;
299 | }
300 | len = m_RepLenDecoder.Decode(m_RangeDecoder, posState) + Base.kMatchMinLen;
301 | state.UpdateRep();
302 | }
303 | else
304 | {
305 | rep3 = rep2;
306 | rep2 = rep1;
307 | rep1 = rep0;
308 | len = Base.kMatchMinLen + m_LenDecoder.Decode(m_RangeDecoder, posState);
309 | state.UpdateMatch();
310 | uint posSlot = m_PosSlotDecoder[Base.GetLenToPosState(len)].Decode(m_RangeDecoder);
311 | if (posSlot >= Base.kStartPosModelIndex)
312 | {
313 | int numDirectBits = (int)((posSlot >> 1) - 1);
314 | rep0 = ((2 | (posSlot & 1)) << numDirectBits);
315 | if (posSlot < Base.kEndPosModelIndex)
316 | rep0 += BitTreeDecoder.ReverseDecode(m_PosDecoders,
317 | rep0 - posSlot - 1, m_RangeDecoder, numDirectBits);
318 | else
319 | {
320 | rep0 += (m_RangeDecoder.DecodeDirectBits(
321 | numDirectBits - Base.kNumAlignBits) << Base.kNumAlignBits);
322 | rep0 += m_PosAlignDecoder.ReverseDecode(m_RangeDecoder);
323 | }
324 | }
325 | else
326 | rep0 = posSlot;
327 | }
328 | if (rep0 >= m_OutWindow.TrainSize + nowPos64 || rep0 >= m_DictionarySizeCheck)
329 | {
330 | if (rep0 == 0xFFFFFFFF)
331 | break;
332 | throw new DataErrorException();
333 | }
334 | m_OutWindow.CopyBlock(rep0, len);
335 | nowPos64 += len;
336 | }
337 | }
338 | }
339 | m_OutWindow.Flush();
340 | m_OutWindow.ReleaseStream();
341 | m_RangeDecoder.ReleaseStream();
342 | }
343 |
344 | public void SetDecoderProperties(byte[] properties)
345 | {
346 | if (properties.Length < 5)
347 | throw new InvalidParamException();
348 | int lc = properties[0] % 9;
349 | int remainder = properties[0] / 9;
350 | int lp = remainder % 5;
351 | int pb = remainder / 5;
352 | if (pb > Base.kNumPosStatesBitsMax)
353 | throw new InvalidParamException();
354 | UInt32 dictionarySize = 0;
355 | for (int i = 0; i < 4; i++)
356 | dictionarySize += ((UInt32)(properties[1 + i])) << (i * 8);
357 | SetDictionarySize(dictionarySize);
358 | SetLiteralProperties(lp, lc);
359 | SetPosBitsProperties(pb);
360 | }
361 |
362 | public bool Train(System.IO.Stream stream)
363 | {
364 | _solid = true;
365 | return m_OutWindow.Train(stream);
366 | }
367 |
368 | /*
369 | public override bool CanRead { get { return true; }}
370 | public override bool CanWrite { get { return true; }}
371 | public override bool CanSeek { get { return true; }}
372 | public override long Length { get { return 0; }}
373 | public override long Position
374 | {
375 | get { return 0; }
376 | set { }
377 | }
378 | public override void Flush() { }
379 | public override int Read(byte[] buffer, int offset, int count)
380 | {
381 | return 0;
382 | }
383 | public override void Write(byte[] buffer, int offset, int count)
384 | {
385 | }
386 | public override long Seek(long offset, System.IO.SeekOrigin origin)
387 | {
388 | return 0;
389 | }
390 | public override void SetLength(long value) {}
391 | */
392 | }
393 | }
394 |
--------------------------------------------------------------------------------
/UnitTestProject/Data/Beatmaps/beatmap_v14.osu:
--------------------------------------------------------------------------------
1 | osu file format v14
2 |
3 | [General]
4 | AudioFilename: audio.mp3
5 | AudioLeadIn: 0
6 | PreviewTime: 64366
7 | Countdown: 1
8 | SampleSet: None
9 | StackLeniency: 0.7
10 | Mode: 0
11 | LetterboxInBreaks: 0
12 | WidescreenStoryboard: 1
13 |
14 | [Editor]
15 | Bookmarks: 1500,7614,13729,22901,35130,47359,59589,64175,90162,93219,105449
16 | DistanceSpacing: 0.3
17 | BeatDivisor: 4
18 | GridSize: 8
19 | TimelineZoom: 1.950003
20 |
21 | [Metadata]
22 | Title:The Whole Rest
23 | TitleUnicode:The Whole Rest
24 | Artist:KIVA
25 | ArtistUnicode:KIVΛ
26 | Creator:Ilex
27 | Version:Insane v1
28 | Source:Cytus II
29 | Tags:Cytus II 2 OST
30 | BeatmapID:2026717
31 | BeatmapSetID:968597
32 |
33 | [Difficulty]
34 | HPDrainRate:6
35 | CircleSize:4
36 | OverallDifficulty:7.3
37 | ApproachRate:9.4
38 | SliderMultiplier:1.8
39 | SliderTickRate:1
40 |
41 | [Events]
42 | //Background and Video events
43 | 0,0,"usedtobe.jpg",0,0
44 | Video,0,"Cytus II Opening - The Whole Rest.mp4"
45 | //Break Periods
46 | 2,13929,22301
47 | 2,47559,52874
48 | //Storyboard Layer 0 (Background)
49 | //Storyboard Layer 1 (Fail)
50 | //Storyboard Layer 2 (Pass)
51 | //Storyboard Layer 3 (Foreground)
52 | //Storyboard Sound Samples
53 |
54 | [TimingPoints]
55 | 1500,382.165605095541,4,1,0,100,1,0
56 | 59589,-133.333333333333,4,1,0,100,0,0
57 | 64557,-57.1428571428571,4,1,0,100,0,0
58 | 80990,-57.1428571428571,4,1,0,100,0,0
59 | 81563,-50,4,1,0,100,0,0
60 | 82136,-44.4444444444445,4,1,0,100,0,0
61 | 82710,-57.1428571428571,4,1,0,100,0,0
62 |
63 |
64 | [HitObjects]
65 | 136,87,1500,5,0,0:0:0:0:
66 | 257,331,2073,1,0,0:0:0:0:
67 | 325,66,2646,1,0,0:0:0:0:
68 | 118,243,3219,1,0,0:0:0:0:
69 | 376,297,4557,5,0,0:0:0:0:
70 | 255,53,5130,1,0,0:0:0:0:
71 | 187,318,5703,1,0,0:0:0:0:
72 | 394,141,6276,1,0,0:0:0:0:
73 | 111,138,6850,1,0,0:0:0:0:
74 | 151,271,7232,1,0,0:0:0:0:
75 | 405,154,7614,5,0,0:0:0:0:
76 | 418,49,7805,1,0,0:0:0:0:
77 | 321,90,7996,1,0,0:0:0:0:
78 | 204,305,8187,5,0,0:0:0:0:
79 | 328,368,8378,1,0,0:0:0:0:
80 | 320,228,8569,1,0,0:0:0:0:
81 | 51,173,8761,5,0,0:0:0:0:
82 | 217,118,8952,1,0,0:0:0:0:
83 | 178,288,9143,1,0,0:0:0:0:
84 | 396,177,9334,5,0,0:0:0:0:
85 | 318,28,9525,1,0,0:0:0:0:
86 | 227,167,9716,1,0,0:0:0:0:
87 | 423,312,9907,5,0,0:0:0:0:
88 | 473,151,10098,1,0,0:0:0:0:
89 | 280,302,10289,5,0,0:3:0:0:
90 | 429,224,10480,1,0,0:3:0:0:
91 | 157,171,10671,5,0,0:0:0:0:
92 | 335,256,10862,1,0,0:0:0:0:
93 | 212,103,11053,1,0,0:0:0:0:
94 | 256,304,11245,5,0,0:0:0:0:
95 | 303,93,11435,1,0,0:0:0:0:
96 | 169,263,11626,1,0,0:0:0:0:
97 | 374,166,11817,5,0,0:3:0:0:
98 | 136,166,12008,1,0,0:3:0:0:
99 | 351,269,12199,1,0,0:3:0:0:
100 | 197,73,12390,5,0,0:3:0:0:
101 | 256,327,12581,1,0,0:3:0:0:
102 | 313,72,12773,1,0,0:3:0:0:
103 | 352,304,12964,5,0,0:0:0:0:
104 | 159,130,13155,1,0,0:0:0:0:
105 | 419,254,13347,5,0,0:0:0:0:
106 | 100,205,13538,1,0,0:0:0:0:
107 | 450,181,13729,5,0,0:0:0:0:
108 | 192,48,22901,5,0,0:0:0:0:
109 | 144,184,23092,1,0,0:0:0:0:
110 | 347,48,23283,2,0,B|472:48,1,90
111 | 299,184,23665,2,0,B|424:184,1,90
112 | 280,312,24047,5,0,0:0:0:0:
113 | 400,184,24238,1,0,0:0:0:0:
114 | 288,56,24429,1,0,0:0:0:0:
115 | 112,232,24621,2,0,B|191:152,1,90
116 | 24,336,25004,2,0,B|24:211,1,90
117 | 192,336,25385,5,0,0:0:0:0:
118 | 271,226,25576,2,0,L|327:307,2,90
119 | 136,216,26149,1,0,0:0:0:0:
120 | 152,40,26340,6,0,L|272:40,1,90
121 | 16,40,26722,2,0,L|88:96,1,90
122 | 312,288,27105,1,0,0:0:0:0:
123 | 408,192,27296,1,0,0:0:0:0:
124 | 368,80,27487,1,0,0:0:0:0:
125 | 192,56,27678,6,0,L|96:32,1,90
126 | 312,120,28060,2,0,L|216:144,1,90
127 | 224,248,28442,1,0,0:0:0:0:
128 | 376,272,28633,2,0,L|440:352,2,90
129 | 352,104,29207,1,0,0:0:0:0:
130 | 165,48,29398,6,0,B|40:48,1,90
131 | 213,184,29780,2,0,B|88:184,1,90
132 | 336,184,30162,1,0,0:0:0:0:
133 | 184,288,30353,1,0,0:0:0:0:
134 | 40,224,30544,1,0,0:0:0:0:
135 | 240,192,30735,6,0,B|352:144,1,90
136 | 104,224,31117,2,0,B|176:120,1,90
137 | 96,304,31499,1,0,0:0:0:0:
138 | 272,304,31691,2,0,L|400:304,2,90
139 | 144,304,32264,1,0,0:0:0:0:
140 | 240,216,32455,6,0,L|352:184,1,90
141 | 112,256,32837,2,0,L|112:160,1,90
142 | 32,360,33219,1,0,0:0:0:0:
143 | 200,360,33410,1,0,0:0:0:0:
144 | 120,232,33601,1,0,0:0:0:0:
145 | 32,104,33792,6,0,L|32:-16,1,90
146 | 40,240,34175,2,0,L|120:160,1,90
147 | 176,296,34557,1,0,0:0:0:0:
148 | 432,264,34748,2,0,L|336:208,1,90
149 | 128,96,35130,6,0,B|256:64|256:64|304:96,1,180
150 | 264,272,35703,1,0,0:0:0:0:
151 | 152,248,35894,2,0,L|48:224,1,90
152 | 224,176,36277,2,0,L|184:88,1,90
153 | 264,272,36659,6,0,B|392:304|392:304|440:272,1,180
154 | 405,66,37232,1,0,0:0:0:0:
155 | 336,156,37423,2,0,L|270:240,1,90
156 | 300,61,37806,2,0,L|204:60,1,90
157 | 448,64,38187,6,0,B|480:192|480:192|448:240,1,180
158 | 272,286,38760,1,0,0:0:0:0:
159 | 180,217,38951,2,0,L|96:151,1,90
160 | 276,182,39334,2,0,L|277:85,1,90
161 | 368,224,39716,2,0,L|452:158,1,90
162 | 272,286,40098,5,0,0:0:0:0:
163 | 368,224,40289,1,0,0:0:0:0:
164 | 248,208,40480,2,0,L|144:208,1,90
165 | 104,296,40863,1,0,0:0:0:0:
166 | 248,296,41054,1,0,0:0:0:0:
167 | 128,224,41245,6,0,B|96:96|96:96|128:48,1,180
168 | 304,88,41818,1,0,0:0:0:0:
169 | 280,200,42009,2,0,L|256:304,1,90
170 | 192,224,42392,2,0,L|104:264,1,90
171 | 272,320,42774,6,0,B|400:352|400:352|448:320,1,180
172 | 448,152,43347,1,0,0:0:0:0:
173 | 304,208,43538,1,0,0:0:0:0:
174 | 448,288,43729,1,0,0:0:0:0:
175 | 416,120,43920,1,0,0:0:0:0:
176 | 328,256,44111,1,0,0:0:0:0:
177 | 472,248,44302,6,0,B|472:112|472:112|440:72,3,180
178 | 256,80,45831,1,0,0:0:0:0:
179 | 376,224,46213,1,0,0:0:0:0:
180 | 480,32,46595,1,0,0:0:0:0:
181 | 208,232,46977,1,0,0:0:0:0:
182 | 336,192,47359,5,0,0:0:0:0:
183 | 224,208,53474,5,0,0:0:0:0:
184 | 288,176,53856,1,0,0:0:0:0:
185 | 195,188,54238,5,0,0:0:0:0:
186 | 316,195,54620,1,0,0:0:0:0:
187 | 169,135,55003,5,0,0:0:0:0:
188 | 342,248,55385,1,0,0:0:0:0:
189 | 177,34,55767,5,0,0:0:0:0:
190 | 334,349,56149,1,0,0:0:0:0:
191 | 261,104,56531,5,0,0:0:0:0:
192 | 250,279,56722,1,0,0:0:0:0:
193 | 334,109,56914,1,0,0:0:0:0:
194 | 177,274,57105,1,0,0:0:0:0:
195 | 403,175,57296,1,0,0:0:0:0:
196 | 108,208,57487,1,0,0:0:0:0:
197 | 416,298,57678,1,0,0:0:0:0:
198 | 95,85,57869,1,0,0:0:0:0:
199 | 283,284,58060,5,0,0:0:0:0:
200 | 328,272,58156,1,0,0:0:0:0:
201 | 368,240,58251,1,0,0:0:0:0:
202 | 376,192,58347,1,0,0:0:0:0:
203 | 344,152,58442,1,0,0:0:0:0:
204 | 296,152,58538,1,0,0:0:0:0:
205 | 256,176,58633,1,0,0:0:0:0:
206 | 224,216,58729,1,0,0:0:0:0:
207 | 184,240,58824,5,0,0:0:0:0:
208 | 136,240,58920,1,0,0:0:0:0:
209 | 88,216,59015,1,0,0:0:0:0:
210 | 64,176,59111,1,0,0:0:0:0:
211 | 72,128,59207,1,0,0:0:0:0:
212 | 112,104,59302,1,0,0:0:0:0:
213 | 160,96,59398,1,0,0:0:0:0:
214 | 208,112,59493,1,0,0:0:0:0:
215 | 237,140,59589,6,0,B|285:156|285:156|269:220|269:220|253:268|263:284|263:284|277:300|295:300|295:300|317:298|325:280|325:280|331:263,1,270.000010299683
216 | 350,188,60544,1,0,0:0:0:0:
217 | 337,26,60831,1,0,0:0:0:0:
218 | 269,76,61117,2,0,L|87:93,1,168.750006437302
219 | 71,140,61691,5,0,0:0:0:0:
220 | 43,216,61882,1,0,0:0:0:0:
221 | 120,238,62073,1,0,0:0:0:0:
222 | 151,163,62264,1,0,0:0:0:0:
223 | 225,131,62455,1,0,0:0:0:0:
224 | 295,170,62646,6,0,B|354:225|333:286,1,101.250003862381
225 | 310,287,63028,6,0,B|232:310|190:261,1,101.250003862381
226 | 193,244,63410,6,0,B|211:165|274:153,1,101.250003862381
227 | 287,165,63792,6,0,L|362:171,2,67.5000025749208
228 | 230,223,64366,5,0,0:0:0:0:
229 | 107,279,64557,1,0,0:0:0:0:
230 | 60,152,64748,5,0,0:0:0:0:
231 | 69,85,64844,1,0,0:0:0:0:
232 | 123,44,64939,1,0,0:0:0:0:
233 | 231,107,65130,5,0,0:0:0:0:
234 | 222,174,65226,1,0,0:0:0:0:
235 | 168,215,65321,1,0,0:0:0:0:
236 | 374,101,65512,6,0,L|423:285,1,157.500001502037
237 | 190,327,65894,1,0,0:0:0:0:
238 | 320,129,66085,1,0,0:0:0:0:
239 | 82,117,66277,1,0,0:0:0:0:
240 | 190,327,66468,6,0,L|86:118,1,157.500001502037
241 | 337,93,66850,1,0,0:0:0:0:
242 | 412,316,67041,2,0,L|256:370,1,157.500001502037
243 | 486,290,67423,1,0,0:0:0:0:
244 | 263,209,67614,6,0,L|439:271,1,157.500001502037
245 | 410,25,67996,2,0,L|240:88,1,157.500001502037
246 | 40,160,68378,1,0,0:0:0:0:
247 | 215,320,68570,6,0,L|413:253,1,157.500001502037
248 | 165,92,68952,2,0,L|124:297,1,157.500001502037
249 | 387,163,69334,2,0,L|230:25,1,157.500001502037
250 | 156,282,69716,2,0,L|361:323,1,157.500001502037
251 | 173,122,70098,5,0,0:0:0:0:
252 | 138,140,70193,1,0,0:0:0:0:
253 | 103,158,70289,1,0,0:0:0:0:
254 | 74,176,70384,5,0,0:0:0:0:
255 | 110,190,70480,1,0,0:0:0:0:
256 | 143,211,70575,1,0,0:0:0:0:
257 | 145,246,70671,1,0,0:0:0:0:
258 | 113,268,70766,1,0,0:0:0:0:
259 | 76,283,70862,1,0,0:0:0:0:
260 | 39,267,70958,1,0,0:0:0:0:
261 | 29,228,71054,5,0,0:0:0:0:
262 | 405,192,71245,1,0,0:0:0:0:
263 | 297,256,71436,1,0,0:0:0:0:
264 | 65,42,71627,5,0,0:0:0:0:
265 | 348,52,71818,6,0,L|240:196,1,157.500001502037
266 | 4,296,72200,1,0,0:0:0:0:
267 | 295,350,72391,2,0,L|111:350,1,157.500001502037
268 | 369,186,72773,1,0,0:0:0:0:
269 | 90,131,72964,2,0,L|248:57,2,157.500001502037
270 | 369,186,73538,5,0,0:0:0:0:
271 | 90,131,73729,1,0,0:0:0:0:
272 | 214,255,73920,1,0,0:0:0:0:
273 | 377,207,74111,5,0,0:0:0:0:
274 | 328,60,74302,1,0,0:0:0:0:
275 | 167,21,74493,1,0,0:0:0:0:
276 | 20,185,74684,6,0,L|195:183,1,157.500001502037
277 | 386,252,75066,2,0,L|228:253,1,157.500001502037
278 | 20,330,75449,2,0,L|177:328,1,157.500001502037
279 | 397,335,75831,6,0,B|437:265|437:265|395:177,1,157.500001502037
280 | 115,49,76213,2,0,B|75:119|75:119|117:207,1,157.500001502037
281 | 449,245,76595,2,0,B|449:164|449:164|368:109,1,157.500001502037
282 | 62,138,76977,2,0,B|62:219|62:219|143:274,1,157.500001502037
283 | 409,269,77359,5,0,0:0:0:0:
284 | 101,78,77550,1,0,0:0:0:0:
285 | 296,328,77742,1,0,0:0:0:0:
286 | 480,48,77933,1,0,0:0:0:0:
287 | 368,65,78124,6,0,P|317:138|358:195,1,157.500001502037
288 | 482,269,78506,1,0,0:0:0:0:
289 | 402,133,78697,1,0,0:0:0:0:
290 | 320,303,78888,1,0,0:0:0:0:
291 | 492,166,79079,1,0,0:0:0:0:
292 | 259,174,79270,1,0,0:0:0:0:
293 | 483,351,79461,6,0,B|449:273|449:273|366:217,1,157.500001502037
294 | 182,310,79843,1,0,0:0:0:0:
295 | 371,311,80035,1,0,0:0:0:0:
296 | 237,135,80226,1,0,0:0:0:0:
297 | 182,310,80417,1,0,0:0:0:0:
298 | 401,222,80608,1,0,0:0:0:0:
299 | 163,79,80990,6,0,B|60:241,1,157.500001502037
300 | 312,240,81372,1,0,0:0:0:0:
301 | 127,43,81563,6,0,B|13:232,1,180
302 | 312,240,81945,1,0,0:0:0:0:
303 | 104,18,82136,6,0,B|-7:202,1,202.500007724762
304 | 312,240,82519,1,0,0:0:0:0:
305 | 147,141,82710,5,0,0:0:0:0:
306 | 200,95,82805,1,0,0:0:0:0:
307 | 200,220,82996,5,0,0:0:0:0:
308 | 252,174,83091,1,0,0:0:0:0:
309 | 251,297,83283,5,0,0:0:0:0:
310 | 304,251,83378,1,0,0:0:0:0:
311 | 352,200,83474,1,0,0:0:0:0:
312 | 376,128,83570,1,0,0:0:0:0:
313 | 344,64,83665,1,0,0:0:0:0:
314 | 272,56,83761,1,0,0:0:0:0:
315 | 208,80,83856,2,0,L|72:168,1,157.500001502037
316 | 160,280,84238,5,0,0:0:0:0:
317 | 272,200,84429,2,0,L|400:96,1,157.500001502037
318 | 488,24,84812,2,0,L|488:200,1,157.500001502037
319 | 384,296,85194,1,0,0:0:0:0:
320 | 240,256,85385,6,0,L|384:184,1,157.500001502037
321 | 384,344,85767,1,0,0:0:0:0:
322 | 240,304,85958,2,0,L|384:232,1,157.500001502037
323 | 472,160,86340,2,0,B|456:72|456:72|384:32,1,157.500001502037
324 | 328,160,86722,1,0,0:0:0:0:
325 | 264,288,86914,6,0,B|204:254|204:254|188:166,1,157.500001502037
326 | 200,24,87296,2,0,B|259:57|259:57|275:145,1,157.500001502037
327 | 288,232,87678,2,0,B|304:320|304:320|363:353,1,157.500001502037
328 | 360,208,88060,2,0,B|344:120|344:120|284:86,1,157.500001502037
329 | 176,160,88633,5,0,0:0:0:0:
330 | 336,224,88824,1,0,0:0:0:0:
331 | 136,209,89015,5,0,0:0:0:0:
332 | 375,174,89206,1,0,0:0:0:0:
333 | 123,296,89398,5,0,0:0:0:0:
334 | 388,87,89589,1,0,0:0:0:0:
335 | 336,304,89780,5,0,0:0:0:0:
336 | 392,288,89875,1,0,0:0:0:0:
337 | 432,248,89971,1,0,0:0:0:0:
338 | 384,224,90066,1,0,0:0:0:0:
339 | 344,208,90162,6,0,L|248:168,5,78.7500007510185
340 | 288,128,90735,2,0,L|384:104,1,78.7500007510185
341 | 336,72,90926,2,0,L|248:32,3,78.7500007510185
342 | 240,80,91308,6,0,L|168:128,5,78.7500007510185
343 | 192,168,91882,2,0,L|288:208,1,78.7500007510185
344 | 296,240,92073,2,0,L|176:256,3,78.7500007510185
345 | 176,224,92455,6,0,L|233:162,3,78.7500007510185
346 | 232,120,92837,1,0,0:0:0:0:
347 | 184,96,92933,1,0,0:0:0:0:
348 | 128,104,93028,1,0,0:0:0:0:
349 | 88,136,93124,1,0,0:0:0:0:
350 | 64,184,93219,1,0,0:0:0:0:
351 | 241,298,93792,5,0,0:0:0:0:
352 | 448,102,93984,1,0,0:0:0:0:
353 | 134,110,94365,1,0,0:0:0:0:
354 | 205,285,94748,1,0,0:0:0:0:
355 | 87,180,95130,1,0,0:0:0:0:
356 | 449,74,95512,1,0,0:0:0:0:
357 | 192,272,96277,5,0,0:0:0:0:
358 | 361,267,96850,1,0,0:0:0:0:
359 | 469,57,97041,1,0,0:0:0:0:
360 | 288,272,97423,1,0,0:0:0:0:
361 | 454,183,97805,1,0,0:0:0:0:
362 | 397,258,98187,1,0,0:0:0:0:
363 | 152,176,98474,5,0,0:0:0:0:
364 | 218,78,98570,1,0,0:0:0:0:
365 | 93,278,98761,1,0,0:0:0:0:
366 | 304,172,98952,1,0,0:0:0:0:
367 | 69,199,99143,1,0,0:0:0:0:
368 | 220,242,99334,5,0,0:0:0:0:
369 | 410,174,100098,1,0,0:0:0:0:
370 | 252,278,100480,1,0,0:0:0:0:
371 | 160,300,100863,1,0,0:0:0:0:
372 | 401,75,101627,1,0,0:0:0:0:
373 | 146,276,102391,5,0,0:0:0:0:
374 | 338,85,103156,1,0,0:0:0:0:
375 | 380,274,103538,1,0,0:0:0:0:
376 | 290,185,103920,1,0,0:0:0:0:
377 | 308,255,104302,1,0,0:0:0:0:
378 | 71,250,104589,5,0,0:0:0:0:
379 | 124,119,104684,1,0,0:0:0:0:
380 | 207,288,104875,1,0,0:0:0:0:
381 | 363,51,105066,1,0,0:0:0:0:
382 | 155,163,105257,1,0,0:0:0:0:
383 | 295,236,105449,5,0,0:0:0:0:
384 | 374,64,106022,1,0,0:0:0:0:
385 | 235,93,106595,1,0,0:0:0:0:
386 | 357,237,107168,1,0,0:0:0:0:
387 | 352,80,107550,5,0,0:0:0:0:
388 | 320,96,107646,1,0,0:0:0:0:
389 | 296,128,107742,1,0,0:0:0:0:
390 | 312,208,107933,1,0,0:0:0:0:
391 | 368,264,108124,1,0,0:0:0:0:
392 | 448,280,108315,1,0,0:0:0:0:
393 | 288,288,108506,5,0,0:0:0:0:
394 | 440,176,109079,1,0,0:0:0:0:
395 | 304,133,109652,1,0,0:0:0:0:
396 | 173,270,110226,1,0,0:0:0:0:
397 | 307,226,110799,5,0,0:0:0:0:
398 | 219,138,111181,1,0,0:0:0:0:
399 | 347,50,111563,1,0,0:0:0:0:
400 |
--------------------------------------------------------------------------------
/UnitTestProject/Data/Beatmaps/4615284.osu:
--------------------------------------------------------------------------------
1 | osu file format v14
2 |
3 | [General]
4 | AudioFilename: audio.ogg
5 | AudioLeadIn: 0
6 | PreviewTime: 55688
7 | Countdown: 0
8 | SampleSet: Normal
9 | StackLeniency: 0.7
10 | Mode: 0
11 | LetterboxInBreaks: 0
12 | WidescreenStoryboard: 1
13 |
14 | [Editor]
15 | Bookmarks: 13380,122196,125337
16 | DistanceSpacing: 0.7
17 | BeatDivisor: 4
18 | GridSize: 16
19 | TimelineZoom: 2.2
20 |
21 | [Metadata]
22 | Title:Bad Apple!! feat. SEKAI
23 | TitleUnicode:Bad Apple!! feat. SEKAI
24 | Artist:25-ji, Nightcord de. x Hatsune Miku
25 | ArtistUnicode:25時、ナイトコードで。 × 初音ミク
26 | Creator:Dailycare
27 | Version:Luscent's Lunatic
28 | Source:プロジェクトセカイ カラフルステージ! feat. 初音ミク
29 | Tags:東方幻想郷 ~ Lotus Land Story Touhou Gensokyo Gensoukyou TH04 エリー Elly Stage 3 Alstroemeria Records ZUN 上海アリス幻樂団 Team Shanghai Alice マサヨシ ミノシマ Masayoshi Minoshima Haruka ビートまりお beatMARIO まろん MARON まらしぃ Marasy ARCD0018 Lovelight Project Sekai Colorful Stage! プロセカ prsk proseka Nightcord at 25:00 ニーゴ niigo 宵崎奏 Yoisaki Kanade 朝比奈まふゆ Asahina Mafuyu 東雲絵名 Shinonome Ena 暁山瑞希 Akiyama Mizuki 楠木ともり Kusunoki Tomori 田辺留依 Tanabe Rui 鈴木みのり Suzuki Minori 佐藤日向 Sato Satou Hinata J-Pop Jpop pop Japanese Video Game Vocaloid Morinaga Luscent
30 | BeatmapID:4615284
31 | BeatmapSetID:2182178
32 |
33 | [Difficulty]
34 | HPDrainRate:5
35 | CircleSize:5
36 | OverallDifficulty:7
37 | ApproachRate:8
38 | SliderMultiplier:1.70000000596046
39 | SliderTickRate:1
40 |
41 | [Events]
42 | //Background and Video events
43 | 0,0,"1921.jpg",0,0
44 | //Break Periods
45 | 2,83859,89648
46 | //Storyboard Layer 0 (Background)
47 | //Storyboard Layer 1 (Fail)
48 | //Storyboard Layer 2 (Pass)
49 | //Storyboard Layer 3 (Foreground)
50 | //Storyboard Layer 4 (Overlay)
51 | //Storyboard Sound Samples
52 |
53 | [TimingPoints]
54 | 181,434.782608695652,4,2,5,50,1,0
55 | 181,-133.333333333333,4,2,5,50,0,0
56 | 14094,-100,4,2,5,80,0,0
57 | 27137,-83.3333333333333,4,2,5,90,0,0
58 | 28007,-100,4,2,5,60,0,0
59 | 41920,-100,4,2,5,70,0,0
60 | 55833,-100,4,2,5,80,0,0
61 | 69746,-83.3333333333333,4,2,5,90,0,0
62 | 90615,-125,4,2,5,50,0,0
63 | 96702,-100,4,2,5,55,0,0
64 | 109746,-100,4,2,5,30,0,0
65 | 110615,-100,4,2,5,70,0,0
66 | 111485,-100,4,1,5,80,0,0
67 | 111702,-90.9090909090909,4,1,5,80,0,0
68 |
69 |
70 | [Colours]
71 | Combo1 : 221,170,204
72 | Combo2 : 187,102,136
73 | Combo3 : 136,137,205
74 | Combo4 : 204,170,136
75 |
76 | [HitObjects]
77 | 96,247,181,5,4,3:2:0:0:
78 | 256,192,615,1,0,3:0:0:0:
79 | 416,247,1050,1,0,3:0:0:0:
80 | 256,192,1485,1,0,3:0:0:0:
81 | 243,164,1594,1,0,3:0:0:0:
82 | 239,134,1702,1,0,3:0:0:0:
83 | 240,104,1811,1,0,3:0:0:0:
84 | 250,76,1920,1,0,3:0:0:0:
85 | 314,247,2354,1,0,3:0:0:0:
86 | 145,193,2789,1,0,3:0:0:0:
87 | 262,351,3224,5,0,3:0:0:0:
88 | 262,351,3441,1,0,3:0:0:0:
89 | 387,301,3659,6,0,B|419:237|390:178,2,127.500005310774,4|0|0,3:2|3:0|3:0,2:0:0:0:
90 | 235,249,4963,1,0,3:0:0:0:
91 | 204,259,5072,1,0,3:0:0:0:
92 | 172,264,5180,1,0,3:0:0:0:
93 | 140,260,5289,1,0,3:0:0:0:
94 | 111,246,5398,1,0,3:0:0:0:
95 | 0,307,5833,1,0,3:0:0:0:
96 | 100,175,6267,1,0,3:0:0:0:
97 | 0,66,6702,5,0,3:0:0:0:
98 | 0,66,6920,1,0,3:0:0:0:
99 | 158,11,7137,6,0,B|180:56|160:119,1,95.6250039830803,4|0,3:2|2:0,2:0:0:0:
100 | 164,104,7572,2,0,L|232:128,1,63.7500026553869,0|0,3:0|2:0,2:0:0:0:
101 | 67,221,8007,2,0,B|125:177|196:201,1,127.500005310774,0|0,3:0|3:0,2:0:0:0:
102 | 218,206,8550,1,0,3:0:0:0:
103 | 249,201,8659,1,0,3:0:0:0:
104 | 277,187,8767,1,0,3:0:0:0:
105 | 300,164,8876,1,0,3:0:0:0:
106 | 390,296,9311,1,0,3:0:0:0:
107 | 388,163,9746,2,0,B|339:104|376:44,1,127.500005310774,0|0,3:0|3:0,2:0:0:0:
108 | 388,163,10398,1,0,3:0:0:0:
109 | 264,217,10615,5,4,3:2:0:0:
110 | 256,192,10724,12,4,13659,2:2:0:0:
111 | 94,221,14094,6,0,B|140:249|188:225,1,85.000000298023,4|0,3:2|2:0,2:0:0:0:
112 | 175,230,14420,2,0,L|225:275,1,42.5000001490115,2|0,2:2|3:0,2:0:0:0:
113 | 247,200,14637,1,0,2:0:0:0:
114 | 254,179,14746,1,2,2:2:0:0:
115 | 370,150,14963,2,0,B|340:109|365:63,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
116 | 361,69,15289,2,0,L|418:102,1,42.5000001490115,2|0,2:2|3:0,2:0:0:0:
117 | 471,105,15507,1,0,2:0:0:0:
118 | 461,123,15615,1,2,2:2:0:0:
119 | 435,212,15833,6,0,B|455:257|438:300,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
120 | 425,310,16159,1,2,2:2:0:0:
121 | 410,325,16267,2,0,B|368:299|316:319,1,85.000000298023,0|2,3:0|2:2,2:0:0:0:
122 | 238,366,16702,1,2,3:2:0:0:
123 | 116,290,16920,1,0,2:0:0:0:
124 | 116,290,17028,1,2,2:2:0:0:
125 | 116,290,17137,2,0,B|98:243|102:199,1,85.000000298023,0|0,3:0|3:0,2:0:0:0:
126 | 62,135,17572,6,0,B|102:193|188:183,1,127.500000447035,0|2,3:0|2:2,2:0:0:0:
127 | 256,136,18115,1,0,3:0:0:0:
128 | 270,151,18224,1,2,2:2:0:0:
129 | 367,183,18441,2,0,B|327:241|241:231,1,127.500000447035,0|2,3:0|2:2,2:0:0:0:
130 | 173,184,18984,1,0,3:0:0:0:
131 | 159,199,19093,1,2,2:2:0:0:
132 | 35,138,19311,6,0,L|51:233,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
133 | 72,238,19637,2,0,B|36:279|52:329,1,85.000000298023,2|0,3:2|2:0,2:0:0:0:
134 | 22,336,19963,1,2,3:2:0:0:
135 | 166,288,20180,5,0,3:0:0:0:
136 | 254,256,20398,1,0,2:0:0:0:
137 | 254,256,20507,1,2,2:2:0:0:
138 | 254,256,20615,2,0,B|294:274|345:262,1,85.000000298023,0|2,3:0|3:2,2:0:0:0:
139 | 446,199,21050,6,0,L|490:210,2,42.5000001490115,0|2|0,3:0|2:2|2:0,2:0:0:0:
140 | 446,199,21376,2,0,B|417:161|434:109,1,85.000000298023,2|0,3:2|2:0,2:0:0:0:
141 | 404,95,21702,1,2,3:2:0:0:
142 | 263,146,21920,2,0,L|307:157,2,42.5000001490115,0|2|0,3:0|2:2|2:0,2:0:0:0:
143 | 263,146,22246,2,0,B|234:108|251:56,1,85.000000298023,2|0,3:2|2:0,2:0:0:0:
144 | 221,42,22572,1,2,3:2:0:0:
145 | 117,156,22789,5,0,3:0:0:0:
146 | 25,137,23007,1,2,2:2:0:0:
147 | 79,223,23224,1,0,3:0:0:0:
148 | 171,204,23442,1,2,2:2:0:0:
149 | 220,270,23659,2,0,B|178:295|128:269,1,85.000000298023,0|2,3:0|2:2,2:0:0:0:
150 | 11,341,24094,2,0,B|53:316|103:342,1,85.000000298023,0|2,3:0|3:3,2:0:0:0:
151 | 190,365,24528,5,2,3:2:0:0:
152 | 364,314,24854,1,2,3:2:0:0:
153 | 220,270,25180,1,2,3:2:0:0:
154 | 394,219,25507,1,2,3:2:0:0:
155 | 250,175,25833,1,2,3:2:0:0:
156 | 328,162,26050,1,2,3:2:0:0:
157 | 180,124,26267,5,2,3:2:0:0:
158 | 237.65527,29.688362,26485,2,0,L|289.65527:24.688362,1,42.5000001490115,0|2,2:0|2:2,2:0:0:0:
159 | 347.58636,57.757484,26702,2,0,L|399.58636:52.757484,3,42.5000001490115,0|2|0|2,3:0|2:2|2:0|2:2,2:0:0:0:
160 | 486,54,27137,6,4,L|467:136,1,67.9999981632232,4|4,2:3|2:3,2:3:0:0:
161 | 394.79285,132.03369,27427,6,4,L|375.79285:214.03369,1,67.9999981632232,4|4,2:3|2:3,2:3:0:0:
162 | 312,207,27717,6,4,L|293:289,1,67.9999981632232,4|4,2:3|2:3,2:3:0:0:
163 | 296,273,28007,6,0,B|238:316|167:283,1,127.500000447035,4|0,3:2|2:0,2:0:0:0:
164 | 175,286,28441,2,0,L|105:351,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
165 | 30,148,28876,2,0,B|98:184|78:264,1,127.500000447035,0|0,3:0|2:0,2:0:0:0:
166 | 80,254,29311,1,0,3:0:0:0:
167 | 204,206,29528,1,0,2:0:0:0:
168 | 80,254,29746,1,0,3:0:0:0:
169 | 323,151,30180,1,0,3:0:0:0:
170 | 479,236,30615,6,0,B|504:195|492:151,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
171 | 414,173,31050,1,0,3:0:0:0:
172 | 430,90,31267,1,0,3:0:0:0:
173 | 318,248,31485,5,0,3:0:0:0:
174 | 334,165,31702,1,0,2:0:0:0:
175 | 245,220,31920,2,0,B|230:270|265:308,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
176 | 245,220,32354,2,0,B|224:282|144:299|108:250,1,170.000000596046,0|0,3:0|3:0,2:0:0:0:
177 | 37,228,33007,1,0,2:0:0:0:
178 | 179,122,33224,5,0,3:0:0:0:
179 | 168,204,33441,1,0,2:0:0:0:
180 | 95,158,33659,1,0,3:0:0:0:
181 | 105,75,33876,1,0,2:0:0:0:
182 | 179,122,34094,5,0,3:0:0:0:
183 | 168,204,34311,1,0,2:0:0:0:
184 | 95,158,34529,1,0,3:0:0:0:
185 | 105,75,34746,1,0,3:0:0:0:
186 | 251,167,34963,6,0,B|295:182|341:164,2,85.000000298023,0|0|0,3:0|2:0|3:0,2:0:0:0:
187 | 392,68,35615,1,0,2:0:0:0:
188 | 392,68,35833,2,0,B|448:117|413:197,1,127.500000447035,0|0,3:0|2:0,2:0:0:0:
189 | 418,184,36267,2,0,L|507:224,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
190 | 289,279,36702,2,0,L|376:254,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
191 | 453,316,37137,1,0,3:0:0:0:
192 | 130,305,37572,5,0,3:0:0:0:
193 | 107,232,37789,1,0,2:0:0:0:
194 | 181,248,38007,1,0,3:0:0:0:
195 | 130,305,38224,1,0,3:0:0:0:
196 | 55,289,38441,5,0,3:0:0:0:
197 | 156,183,38659,1,0,2:0:0:0:
198 | 204,319,38876,2,0,B|243:346|291:332,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
199 | 359,361,39311,2,0,B|422:346|431:247|372:224,1,170.000000596046,0|0,3:0|3:0,2:0:0:0:
200 | 310,261,39963,1,0,2:0:0:0:
201 | 138,223,40180,6,0,B|188:240|225:216,1,85.000000298023,0|0,3:0|3:0,2:0:0:0:
202 | 419,108,40615,2,0,B|369:91|332:115,1,85.000000298023,0|0,3:0|3:0,2:0:0:0:
203 | 224,117,41050,6,4,B|206:85|223:47,2,56.6666668653487,4|4|4,3:3|3:3|3:3,3:3:0:0:
204 | 289,228,41485,6,4,B|307:196|290:158,2,56.6666668653487,4|4|4,3:3|3:3|3:3,3:3:0:0:
205 | 188,328,41920,6,0,B|231:309|281:321,1,85.000000298023,4|0,3:2|2:0,2:0:0:0:
206 | 297,333,42246,1,2,2:2:0:0:
207 | 322,348,42354,2,0,L|348:260,1,85.000000298023,0|2,3:0|2:2,2:0:0:0:
208 | 461,133,42789,2,0,B|402:185|441:251,1,127.500000447035,0|0,3:0|2:0,2:0:0:0:
209 | 438,246,43224,1,2,3:2:0:0:
210 | 237,196,43441,2,0,B|275:170|327:184,1,85.000000298023,0|2,2:0|3:2,2:0:0:0:
211 | 76,306,44094,5,2,3:2:0:0:
212 | 237,196,44528,2,0,L|217:110,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
213 | 241,19,44963,1,0,3:0:0:0:
214 | 217,113,45180,1,2,3:2:0:0:
215 | 122,169,45398,5,0,3:0:0:0:
216 | 237,196,45615,1,2,2:2:0:0:
217 | 262,271,45833,5,0,3:0:0:0:
218 | 366,209,46050,1,0,2:0:0:0:
219 | 486,225,46267,2,0,B|452:168|488:101,1,127.500000447035,2|0,3:2|2:0,2:0:0:0:
220 | 486,103,46702,2,0,B|449:138|401:127,1,85.000000298023,0|2,3:0|2:2,2:0:0:0:
221 | 317,156,47137,5,0,3:0:0:0:
222 | 317,156,47354,1,0,2:0:0:0:
223 | 170,41,47572,2,0,L|241:106,1,85.000000298023,2|0,3:2|2:0,2:0:0:0:
224 | 87,204,48007,2,0,L|152:133,1,85.000000298023,6|0,3:2|2:0,2:0:0:0:
225 | 253,281,48441,2,0,L|182:216,1,85.000000298023,6|0,3:2|3:0,2:0:0:0:
226 | 383,319,48876,5,6,3:2:0:0:
227 | 278,368,49094,1,2,2:2:0:0:
228 | 383,319,49311,2,0,B|361:270|375:225,1,85.000000298023,0|2,3:0|2:2,2:0:0:0:
229 | 414,169,49746,2,0,B|366:226|299:208,1,127.500000447035,0|0,3:0|2:0,2:0:0:0:
230 | 300,208,50180,1,2,3:2:0:0:
231 | 187,146,50398,1,0,2:0:0:0:
232 | 202,330,50615,2,0,B|231:291|216:245,1,85.000000298023,2|0,3:2|2:0,2:0:0:0:
233 | 116,336,51050,6,0,L|16:377,1,85.000000298023,2|0,3:2|2:0,2:0:0:0:
234 | 105,234,51485,1,2,3:2:0:0:
235 | 13,276,51702,1,0,2:0:0:0:
236 | 105,234,51920,1,0,3:0:0:0:
237 | 52,159,52137,1,2,3:2:0:0:
238 | 206,252,52354,6,0,B|183:211|206:165,1,85.000000298023,0|2,3:0|2:2,2:0:0:0:
239 | 309,171,52789,2,0,B|332:212|309:258,1,85.000000298023,0|2,3:0|2:2,2:0:0:0:
240 | 206,252,53224,2,0,B|125:187|176:102,1,170.000000596046,2|0,3:2|3:0,2:0:0:0:
241 | 309,171,53876,1,2,2:2:0:0:
242 | 245,321,54094,1,4,3:2:0:0:
243 | 46,273,54528,5,4,3:2:0:0:
244 | 302,283,54963,6,4,B|336:294|368:272,2,56.6666668653487,4|4|4,3:3|3:3|3:3,3:3:0:0:
245 | 348,202,55398,6,4,B|382:213|414:191,2,56.6666668653487,4|4|4,3:3|3:3|3:3,3:3:0:0:
246 | 205,294,55833,6,0,B|169:260|190:207,1,85.000000298023,4|2,2:2|2:2,2:0:0:0:
247 | 268,143,56267,1,2,1:2:0:0:
248 | 237,43,56485,1,0,2:0:0:0:
249 | 268,143,56702,1,2,3:2:0:0:
250 | 380,108,56920,1,0,2:0:0:0:
251 | 380,108,57028,1,0,2:0:0:0:
252 | 380,108,57137,2,0,B|362.3498:151.6618|382:199,1,85.2040023803711,0|2,1:0|2:2,2:0:0:0:
253 | 500,221,57572,5,0,2:0:0:0:
254 | 435,302,57789,1,2,2:2:0:0:
255 | 323,279,58007,1,2,1:2:0:0:
256 | 247,351,58224,1,0,2:0:0:0:
257 | 144,293,58441,2,0,B|90:249|113:176,1,127.500000447035,2|0,2:2|2:0,2:0:0:0:
258 | 112,178,58876,2,0,L|55:252,1,85.000000298023,0|2,1:0|1:2,2:0:0:0:
259 | 0,70,59311,2,0,L|20:163,1,85.000000298023,0|2,3:0|2:2,2:0:0:0:
260 | 98,102,59746,5,2,1:2:0:0:
261 | 193,133,59963,1,0,2:0:0:0:
262 | 282,171,60180,2,0,B|365:119|446:157,1,170.000000596046,2|2,3:2|1:2,2:0:0:0:
263 | 477,85,60833,1,2,2:2:0:0:
264 | 410,29,61050,5,0,3:0:0:0:
265 | 288,88,61267,1,2,2:2:0:0:
266 | 152,40,61485,1,0,1:0:0:0:
267 | 288,88,61702,2,0,B|312:133|302:180,1,85.000000298023,2|0,2:2|1:0,2:0:0:0:
268 | 245,227,62137,1,0,1:0:0:0:
269 | 354,336,62354,1,4,1:1:0:0:
270 | 354,336,62789,6,0,B|401:313|451:328,1,85.000000298023,4|2,1:2|2:2,2:0:0:0:
271 | 289,289,63224,1,2,1:2:0:0:
272 | 173,345,63441,1,0,2:0:0:0:
273 | 49,316,63659,2,0,B|-5:262|31:201,1,127.500000447035,2|0,3:2|2:0,2:0:0:0:
274 | 31,200,64094,2,0,L|-2:119,1,85.000000298023,0|2,1:0|2:2,2:0:0:0:
275 | 113,250,64528,5,0,2:0:0:0:
276 | 162,143,64746,1,2,2:2:0:0:
277 | 113,250,64963,2,0,L|142:343,1,85.000000298023,0|2,1:0|2:2,2:0:0:0:
278 | 246,276,65398,2,0,B|311:300|373:277,1,127.500000447035,2|0,3:2|2:0,2:0:0:0:
279 | 370,277,65833,1,0,1:0:0:0:
280 | 474,214,66050,1,2,1:2:0:0:
281 | 372,43,66267,5,0,3:0:0:0:
282 | 277,90,66485,1,2,2:2:0:0:
283 | 293,194,66702,1,0,1:0:0:0:
284 | 398,211,66920,1,2,2:2:0:0:
285 | 445,117,67137,1,0,3:0:0:0:
286 | 182,337,67572,1,0,1:0:0:0:
287 | 98,307,67789,1,2,2:2:0:0:
288 | 18,275,68007,5,0,3:0:0:0:
289 | 49,173,68225,1,2,2:2:0:0:
290 | 154,173,68442,1,2,1:2:0:0:
291 | 187,274,68660,1,0,2:0:0:0:
292 | 102,335,68876,2,4,B|85:301|94:263,2,56.6666668653487,4|4|4,1:3|1:3|1:3,2:3:0:0:
293 | 270,262,69311,2,4,B|276:241|259:207,2,56.6666668653487,4|4|4,1:3|1:3|1:3,2:3:0:0:
294 | 428,304,69746,6,0,B|494:248|458:167,1,152.999995867252,4|0,1:2|2:0,2:0:0:0:
295 | 369,102,70180,2,0,B|343:152|384:195,1,101.999997244835,4|0,1:2|2:0,2:0:0:0:
296 | 209,287,70615,2,0,B|186:203|97:201,1,152.999995867252,4|0,1:2|2:0,2:0:0:0:
297 | 32,229,71050,1,4,1:2:0:0:
298 | 243,155,71267,5,0,2:0:0:0:
299 | 243,155,71485,2,0,B|273:108|258:52,1,101.999997244835,4|0,1:2|2:0,2:0:0:0:
300 | 182,323,71920,2,0,B|208:249|308:248,1,152.999995867252,4|0,1:2|2:0,2:0:0:0:
301 | 353,188,72354,2,0,L|457:213,1,101.999997244835,4|0,1:2|2:0,2:0:0:0:
302 | 169,220,72789,1,4,1:2:0:0:
303 | 353,188,73007,1,0,1:0:0:0:
304 | 169,220,73224,2,0,L|65:195,1,101.999997244835,4|0,1:2|2:0,2:0:0:0:
305 | 114,50,73659,6,0,B|158:88|139:150,1,101.999997244835,4|0,1:2|2:0,2:0:0:0:
306 | 53,361,74094,2,0,B|-32:314|16:227,1,152.999995867252,4|0,1:2|2:0,2:0:0:0:
307 | 69,196,74528,1,4,1:2:0:0:
308 | 201,222,74746,1,0,2:0:0:0:
309 | 333,167,74963,5,4,1:2:0:0:
310 | 327,60,75180,1,0,2:0:0:0:
311 | 424,189,75398,5,4,1:2:0:0:
312 | 430,296,75615,1,0,2:0:0:0:
313 | 512,151,75833,5,4,1:2:0:0:
314 | 512,151,76267,1,4,1:2:0:0:
315 | 512,151,76485,1,0,1:0:0:0:
316 | 359,246,76702,6,0,B|297:199|207:227,1,152.999995867252,4|0,1:2|2:0,2:0:0:0:
317 | 85,284,77137,2,0,B|137:316|185:292,1,101.999997244835,4|0,1:2|2:0,2:0:0:0:
318 | 259,338,77572,1,4,1:2:0:0:
319 | 141,207,77789,1,0,2:0:0:0:
320 | 259,338,78007,2,0,L|286:228,1,101.999997244835,4|0,1:2|2:0,2:0:0:0:
321 | 344,46,78441,6,0,L|368:145,1,101.999997244835,4|0,1:2|2:0,2:0:0:0:
322 | 243,168,78876,1,4,1:2:0:0:
323 | 114,189,79094,1,0,2:0:0:0:
324 | 0,209,79311,2,0,B|87:225|107:318,1,152.999995867252,4|0,1:2|2:0,2:0:0:0:
325 | 60,363,79746,1,4,1:2:0:0:
326 | 273,238,79963,1,0,1:0:0:0:
327 | 264,142,80180,5,4,1:2:0:0:
328 | 186,198,80398,1,0,2:0:0:0:
329 | 195,294,80615,1,4,1:2:0:0:
330 | 282,334,80833,1,0,2:0:0:0:
331 | 363,279,81050,2,0,B|404:206|367:127,1,152.999995867252,4|0,1:2|2:0,2:0:0:0:
332 | 382,57,81485,5,0,1:0:0:0:
333 | 346,60,81594,1,0,1:0:0:0:
334 | 310,64,81702,1,0,1:0:0:0:
335 | 274,68,81811,1,0,1:0:0:0:
336 | 238,72,81920,1,4,1:2:0:0:
337 | 256,192,82028,12,4,83659,1:1:0:0:
338 | 247,189,90398,5,0,1:0:0:0:
339 | 54,123,90615,6,0,B|40:160|58:196,1,68.0000002384184,6|0,2:2|2:0,2:0:0:0:
340 | 156,281,91050,2,0,B|170:244|152:208,1,68.0000002384184,2|0,3:3|2:0,2:0:0:0:
341 | 309,111,91485,2,0,B|260:117|234:176|264:218,1,136.000000476837,2|0,2:2|2:0,2:0:0:0:
342 | 345,255,92137,1,2,2:2:0:0:
343 | 432,184,92354,6,0,B|400:170|362:179,1,68.0000002384184,0|0,2:0|2:0,2:0:0:0:
344 | 212,295,92789,2,0,B|244:309|282:300,1,68.0000002384184,2|0,3:3|2:0,2:0:0:0:
345 | 372,354,93224,1,2,2:2:0:0:
346 | 372,354,93659,5,0,2:0:0:0:
347 | 473,318,93876,1,2,2:2:0:0:
348 | 348,257,94094,1,0,2:0:0:0:
349 | 420,234,94311,1,0,2:0:0:0:
350 | 348,257,94528,1,2,3:3:0:0:
351 | 205,181,94746,1,0,2:0:0:0:
352 | 306,145,94963,1,2,2:2:0:0:
353 | 306,145,95398,5,0,2:0:0:0:
354 | 258,244,95615,1,2,2:2:0:0:
355 | 193,150,95833,1,0,2:0:0:0:
356 | 131,177,96050,1,0,2:0:0:0:
357 | 102,108,96267,1,2,3:3:0:0:
358 | 47,213,96485,1,0,2:0:0:0:
359 | 0,70,96702,2,4,B|-2:107|14:138,2,56.6666668653487,4|4|4,1:3|1:3|1:3,2:3:0:0:
360 | 168,60,97137,2,4,B|178:78|176:115,2,56.6666668653487,4|4|4,1:3|1:3|1:3,2:3:0:0:
361 | 311,73,97572,5,4,1:2:0:0:
362 | 332,207,98007,1,2,2:3:0:0:
363 | 353,341,98441,1,2,2:3:0:0:
364 | 332,207,98876,1,2,2:3:0:0:
365 | 266,247,99094,1,2,2:2:0:0:
366 | 400,162,99311,5,0,2:0:0:0:
367 | 265,143,99746,1,2,2:3:0:0:
368 | 130,124,100180,1,2,2:3:0:0:
369 | 265,143,100615,1,2,2:3:0:0:
370 | 323,93,100833,1,2,2:2:0:0:
371 | 338,169,101050,5,0,2:0:0:0:
372 | 280,219,101267,1,0,2:0:0:0:
373 | 353,245,101485,1,2,2:3:0:0:
374 | 368,321,101702,1,0,2:0:0:0:
375 | 295,295,101920,1,2,2:3:0:0:
376 | 22,306,102354,1,2,2:3:0:0:
377 | 95,332,102572,1,2,2:2:0:0:
378 | 80,256,102789,1,0,2:0:0:0:
379 | 153,282,103007,1,0,2:0:0:0:
380 | 168,358,103224,1,2,2:3:0:0:
381 | 226,308,103441,1,0,2:0:0:0:
382 | 241,384,103658,1,2,2:3:0:0:
383 | 364,303,104094,5,2,2:3:0:0:
384 | 364,303,104311,1,2,2:2:0:0:
385 | 364,303,104528,5,0,2:0:0:0:
386 | 414,257,104745,1,0,2:0:0:0:
387 | 432,192,104963,1,2,2:3:0:0:
388 | 414,127,105180,1,0,2:0:0:0:
389 | 364,81,105397,1,2,2:3:0:0:
390 | 156.86244,293,105833,5,2,2:3:0:0:
391 | 154.86244,297,106050.56521739127,1,2,2:2:0:0:
392 | 151.86244,301,106267.95652173909,5,0,2:0:0:0:
393 | 98,257,106484,1,0,2:0:0:0:
394 | 80,192,106702,1,2,2:3:0:0:
395 | 98,127,106919,1,0,2:0:0:0:
396 | 148,81,107136,1,2,2:3:0:0:
397 | 256,192,107572,5,2,2:3:0:0:
398 | 256,192,107789,1,2,2:2:0:0:
399 | 127,241,108007,6,0,B|167:264|213:247,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
400 | 385,143,108441,2,0,B|345:120|299:137,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
401 | 67,296,108876,6,0,B|97:329|150:315,1,85.000000298023,0|0,3:0|2:0,2:0:0:0:
402 | 445,88,109310,2,0,B|415:55|362:69,1,85.000000298023,0|0,3:0|3:0,2:0:0:0:
403 | 256,192,109746,5,4,1:2:0:0:
404 | 256,192,110615,5,4,1:2:0:0:
405 | 256,192,111050,5,4,1:2:0:0:
406 | 161,293,111485,5,4,1:1:0:0:
407 | 43,257,111702,2,0,B|14:212|31:160,1,93.500003181219,2|0,1:1|1:0,1:0:0:0:
408 | 71,113,112028,1,2,1:1:0:0:
409 | 193,176,112246,1,0,1:0:0:0:
410 | 193,176,112354,2,0,L|241:170,1,46.7500015906095,0|0,1:0|1:0,1:0:0:0:
411 | 302,205,112572,1,2,1:1:0:0:
412 | 380,135,112789,1,0,1:0:0:0:
413 | 391,104,112898,1,2,1:1:0:0:
414 | 491,149,113115,1,0,1:0:0:0:
415 | 508,177,113224,6,0,B|525:228|505:271,1,93.500003181219,0|2,1:0|1:1,1:0:0:0:
416 | 435,289,113550,2,0,L|324:269,1,93.500003181219,0|2,1:0|1:1,1:0:0:0:
417 | 275,306,113876,2,0,L|219:295,2,46.7500015906095,0|0|2,1:0|1:0|1:1,1:0:0:0:
418 | 247,193,114311,1,0,1:0:0:0:
419 | 174,118,114528,1,2,1:1:0:0:
420 | 159,88,114637,1,0,1:0:0:0:
421 | 67,147,114854,6,0,B|43:194|76:246,1,93.500003181219,2|0,1:1|1:0,1:0:0:0:
422 | 23,301,115180,2,0,B|90:312|105:359,2,93.500003181219,2|0|2,1:1|1:0|1:1,1:0:0:0:
423 | 191,292,115833,1,0,1:0:0:0:
424 | 217,272,115941,1,2,1:1:0:0:
425 | 243,252,116050,2,0,L|352:238,1,93.500003181219,0|2,1:0|1:1,1:0:0:0:
426 | 442,277,116485,1,0,1:0:0:0:
427 | 429,155,116702,5,0,1:0:0:0:
428 | 402,174,116811,2,2,B|355:182|298:137|312:84,1,140.250004771828,2|2,1:1|1:1,1:1:0:0:
429 | 208,15,117354,1,0,1:0:0:0:
430 | 184,37,117463,1,2,1:1:0:0:
431 | 160,59,117572,2,0,B|144:112|183:156,1,93.500003181219,0|2,1:0|1:1,1:0:0:0:
432 | 206.13815,166.00003,117898,1,0,1:0:0:0:
433 | 235.48238,185.34476,118007,2,0,B|270.48236:224.34476|249.48238:284.34476,1,93.500003181219,0|2,1:0|1:1,1:0:0:0:
434 | 142,251,118441,6,0,B|118:262|89:259,2,46.7500015906095,0|0|2,1:0|1:0|1:1,1:0:0:0:
435 | 19,232,118876,1,0,1:0:0:0:
436 | 34,312,119094,1,2,1:1:0:0:
437 | 102,169,119311,1,0,1:0:0:0:
438 | 132,340,119528,1,2,1:1:0:0:
439 | 206,198,119746,1,2,1:1:0:0:
440 | 221,278,119964,1,0,1:0:0:0:
441 | 340,290,120180,5,2,1:1:0:0:
442 | 367,302,120289,2,0,B|416:268|401:218,1,93.500003181219,0|0,1:0|1:0,1:0:0:0:
443 | 329,214,120615,2,2,B|281:190|287:117|335:104,1,140.250004771828,2|2,1:1|1:1,1:1:0:0:
444 | 406,119,121050,1,2,1:1:0:0:
445 | 329,214,121267,1,0,1:0:0:0:
446 | 246,304,121485,1,2,1:1:0:0:
447 | 329,214,121702,1,0,1:0:0:0:
448 | 233,203,121920,5,2,1:1:0:0:
449 | 208,215,122028,1,0,1:0:0:0:
450 | 180,220,122137,1,0,1:0:0:0:
451 | 152,212,122246,1,0,1:0:0:0:
452 | 123,224,122354,1,2,1:1:0:0:
453 | 98,212,122462,1,0,1:0:0:0:
454 | 70,207,122571,1,0,1:0:0:0:
455 | 42,215,122680,1,0,1:0:0:0:
456 | 35,188,122789,6,0,B|24:163|32:133,3,46.7500015906095,2|0|0|0,1:1|1:0|1:0|1:0,1:0:0:0:
457 | 87,86,123224,2,0,B|92:65|81:40,3,46.7500015906095,2|0|0|0,1:1|1:0|1:0|1:0,1:0:0:0:
458 | 158,129,123659,6,0,L|212:138,1,46.7500015906095,2|0,1:1|1:0,1:0:0:0:
459 | 274,97,123876,2,0,L|328:106,1,46.7500015906095,0|0,1:0|1:0,1:0:0:0:
460 | 178,150,124094,2,0,L|232:159,1,46.7500015906095,2|0,1:1|1:0,1:0:0:0:
461 | 294,118,124311,2,0,L|348:127,1,46.7500015906095,0|0,1:0|1:0,1:0:0:0:
462 | 414,76,124528,6,4,B|431:108|421:145,1,62.333335454146,4|4,1:3|1:3,1:3:0:0:
463 | 329,247,124818,2,4,B|352:224|392:227,1,62.333335454146,4|4,1:3|1:3,1:3:0:0:
464 | 489,218,125108,1,4,1:3:0:0:
465 | 489,218,125253,1,4,1:2:0:0:
466 |
--------------------------------------------------------------------------------