### Running osu-web Locally Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! This shell command demonstrates how to start the osu-web service using Docker Compose. It assumes you are in the osu-web directory. ```sh cd osu-web docker-composer up ``` -------------------------------- ### Running osu-server-spectator Locally Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! This shell command shows how to run the osu-server-spectator in release configuration, which is necessary for it to connect to the osu-web instance. It assumes you are in the osu-server-spectator directory. ```sh cd osu-server-spectator dotnet run -c Release ``` -------------------------------- ### Launch Settings for osu-server-spectator Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! This JSON configuration for osu-server-spectator's launchSettings.json enables local replay saving and specifies the directory for replay storage. This is crucial for the replay capturing flow. ```json { "profiles": { "osu-server-spectator": { "commandName": "Project", "launchBrowser": false, "launchUrl": "spectator", "applicationUrl": "http://localhost:5009", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "SAVE_REPLAYS": "1", "REPLAYS_PATH": "${OSU_WEB_DIRECTORY}/public/uploads-solo-replay" } } } } ``` -------------------------------- ### Configure Development Endpoints in osu! Client Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! Modifies the `DevelopmentEndpointConfiguration.cs` file to set local development URLs and client credentials for the osu! client. This allows the client to connect to local development servers. ```csharp public class DevelopmentEndpointConfiguration : EndpointConfiguration { public DevelopmentEndpointConfiguration() { WebsiteUrl = APIUrl = @"http://localhost:8080"; APIClientSecret = @""; APIClientID = ""; const string spectator_server_root_url = @"http://localhost:80"; SpectatorUrl = $@"{spectator_server_root_url}/signalr/spectator"; MultiplayerUrl = $@"{spectator_server_root_url}/signalr/multiplayer"; MetadataUrl = $@"{spectator_server_root_url}/signalr/metadata"; BeatmapSubmissionServiceUrl = $@"http://localhost:5089/beatmap-submission"; } } ``` -------------------------------- ### Copy osu-web OAuth Public Key Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! Copies the OAuth public key from the osu-web project to the osu-server-spectator project. This key is essential for the spectator server to validate tokens issued by osu-web. ```sh $cp osu-web/storage/oauth-public.key osu-server-spectator/osu.Server.Spectator/oauth-public.key ``` -------------------------------- ### Create osu! Example Game Templates Source: https://github.com/ppy/osu/blob/master/Templates/README.md Creates a new osu! game project with a working example. Use `-n` to specify the project name. Supports both freeform and scrolling game examples. ```bash # ..or start with a working sample freeform game dotnet new ruleset-example -n MyCoolWorkingRuleset # ..or a working sample scrolling game dotnet new ruleset-scrolling-example -n MyCoolWorkingRuleset ``` -------------------------------- ### Allow Insecure Requests in APIDownloadRequest Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! This C# code snippet shows how to enable insecure requests for APIDownloadRequest in the osu! client, allowing it to download replays over HTTP in a local development setup. ```csharp var request = new FileWebRequest(filename, Uri); request.DownloadProgress += request_Progress; request.AllowInsecureRequests = true; return request; ``` -------------------------------- ### Enable Insecure Requests for osu! Client Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! Applies a patch to `OsuJsonWebRequest.cs` and `OsuWebRequest.cs` in the osu! client to allow insecure HTTP requests. This is necessary when running development servers without HTTPS. ```csharp public class OsuJsonWebRequest : JsonWebRequest { public OsuJsonWebRequest(string uri) : base(uri) { AllowInsecureRequests = true; } public OsuJsonWebRequest() { AllowInsecureRequests = true; } protected override string UserAgent => "osu!"; } public class OsuWebRequest : WebRequest { public OsuWebRequest(string uri) : base(uri) { AllowInsecureRequests = true; } public OsuWebRequest() { AllowInsecureRequests = true; } protected override string UserAgent => "osu!"; } ``` -------------------------------- ### Replay Storage Configuration for osu-server-spectator Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! This C# code snippet modifies the ServiceCollectionExtensions in osu-server-spectator to use FileScoreStorage instead of S3ScoreStorage. This change is part of enabling local replay storage. ```csharp .AddSingleton>() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); ``` -------------------------------- ### Allow Insecure Requests in RegistrationRequest Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! This C# code demonstrates enabling insecure requests for the RegistrationRequest class, a common practice in local osu! development to bypass SSL certificate validation. ```csharp public class RegistrationRequest : OsuWebRequest { internal string Email = string.Empty; internal string Password = string.Empty; public RegistrationRequest() { AllowInsecureRequests = true; } protected override void PrePerform() { AddParameter("user[username]", Username); } } ``` -------------------------------- ### Install osu! Templates Source: https://github.com/ppy/osu/blob/master/Templates/README.md Installs or updates the ppy.osu.Game.Templates package using the .NET CLI. This command only needs to be run once. ```bash dotnet new install ppy.osu.Game.Templates ``` -------------------------------- ### Guid Creation Source: https://github.com/ppy/osu/blob/master/CodeAnalysis/BannedSymbols.txt Clarifies Guid instantiation. Suggests using Guid.NewGuid() for new GUIDs or Guid.Empty for an empty GUID. ```C# M:System.Guid.#ctor;Probably meaning to use Guid.NewGuid() instead. If actually wanting empty, use Guid.Empty. ``` -------------------------------- ### Configure JWT Bearer Options in osu-server-spectator Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! Updates the `ConfigureJwtBearerOptions.cs` file in the osu-server-spectator project to match the client ID used in the osu! client's development configuration. This ensures proper authentication between the client and the spectator server. ```csharp options.TokenValidationParameters = new TokenValidationParameters { IssuerSigningKey = new RsaSecurityKey(rsa), ValidAudience = "", // should match the client ID assigned to osu! in the osu-web target deploy. // TODO: figure out why this isn't included in the token. ValidateIssuer = false, ValidIssuer = "https://osu.ppy.sh/" }; ``` -------------------------------- ### Score Submission Configuration for osu-web Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! This configuration snippet for osu-web's .env file disables client version checks, which is required for local score submissions. It ensures that the osu-web instance accepts scores from any client build. ```sh CLIENT_CHECK_VERSION=false ``` -------------------------------- ### Configure Spectator Server Binding Port Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! Modifies the `Program.cs` file for `osu-server-spectator` to change the default binding port from 80 to 8081. This is useful if port 80 is already in use or restricted, preventing a 'Permission denied' error. ```csharp webBuilder.UseUrls(urls: new[] { "http://*:8081" }); ``` -------------------------------- ### Allow Insecure Requests in OsuWebRequest Source: https://github.com/ppy/osu/wiki/Testing-web-server-full-stack-with-osu! This snippet shows how to enable insecure requests within the OsuWebRequest class in C#. This is often necessary for local development environments where SSL certificates might not be properly configured. ```csharp public class OsuWebRequest : WebRequest { public OsuWebRequest(string uri) : base(uri) { AllowInsecureRequests = true; } public OsuWebRequest() { } } ``` -------------------------------- ### osu! Localisation Generation Flow Source: https://github.com/ppy/osu/wiki/Localising-the-client Visual representation of the localisation generation process, showing the movement of strings from client and web sources through analysis tools, Crowdin, and finally to the game resources. ```mermaid graph TD client_english[LocalisableString added to
ppy/osu/osu.Game/Localisation] -- Exported via ppy/osu-localisation-analyser
see: LocalisationAnalyser.Tools to-resx --> client_string_resx_en[New English string resource added to
ppy/osu-resources/osu.Game.Resources/Localisation] client_string_resx_en -- .resx files imported into Crowdin --> crowdin_en[New localisable string added to
https://crowdin.com/project/osu-web] crowdin_en --> crowdin_all[Non-English string localisations
contributed on Crowdin by users] crowdin_all -- client strings: .resx files exported from Crowdin

