cadence’s website.

Some changes will be applied after reloading.
Some changes will be applied after reloading.

Hacking osu!lazer for fun and fun

You can read this post on Gemini if you want to. The words are identical either way.

Introduction

The circles mode in osu! is a somewhat fun game. I want to play it casually from time to time, and enjoy myself, without taking it seriously. But the game has always fought back against me whenever I've tried to play it more. The life bar is strangely aggressive on some songs and the scoring system places a huge amount of emphasis on combo, to the point where accidentally missing a single note will make your score worthless, no matter what else happened in that attempt.

Something I love to do in rhythm games is improve upon my past scores, but the combo system makes this difficult and frustrating. "Improving" in the circles mode of osu! means "getting lucky and not missing for longer chains". Some people may find satisfaction in playing easier songs and striving for a full combo, but I find it much more fun to play more difficult and intense songs and try to get a certain % score on them.

The closed-source nature of the original game means that its scoring system is set in stone. Without providing me an avenue to change the numbers to be more friendly and fun for my way of playing, I would always give up and stop playing after only a few days. In the past I attempted to modify opsu!, which is an open-source Java clone of osu!, to calculate scores differently, but I got lost in the code and wasn't able to accomplish my goal.

osu!lazer is open-source and provides easy avenues to modify the gameplay and scoring system to match the way that I want to play. Here I describe what I changed, and how I did it, to document my work and provide help for anybody else who wants to do similar things.

As far as I know, local builds of the game cannot submit plays for online scoring or pp gains. If this is incorrect, and it's possible to submit plays on local builds, please let me know, because I'd be interested in doing that!

Local builds can still log in and use online features like in-game beatmap downloading.

The rest of this post will be a technically-oriented tutorial. Not all the sections may be relevant to you, so feel free to skip around.

My system

I'm using Fedora Silverblue 36 (Kinoite branch) which has the additional challenge that the operating system is immutable and installing system packages is discouraged. Silverblue provides several container systems to help install packages, and we'll be using those to build osu!. If you're not on Silverblue, you can just use your system package manager and ignore the things I wrote about containers.

.NET

osu!lazer is based on .NET 6.0. We need the .NET 6.0 SDK to build it.

.NET is designed so that there is an SDK, which is used to build the application, and a runtime, which is used to run the application. .NET applications don't compile to native code, so whenever anybody needs to run a .NET application, they need the runtime installed on their system. Since this is just for personal use, normally this wouldn't matter. You could install the SDK (which includes the runtime) and you'd be fine.

There's a catch. The container software we're going to use, called Toolbox, is supposed to support X and Wayland forwarding so you can run graphical applications inside the container and they will Just Work. osu! doesn't seem to recognise this, and won't start inside the container. This means you need to build it in the container and then run it on bare metal (i.e. outside of the container). The challenge is that in order to run a .NET application on bare metal you need the runtime installed on bare metal. We're going to work around this requirement for the runtime, but more on that later. For now let's just set up the container and build the lazer source code.

Microsoft is Microsoft

Oh, of course. .NET has telemetry in it. It can be disabled with an environment variable. If you want to disable the telemetry, you should set this enviroment variable now before you install .NET. You just need to change DOTNET_CLI_TELEMETRY_OPTOUT to 1.

In fish shell, you can set the environment variable like this:

set -x DOTNET_CLI_TELEMETRY_OPTOUT 1

You can add this line to ~/.config/fish/config.fish to make it automatically apply when you open a shell.

In bash, you can set it like this:

export DOTNET_CLI_TELEMETRY_OPTOUT=1

You can add this to your bash startup file to make it automatically apply when you open a shell.

Once it's in your startup file, if you want to make sure that nothing leaks, it would be safest to log out and back in again to make sure you don't have any old terminals hanging around which could be missing the new setting.

Setting up the container

The first step is to download the osu! source code. Git is available on the default installation, so we'll just clone the repo now.

