├── 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 | [![CodeFactor](https://www.codefactor.io/repository/github/holly-hacker/osu-database-reader/badge)](https://www.codefactor.io/repository/github/holly-hacker/osu-database-reader) 2 | [![NuGet](https://img.shields.io/nuget/v/HoLLy.osu.DatabaseReader.svg?style=flat-square)](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 | --------------------------------------------------------------------------------