web strings: .php files exported from Crowdin
and converted via ppy/osu-localisation-analyser to .resx
see: LocalisationAnalyser.Tools from-php --> resources_resx_all[All string localisations imported into
ppy/osu-resources] resources_resx_all --> client_consumes[All string localisations consumed by ppy/osu] web_english[New English string resource added to
ppy/osu-web/resources/lang/en] -- .php files imported into Crowdin --> crowdin_en ``` -------------------------------- ### Dotnet Workload Restore Source: https://github.com/ppy/osu/blob/master/README.md Command to restore dotnet workloads, which is necessary for building mobile platforms (Android/iOS) if the required tooling hasn't been installed previously. ```shell sudo dotnet workload restore ``` -------------------------------- ### Integrating Conversion Mappings into osu!lazer Tests Source: https://github.com/ppy/osu/wiki/Generating-osu!stable-conversion-mappings Instructions on how to use the generated conversion mappings within osu!lazer's test projects. This involves copying the files and adding a `TestCase` attribute to the relevant C# test class. ```csharp Copy both files into the `./Resources/Testing/Beatmaps` situated in every ruleset project in osu!lazer. Open the `BeatmapConversionTest.cs` file in the relevant ruleset's test project, and add a new testcase attribute to the `Test(string name)` method, with the file name as the argument. For example: ```csharp [TestCase("1162021")] public new void Test(string name) { base.Test(name); } ``` ``` -------------------------------- ### Conversion Mappings File Naming and Structure Source: https://github.com/ppy/osu/wiki/Generating-osu!stable-conversion-mappings Describes the output format of conversion mappings, which are per-ruleset JSON files paired with beatmap files. It also provides guidelines for renaming these files for clarity in testing. ```text The mappings are output per-ruleset, as a json + beatmap pair. For example: ``` 1162021.osu 1162021-expected-conversion.json ``` Rename these files to something which describes the test case. Do not remove `-expected-conversion`. ``` -------------------------------- ### Generating Conversion Mappings in osu! Source: https://github.com/ppy/osu/wiki/Generating-osu!stable-conversion-mappings Steps to generate conversion mappings from osu!stable. This involves changing the release stream, creating a beatmap, and using the 'Generate conversion mappings' button in the options menu. ```text 1. Change to the `Cutting Edge` release stream. 2. Create a beatmap which demonstrates the test case scenario. 3. Select the beatmap in song select. 4. Open Options and click the "Generate conversion mappings" button at the bottom. Click the "Open osu! folder" button in Options, and you should see a new folder named "ConversionMappings". ``` -------------------------------- ### File Hashing and Storage Example Source: https://github.com/ppy/osu/wiki/User-file-storage Illustrates how osu! lazer stores files using SHA-256 hashes. A file with a specific SHA-256 hash is stored in a structured directory based on its hash. ```text SHA-256 Hash: 1a47929b6056d34d25a95eeb2012395ceed66af6f40cc37c898a08482d6325d2 Stored Path: ./files/1/1a/1a47929b6056d34d25a95eeb2012395ceed66af6f40cc37c898a08482d6325d2 ``` -------------------------------- ### Beatmap Processing Pipeline Source: https://github.com/ppy/osu/wiki/Beatmap-class-terminology To obtain a playable beatmap, first get a WorkingBeatmap and then call GetPlayableBeatmap(). This process involves BeatmapConverter for ruleset conversion and BeatmapProcessor for final gameplay adjustments. ```csharp // Obtain a WorkingBeatmap var workingBeatmap = /* ... get WorkingBeatmap ... */; // Convert and process the beatmap for gameplay var playableBeatmap = workingBeatmap.GetPlayableBeatmap(); // BeatmapConverter converts between rulesets. // BeatmapProcessor applies final touches for gameplay. ``` -------------------------------- ### Localising Hardcoded Strings with Analyser Source: https://github.com/ppy/osu/wiki/Localising-the-client Steps to localise hardcoded strings using the integrated localisation analyser in the osu! codebase. This process involves searching for the string, using the analyser tool (light bulb icon or Ctrl+.), and selecting an option to create a new localisable string. It also covers checking for existing or duplicated strings and renaming properties if necessary. ```en 1. Have the [prerequisites](https://github.com/ppy/osu?tab=readme-ov-file#prerequisites) for developing osu!. 2. Do a repo-wide search for the hardcoded string. There will be a dotted underline on the hardcoded string and a light bulb. 3. Click the light bulb (or Ctrl + .). 4. Pick an option to put the new localisable string in a new class or common where applicable. - Check for existing `Strings` classes that may fit and move accordingly. - Also check for strings that are duplicated and may be classified as common now (i.e. have multiple usages). - Localising a sentence may generate an unsuitable property name and key and will need to be renamed. ``` -------------------------------- ### IDE Project Loading Source: https://github.com/ppy/osu/blob/master/README.md Instructions for loading the osu! solution in an IDE. It recommends using platform-specific `.slnf` files to reduce dependencies and hide irrelevant platforms. The `osu.Desktop.slnf` is the most common. ```text - osu.Desktop.slnf (most common) - osu.Android.slnf - osu.iOS.slnf ``` -------------------------------- ### CLI Build and Run Source: https://github.com/ppy/osu/blob/master/README.md Command to build and run osu! from the command line. It suggests using `-c Release` for performance testing to avoid the overhead of the `Debug` configuration. ```shell dotnet run --project osu.Desktop dotnet restore ``` -------------------------------- ### Running Code Style Analysis Source: https://github.com/ppy/osu/blob/master/CONTRIBUTING.md Before opening a pull request, run the code style analysis using the provided scripts. This is crucial for first-time contributors as CI checks are not automatically enabled for their PRs. ```Shell InspectCode.{ps1,sh} ``` -------------------------------- ### Editor Visual Test Scene Source: https://github.com/ppy/osu/wiki/Implementing-a-ruleset-editor Creates a visual test scene for the editor, inheriting from EditorTestScene and initializing it with a specific ruleset. ```csharp [TestFixture] public class TestSceneEditor : EditorTestScene { public TestSceneEditor() : base(new TaikoRuleset()) { } } ``` -------------------------------- ### Git Workflow Recommendations Source: https://github.com/ppy/osu/blob/master/CONTRIBUTING.md Follow these recommendations when working with Git and submitting pull requests to ensure a smooth contribution process. This includes branching strategies and managing commits. ```Git # Submit pull requests from a topic branch (not master) # git checkout -b my-feature-branch # Allow edits from maintainers (keep this checked on GitHub) # Avoid pushing untested or incomplete code # Do not force-push or rebase unless asked # Do not merge master continually if no conflicts # Maintainers will handle merging master when ready ``` -------------------------------- ### Customizing HitObject Judgement Source: https://github.com/ppy/osu/wiki/Scoring This C# example demonstrates how to customize the `MaxResult` for a specific `HitObject` type by deriving `HitObject` and `Judgement`. It sets `MySliderTickJudgement.MaxResult` to `HitResult.SmallTickHit`, influencing the accepted judgement result types. ```csharp public class MySliderTick : HitObject { public override Judgement CreateJudgement() => new MySliderTickJudgement(); } public class MySliderTickJudgement : Judgement { public override HitResult MaxResult => HitResult.SmallTickHit; } ``` -------------------------------- ### Code Analysis and Formatting Source: https://github.com/ppy/osu/blob/master/README.md Commands and tools for code analysis and formatting. `dotnet format` is used for code formatting, and integrated compiler analyzers provide warnings. JetBrains ReSharper InspectCode is also utilized. ```shell dotnet format ./InspectCode.ps1 ``` -------------------------------- ### Create osu! Ruleset Templates Source: https://github.com/ppy/osu/blob/master/Templates/README.md Creates a new custom osu! ruleset project. Use `-n` to specify the ruleset name. Supports both freeform and scrolling rulesets. ```bash # create an empty freeform ruleset dotnet new ruleset -n MyCoolRuleset # create an empty scrolling ruleset (which provides the basics for a scrolling ←↑→↓ ruleset) dotnet new ruleset-scrolling -n MyCoolRuleset ``` -------------------------------- ### Modifying Realm Data Source: https://github.com/ppy/osu/wiki/Realm-usage-rules Provides examples for modifying Realm data. It covers updating a `Live<>` instance directly and modifying data within a Realm instance using the `Write` method. ```csharp // Modifying a Live<> instance Live keyBinding; keyBinding.Write(k => { k.Text = "new text"; }); // Modifying directly on a realm RealmAccess realm; realm.Write(r => { var keyBinding = r.Find(lookup).First(); keyBinding.Text = "new text"; }); ``` -------------------------------- ### Clone osu! Repository Source: https://github.com/ppy/osu/blob/master/README.md This snippet shows how to clone the osu! game repository from GitHub using Git. It also includes the command to navigate into the cloned directory. ```shell git clone https://github.com/ppy/osu cd osu ``` -------------------------------- ### TagLib File Creation Source: https://github.com/ppy/osu/blob/master/CodeAnalysis/BannedSymbols.txt Advises using TagLibUtils.GetTagLibFile() instead of TagLib.File.Create() methods due to potential culture-dependent MIME type detection in TagLib. ```C# M:TagLib.File.Create(System.String);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead. M:TagLib.File.Create(TagLib.File.IFileAbstraction);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead. M:TagLib.File.Create(System.String,TagLib.ReadStyle);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead. M:TagLib.File.Create(TagLib.File.IFileAbstraction,TagLib.ReadStyle);TagLib's MIME type detection changes behaviour depending on CultureInfo.CurrentCulture. Use TagLibUtils.GetTagLibFile() instead. ``` -------------------------------- ### Customizing Health Increase Source: https://github.com/ppy/osu/wiki/Scoring This C# code snippet shows how to create a custom `Judgement` class to modify the health increase for specific hit results. It overrides the `HealthIncreaseFor` method to change the default behavior, for example, making 'Good' hits not reduce HP. ```csharp public class MyJudgement : Judgement { protected override double HealthIncreaseFor(HitResult result) { switch (result) { case HitResult.Good: // Make Goods not reduce HP. return DEFAULT_MAX_HEALTH_INCREASE * 0.1; default: return base.HealthIncreaseFor(result); } } } ``` -------------------------------- ### Local Framework/Resource Testing Scripts Source: https://github.com/ppy/osu/blob/master/README.md Scripts to test changes in osu-resources or osu-framework by using local copies of these projects. These scripts assume the projects are checked out in adjacent directories. ```powershell UseLocalFramework.ps1 UseLocalResources.ps1 ``` ```shell UseLocalFramework.sh UseLocalResources.sh ``` -------------------------------- ### Realm Notifications Source: https://github.com/ppy/osu/blob/master/CodeAnalysis/BannedSymbols.txt Provides guidance on subscribing to Realm notifications, recommending osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications for IRealmCollection and IQueryable, and IList. ```C# M:Realms.IRealmCollection`1.SubscribeForNotifications`1(Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IRealmCollection,NotificationCallbackDelegate) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Linq.IQueryable{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IQueryable,NotificationCallbackDelegate) instead. M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Generic.IList{``0},Realms.NotificationCallbackDelegate{``0});Use osu.Game.Database.RealmObjectExtensions.QueryAsyncWithNotifications(IList,NotificationCallbackDelegate) instead. ``` -------------------------------- ### Add Context Menu Items for Selection Source: https://github.com/ppy/osu/wiki/Implementing-a-ruleset-editor Overrides the GetContextMenuItemsForSelection method to add custom context menu options based on the current selection. It demonstrates using TernaryStateMenuItem for multi-state options and includes a helper function to determine the initial state of these menu items. ```csharp protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) { // validity check — we can only show this menu item if all of the selection is the correct type. if (selection.All(s => s.HitObject is TaikoHitObject)) { // pre-cast for simplicity. var hits = selection.Select(s => s.HitObject).OfType(); yield return new TernaryStateMenuItem("Strong", action: state => { // applied state will always be true or false — this is after a user change. foreach (var h in hits) { switch (state) { case TernaryState.True: h.IsStrong = true; break; case TernaryState.False: h.IsStrong = false; break; } // Only required if you need to run ApplyDefaults on the HitObject. // If you are handling the change via bindables this is usually not required. EditorBeatmap?.UpdateHitObject(h); } }) { // set the initial state using a handle helper function below. State = { Value = getTernaryState(hits, h => h.IsStrong) } }; } } private TernaryState getTernaryState(IEnumerable selection, Func func) { if (selection.Any(func)) return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; return TernaryState.False; } ``` -------------------------------- ### Storing and Passing Realm Data with Live Source: https://github.com/ppy/osu/wiki/Realm-usage-rules Explains the use of `Live` for safely storing and passing Realm-managed objects across different threads. It shows how to convert a Realm object to a `Live` instance and perform read operations on any thread. ```csharp RealmAccess realm; var liveKeyBinding = realm.Run(r => r.Find(lookup).First().ToLive(r)); Task.Run(() => { // can now be used on any thread string text = liveKeyBinding.PerformRead(k => k.Text); }); ``` -------------------------------- ### LegacySkin Implementation for osu-stable Source: https://github.com/ppy/osu/wiki/Skinning-structure The LegacySkin class handles osu-stable skins, typically packaged as .osk files. Its logic is confined to parsing legacy configurations and providing elements if they exist locally within the skin. ```APIDOC class LegacySkin : ISkin # Implements osu-stable skin handling (e.g., .osk files). # Responsibilities: Parse legacy config, provide local elements. # Limitations: Logic limited to legacy parsing and local availability. # Purpose: Support older skin formats. ``` -------------------------------- ### Use osu.Game.Utils.IDeepCloneable Source: https://github.com/ppy/osu/blob/master/CodeAnalysis/BannedSymbols.txt Suggests using osu.Game.Utils.IDeepCloneable instead of SixLabors.ImageSharp.IDeepCloneable for deep cloning operations. ```C# T:SixLabors.ImageSharp.IDeepCloneable`1;Use osu.Game.Utils.IDeepCloneable instead. ``` -------------------------------- ### HitObjectComposer Implementation Source: https://github.com/ppy/osu/wiki/Implementing-a-ruleset-editor Implements the base HitObjectComposer for a specific ruleset (Taiko). It defines the composition tools and links to the ruleset. ```csharp public class TaikoHitObjectComposer : HitObjectComposer { public TaikoHitObjectComposer(TaikoRuleset ruleset) : base(ruleset) { } protected override IReadOnlyList CompositionTools => Array.Empty(); } ``` -------------------------------- ### Ruleset Class Update Source: https://github.com/ppy/osu/wiki/Implementing-a-ruleset-editor Updates the main Ruleset class to provide an instance of the custom HitObjectComposer. ```csharp public class TaikoRuleset : Ruleset, ILegacyRuleset { ... public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this); } ``` -------------------------------- ### Subscribing to Realm Collection Changes Source: https://github.com/ppy/osu/wiki/Realm-usage-rules Illustrates how to subscribe to notifications for changes in a Realm collection using `RegisterForNotifications`. It includes handling initial notifications, changes, and the lifecycle management via `Dispose`. ```csharp RealmAccess realm; // subscribe to changes (aka realm's `SubscribeForNotifications`) var subscription = realm.RegisterForNotifications(r => r.All(), keyBindingsChanged); // make sure to dispose when done. subscription.Dispose(); private void keyBindingsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { // all callbacks are fired on the update thread, but not local to the current drawable. // if this is important, using an additional `Schedule` is advised. if (changes == null) { // changes will be null once initially after subscription (realm behaviour). // it will also be null when the update thread realm is recycled (our behaviour). // - during a realm recycle operation, an empty result set will be returned via `sender`. // - after a recycle operation, the subscription will be re-established, causing the initial realm response to arrive a second time. // put simply, when a null `ChangeSet` arrives a component should clear whatever it's displaying // and update with the new contents of `sender`. } } ``` -------------------------------- ### collection.db File Structure Source: https://github.com/ppy/osu/wiki/Legacy-database-file-structure Details the format of the collection.db file, which stores user-created beatmap collections. Includes versioning and the structure for collection names and associated beatmap hashes. ```APIDOC collection.db File Structure: - Int: Version (e.g., 20150203). - Int: Number of collections. For each collection: - String: Name of the collection. - Int: Number of beatmaps in the collection. - String*: Beatmap MD5 hash (repeated for each beatmap). ```