cadence@ribbon ~/Software> git clone https://github.com/ppy/osu/
cadence@ribbon ~/Software> cd osu
cadence@ribbon ~/S/osu>

As mentioned, we're going to use the "toolbox" container system, and it's easiest to start from scratch with a brand new toolbox rather than going with your existing one. I'll use the name "lazer" for the container, so that name will appear in my prompt line. You can use whatever name you want.

cadence@ribbon ~/S/osu> toolbox create lazer
cadence@ribbon ~/S/osu> toolbox enter lazer
⬢ cadence@lazer ~/S/osu>

As mentioned, we need the .NET sdk, so install that. osu!lazer (at time of writing) specifically needs version 6.0, which is the latest version.

⬢ cadence@lazer ~/S/osu> sudo dnf install dotnet-sdk-6.0

Checkout a past release of lazer. We should use a known-good commit to make sure the build actually works in our environment, before we try editing the source code.

⬢ cadence@lazer ~/S/osu> git checkout 2022.405.0

(If you're reading this in the far future, you might want to try checking out a more modern release. I don't know.) If this is the case, you can see a list of available releases with:

⬢ cadence@lazer ~/S/osu> git tag

That's basically all the setup, and now we can make a build.

Building the code

⬢ cadence@lazer ~/S/osu> dotnet build osu.Desktop -c Release

This will take a while and download some packages. Eventually, you should see "0 Errors".

If you get a message like:

The .NET SDK has newer analyzers with version '6.0.0' than what version '5.0.3' of 'Microsoft.CodeAnalysis.NetAnalyzers' package provides. Update or remove this package reference.

If this appears, the nuget cache is for an old .NET version, and you should clear the cache. You can do this with:

⬢ cadence@lazer ~/S/osu> dotnet nuget locals all --clear

If you're not using Silverblue, then .NET is available system-wide. You can now grab headphones and run osu!lazer with:

cadence@ribbon ~/S/osu> dotnet run --project osu.Desktop -c Release

Otherwise, for Silverblue, you have to publish the project as a "self-contained" directory, which allows it to be run without the .NET runtime. This is what I was referring to at the start. This trick means that after it's built, we can just run it normally on bare metal.

⬢ cadence@lazer ~/S/osu> dotnet publish osu.Desktop -c Release -r linux-x64 --self-contained

This puts an executable file in <the osu code directory>/osu.Desktop/bin/Release/net6.0/linux-x64/osu! which can be executed on bare metal to run the game.

Making changes

You should git checkout master now so that you have the latest code. Cool! Go change stuff in the source code!

If you'd like to apply my scoring changes, here they are:

Set score portion balance to 0.9 accuracy 0.1 combo

osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)

This patch makes accuracy give 90% of the score and combo give 10% of the score. In the default game, this is balanced to 70% combo and 30% accuracy. You can change these numbers to whatever you want.

Set SpunOut mod to 1.0x score

osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)

This is an accessibility thing. I play with a laptop with a touchscreen, and spinning is really tough for me on that touchscreen. The SpunOut mod doesn't even spin very fast, and can even fail very short spinners, so for these reasons changing the mod score multiplier to 1.0x makes sense.

Set NoFail mod to 1.0x score

osu.Game/Rulesets/Mods/ModNoFail.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)

Why shouldn't I?

Building the game again with your changes

If you installed the .NET runtime system-wide:

cadence@ribbon ~/S/osu> dotnet run --project osu.Desktop -c Release

This will build your new changes and then start the game directly.

Self-contained for Silverblue:

⬢ cadence@lazer ~/S/osu> dotnet publish osu.Desktop -c Release -r linux-x64 --self-contained

This will build your changes and make a new executable. You can start the game on bare metal as before.

That's it!

That's all for today. Go forth and edit your favourite games to be more fun for your brain!! ^▼^

Cadence

A seal on a cushion spinning a globe on its nose.
Another seal. They are friends!