17 Angajamente 981797ba00 ... 4ad10055de

Autor SHA1 Permisiunea de a trimite mesaje. Dacă este dezactivată, utilizatorul nu va putea trimite nici un fel de mesaj Data
  Clément Wolf 4ad10055de Remove switch support 3 săptămâni în urmă
  Clément Wolf 6e685e80eb Update protocol version 3 săptămâni în urmă
  Clément Wolf c668ce1611 Add loudness normalization 3 săptămâni în urmă
  Clément Wolf 903ba352b6 Allow singleplayer cheats when the server doesn't accept scores 3 săptămâni în urmă
  Clément Wolf 6458ee42ca Fix ALT key on linux 3 săptămâni în urmă
  Clément Wolf 0b9a754ab5 Handle restarting SoundEngine during play mode 3 săptămâni în urmă
  Clément Wolf 71b79637e6 Add instant_replay_duration convar 3 săptămâni în urmă
  Clément Wolf a677ace2d8 Fix replay playback starting too fast 3 săptămâni în urmă
  Clément Wolf 194583a77e Set experimental mods while watching replay 3 săptămâni în urmă
  Clément Wolf 5ac4ceeec6 Fix FPoSu camera not following cursor while watching replay 3 săptămâni în urmă
  Clément Wolf 1afbca198c Fix pause button not working after cancelling database load 3 săptămâni în urmă
  Clément Wolf f7067f7a57 Delay chat layout updates while chat is hidden 3 săptămâni în urmă
  Clément Wolf 387286acf8 Fix level bar always being at 0% 3 săptămâni în urmă
  Clément Wolf 284b9f8cbf Display server icon on main menu button 3 săptămâni în urmă
  Clément Wolf 88fda944dd Change instant replay default key to F2 3 săptămâni în urmă
  Clément Wolf ca6ae35905 Disable score submission when user changes mods mid-game 3 săptămâni în urmă
  Clément Wolf 0bf89004dd Rename 'McOsu Multiplayer' to 'neosu' 3 săptămâni în urmă
73 a modificat fișierele cu 894 adăugiri și 2034 ștergeri
  1. 3 3
      Makefile
  2. 2 1
      README.md
  3. 3 3
      build.bat
  4. 0 0
      libraries/bassloud/2.4.txt
  5. BIN
      libraries/bassloud/bin/bassloud.dll
  6. 48 0
      libraries/bassloud/include/bassloud.h
  7. BIN
      libraries/bassloud/lib/windows/bassloud.lib
  8. 1 1
      resources/materials/default/skin.ini
  9. 5 2
      src/App/Osu/Bancho.cpp
  10. 5 1
      src/App/Osu/Bancho.h
  11. 9 3
      src/App/Osu/BanchoNetworking.cpp
  12. 6 6
      src/App/Osu/BanchoNetworking.h
  13. 4 0
      src/App/Osu/BanchoSubmitter.cpp
  14. 1 0
      src/App/Osu/Downloader.cpp
  15. 181 155
      src/App/Osu/Osu.cpp
  16. 4 3
      src/App/Osu/Osu.h
  17. 123 18
      src/App/Osu/OsuBeatmap.cpp
  18. 1 1
      src/App/Osu/OsuBeatmap.h
  19. 24 2
      src/App/Osu/OsuChangelog.cpp
  20. 11 0
      src/App/Osu/OsuChat.cpp
  21. 1 0
      src/App/Osu/OsuChat.h
  22. 11 12
      src/App/Osu/OsuDatabase.cpp
  23. 50 1
      src/App/Osu/OsuDatabaseBeatmap.cpp
  24. 4 2
      src/App/Osu/OsuDatabaseBeatmap.h
  25. 5 5
      src/App/Osu/OsuDifficultyCalculator.cpp
  26. 5 7
      src/App/Osu/OsuHUD.cpp
  27. 1 1
      src/App/Osu/OsuKeyBindings.cpp
  28. 120 341
      src/App/Osu/OsuMainMenu.cpp
  29. 5 9
      src/App/Osu/OsuMainMenu.h
  30. 7 9
      src/App/Osu/OsuModFPoSu.cpp
  31. 27 48
      src/App/Osu/OsuModSelector.cpp
  32. 91 124
      src/App/Osu/OsuOptionsMenu.cpp
  33. 1 0
      src/App/Osu/OsuOptionsMenu.h
  34. 3 14
      src/App/Osu/OsuPauseMenu.cpp
  35. 1 1
      src/App/Osu/OsuRankingScreen.cpp
  36. 2 2
      src/App/Osu/OsuReplay.h
  37. 5 5
      src/App/Osu/OsuRichPresence.cpp
  38. 7 11
      src/App/Osu/OsuRoom.cpp
  39. 4 8
      src/App/Osu/OsuScore.cpp
  40. 6 12
      src/App/Osu/OsuSkin.cpp
  41. 2 2
      src/App/Osu/OsuSkinImage.cpp
  42. 1 1
      src/App/Osu/OsuSlider.cpp
  43. 19 33
      src/App/Osu/OsuSongBrowser.cpp
  44. 6 3
      src/App/Osu/OsuUIAvatar.cpp
  45. 1 0
      src/App/Osu/OsuUIAvatar.h
  46. 1 4
      src/App/Osu/OsuUISongBrowserCollectionButton.cpp
  47. 1 122
      src/App/Osu/OsuUISongBrowserScoreButton.cpp
  48. 0 1
      src/App/Osu/OsuUISongBrowserScoreButton.h
  49. 1 3
      src/App/Osu/OsuUISongBrowserSongButton.cpp
  50. 2 2
      src/App/Osu/OsuUISongBrowserUserButton.cpp
  51. 2 7
      src/App/Osu/OsuUIUserContextMenu.cpp
  52. 4 2
      src/App/Osu/OsuUpdateHandler.cpp
  53. 4 8
      src/App/Osu/OsuUserStatsScreen.cpp
  54. 4 5
      src/App/Osu/OsuVolumeOverlay.cpp
  55. 1 1
      src/Engine/Environment.h
  56. 1 2
      src/Engine/Font.cpp
  57. 2 0
      src/Engine/Input/Keyboard.cpp
  58. 0 1
      src/Engine/Input/Mouse.cpp
  59. 0 89
      src/Engine/Main/main_Horizon.cpp
  60. 16 97
      src/Engine/Main/main_SDL.cpp
  61. 0 561
      src/Engine/Platform/HorizonSDLEnvironment.cpp
  62. 0 75
      src/Engine/Platform/HorizonSDLEnvironment.h
  63. 0 34
      src/Engine/Platform/HorizonThread.cpp
  64. 0 32
      src/Engine/Platform/HorizonThread.h
  65. 0 41
      src/Engine/Platform/HorizonTimer.cpp
  66. 0 38
      src/Engine/Platform/HorizonTimer.h
  67. 0 7
      src/Engine/Platform/OpenGLHeaders.h
  68. 0 6
      src/Engine/Platform/Thread.cpp
  69. 0 8
      src/Engine/Platform/Timer.cpp
  70. 0 17
      src/Engine/ResourceManager.cpp
  71. 1 1
      src/Engine/Sound.cpp
  72. 1 0
      src/Engine/Sound.h
  73. 37 20
      src/Engine/SoundEngine.cpp

+ 3 - 3
Makefile

@@ -1,4 +1,4 @@
-TARGET = build/McOsu
+TARGET = build/neosu
 SOURCES = $(shell find src -type f -name '*.cpp')
 OBJECTS = $(patsubst src/%.cpp, obj/%.o, $(SOURCES))
 HEADERS = $(shell find src -type f -name '*.h')
@@ -8,10 +8,10 @@ LIBS = blkid freetype2 glew libenet libjpeg liblzma OpenCL xi zlib
 CXXFLAGS = -std=c++17 -fmessage-length=0 -Wno-sign-compare -Wno-unused-local-typedefs -Wno-reorder -Wno-switch -Wall
 CXXFLAGS += `pkgconf --static --cflags $(LIBS)` `curl-config --cflags`
 CXXFLAGS += -Isrc/App -Isrc/App/Osu -Isrc/Engine -Isrc/GUI -Isrc/GUI/Windows -Isrc/GUI/Windows/VinylScratcher -Isrc/Engine/Input -Isrc/Engine/Platform -Isrc/Engine/Main -Isrc/Engine/Renderer -Isrc/Util
-CXXFLAGS += -Ilibraries/bassasio/include -Ilibraries/bassmix/include -Ilibraries/basswasapi/include
+CXXFLAGS += -Ilibraries/bassasio/include -Ilibraries/bassmix/include -Ilibraries/basswasapi/include -Ilibraries/bassloud/include
 CXXFLAGS += -g3
 
-LDFLAGS = -ldiscord-rpc -lbass -lbassmix -lbass_fx -lpthread -lstdc++
+LDFLAGS = -ldiscord-rpc -lbass -lbassmix -lbass_fx -lbassloud -lpthread -lstdc++
 LDFLAGS += `pkgconf --static --libs $(LIBS)` `curl-config --static-libs --libs`
 
 

+ 2 - 1
README.md

@@ -1,4 +1,4 @@
-# Multiplayer McOsu
+# neosu
 
 This is a third-party fork of [McOsu](https://store.steampowered.com/app/607260/McOsu/), unsupported by McKay.
 
@@ -19,6 +19,7 @@ Required dependencies:
 - [bass](https://www.un4seen.com/download.php?bass24-linux)
 - [bassmix](https://www.un4seen.com/download.php?bassmix24-linux)
 - [bass_fx](https://www.un4seen.com/download.php?z/0/bass_fx24-linux)
+- [BASSloud](https://www.un4seen.com/download.php?bassloud24-linux)
 - blkid
 - freetype2
 - glew

+ 3 - 3
build.bat

@@ -13,7 +13,7 @@ if %errorlevel% equ 0 (
 set CXXFLAGS=-std=c++17 -Wall -fmessage-length=0 -Wno-sign-compare -Wno-unused-local-typedefs -Wno-reorder -Wno-switch -IC:\mingw32\include
 set CXXFLAGS=%CXXFLAGS% -D__GXX_EXPERIMENTAL_CXX0X__
 set CXXFLAGS=%CXXFLAGS% -Isrc/App -Isrc/App/Osu -Isrc/Engine -Isrc/GUI -Isrc/GUI/Windows -Isrc/GUI/Windows/VinylScratcher -Isrc/Engine/Input -Isrc/Engine/Platform -Isrc/Engine/Main -Isrc/Engine/Renderer -Isrc/Util
-set LDFLAGS=-logg -lADLMIDI -lmad -lmodplug -lsmpeg -lgme -lvorbis -lopus -lvorbisfile -ldiscord-rpc -lSDL2_mixer_ext.dll -lSDL2 -ld3dcompiler_47 -ld3d11 -ldxgi -lcurl -llibxinput9_1_0 -lfreetype -lopengl32 -lOpenCL -lvulkan-1 -lglew32 -lglu32 -lgdi32 -lbass -lbassasio -lbass_fx -lbassmix -lbasswasapi -lcomctl32 -lDwmapi -lComdlg32 -lpsapi -lws2_32 -lwinmm -lpthread -llibjpeg -lwbemuuid -lole32 -loleaut32 -llzma
+set LDFLAGS=-logg -lADLMIDI -lmad -lmodplug -lsmpeg -lgme -lvorbis -lopus -lvorbisfile -ldiscord-rpc -lSDL2_mixer_ext.dll -lSDL2 -ld3dcompiler_47 -ld3d11 -ldxgi -lcurl -llibxinput9_1_0 -lfreetype -lopengl32 -lOpenCL -lvulkan-1 -lglew32 -lglu32 -lgdi32 -lbass -lbassasio -lbass_fx -lbassmix -lbasswasapi -lbassloud -lcomctl32 -lDwmapi -lComdlg32 -lpsapi -lws2_32 -lwinmm -lpthread -llibjpeg -lwbemuuid -lole32 -loleaut32 -llzma
 
 set CXXFLAGS=%CXXFLAGS% -g3
 rem set CXXFLAGS=%CXXFLAGS% -O3
@@ -48,7 +48,7 @@ set CXXFLAGS=%CXXFLAGS% %LIBPATHS%
 
 rem BUILD OBJECTS
 if exist "build_flags.txt" del /q "build_flags.txt"
-<nul set /p "=-o build/McOsu.exe " >> build_flags.txt
+<nul set /p "=-o build/neosu.exe " >> build_flags.txt
 <nul set /p "=!CXXFLAGS! " >> build_flags.txt
 set OBJECTS=
 for /r "src" %%i in (*.cpp) do (
@@ -79,7 +79,7 @@ if !ERRORLEVEL! neq 0 (
 ) else (
 	del /q "build_flags.txt"
 	cd build
-	McOsu.exe
+	neosu.exe
 )
 
 :END

+ 0 - 0
libraries/bassloud/2.4.txt


BIN
libraries/bassloud/bin/bassloud.dll


+ 48 - 0
libraries/bassloud/include/bassloud.h

@@ -0,0 +1,48 @@
+/*
+	BASSloud 2.4 C/C++ header file
+	Copyright (c) 2023 Un4seen Developments Ltd.
+
+	See the BASSLOUD.CHM file for more detailed documentation
+*/
+
+#ifndef BASSLOUDNESS_H
+#define BASSLOUDNESS_H
+
+#include "bass.h"
+
+#if BASSVERSION!=0x204
+#error conflicting BASS and BASSLOUDNESS versions
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef BASSLOUDDEF
+#define BASSLOUDDEF(f) WINAPI f
+#endif
+
+typedef DWORD HLOUDNESS;		// loudness handle
+	
+// BASS_Loudness_Start flags / BASS_Loudness_GetLevel modes
+#define BASS_LOUDNESS_CURRENT		0
+#define BASS_LOUDNESS_INTEGRATED	1
+#define BASS_LOUDNESS_RANGE			2
+#define BASS_LOUDNESS_PEAK			4
+#define BASS_LOUDNESS_TRUEPEAK		8
+#define BASS_LOUDNESS_AUTOFREE		0x8000
+
+DWORD BASSLOUDDEF(BASS_Loudness_GetVersion)(void);
+
+HLOUDNESS BASSLOUDDEF(BASS_Loudness_Start)(DWORD handle, DWORD flags, int priority);
+BOOL BASSLOUDDEF(BASS_Loudness_Stop)(DWORD handle);
+BOOL BASSLOUDDEF(BASS_Loudness_SetChannel)(HLOUDNESS handle, DWORD channel, int priority);
+DWORD BASSLOUDDEF(BASS_Loudness_GetChannel)(HLOUDNESS handle);
+BOOL BASSLOUDDEF(BASS_Loudness_GetLevel)(HLOUDNESS handle, DWORD mode, float *level);
+BOOL BASSLOUDDEF(BASS_Loudness_GetLevelMulti)(HLOUDNESS *handles, DWORD count, DWORD mode, float *level);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif

BIN
libraries/bassloud/lib/windows/bassloud.lib


+ 1 - 1
resources/materials/default/skin.ini

@@ -1,5 +1,5 @@
 [General]
-Name: McOsu! VR
+Name: neosu
 Author: Various Artists
 Version: latest
 SliderStyle: 2

+ 5 - 2
src/App/Osu/Bancho.cpp

@@ -194,7 +194,7 @@ void handle_packet(Packet *packet) {
             bancho.osu->m_optionsMenu->logInButton->setColor(0xffff0000);
             bancho.osu->m_optionsMenu->logInButton->is_loading = false;
             convar->getConVarByName("mp_autologin")->setValue(true);
-            ConVars::sv_cheats.setValue(false);
+            ConVars::sv_cheats.setValue(!bancho.submit_scores());
             print_new_channels = true;
 
             std::stringstream ss;
@@ -398,7 +398,10 @@ void handle_packet(Packet *packet) {
         }
     } else if(packet->id == MAIN_MENU_ICON) {
         UString icon = read_string(packet);
-        debugLog("Main menu icon: %s\n", icon.toUtf8());
+        auto urls = icon.split("|");
+        if(urls.size() == 2 && urls[0].find("https://") == 0) {
+            bancho.server_icon_url = urls[0];
+        }
     } else if(packet->id == MATCH_PLAYER_SKIPPED) {
         int32_t user_id = read_int32(packet);
         bancho.osu->m_room->on_player_skip(user_id);

+ 5 - 1
src/App/Osu/Bancho.h

@@ -7,6 +7,7 @@
 #include "UString.h"
 
 class Osu;
+class Image;
 
 enum class ServerPolicy {
     NO,
@@ -15,7 +16,7 @@ enum class ServerPolicy {
 };
 
 struct Bancho {
-    UString mcosu_version;
+    UString neosu_version;
 
     Osu *osu = nullptr;
     UString endpoint;
@@ -24,6 +25,9 @@ struct Bancho {
     MD5Hash pw_md5;
     Room room;
 
+    UString server_icon_url;
+    Image *server_icon = nullptr;
+
     bool prefer_daycore = false;
 
     ServerPolicy score_submission_policy = ServerPolicy::NO_PREFERENCE;

+ 9 - 3
src/App/Osu/BanchoNetworking.cpp

@@ -21,6 +21,7 @@
 #include "OsuSongBrowser.h"
 #include "OsuUIAvatar.h"
 #include "OsuUIButton.h"
+#include "ResourceManager.h"
 #include "miniz.h"
 
 // Bancho protocol
@@ -54,7 +55,7 @@ void disconnect() {
         write_int32(&packet, 0);
 
         CURL *curl = curl_easy_init();
-        auto version_header = UString::format("x-mcosu-ver: %s", bancho.mcosu_version.toUtf8());
+        auto version_header = UString::format("x-mcosu-ver: %s", bancho.neosu_version.toUtf8());
         struct curl_slist *chunk = NULL;
         chunk = curl_slist_append(chunk, auth_header.c_str());
         chunk = curl_slist_append(chunk, version_header.toUtf8());
@@ -79,6 +80,11 @@ void disconnect() {
     free(outgoing.memory);
     outgoing = Packet();
     bancho.user_id = 0;
+    bancho.server_icon_url = "";
+    if(bancho.server_icon != nullptr) {
+        engine->getResourceManager()->destroyResource(bancho.server_icon);
+        bancho.server_icon = nullptr;
+    }
 
     bancho.score_submission_policy = ServerPolicy::NO_PREFERENCE;
     bancho.set_fposu_flag = false;
@@ -105,7 +111,7 @@ void disconnect() {
 void reconnect() {
     if(bancho.osu->m_iInstanceID > 0) {
         // XXX: To handle multiple instances you would have to do some complex IPC
-        //      Would be great to be able to use McOsu as tournament spectator client...
+        //      Would be great to be able to use neosu as tournament spectator client...
         //      But that's not in scope right now.
         return;
     }
@@ -203,7 +209,7 @@ static void send_bancho_packet(CURL *curl, Packet outgoing) {
     response.memory = (uint8_t *)malloc(2048);
 
     struct curl_slist *chunk = NULL;
-    auto version_header = UString::format("x-mcosu-ver: %s", bancho.mcosu_version.toUtf8());
+    auto version_header = UString::format("x-mcosu-ver: %s", bancho.neosu_version.toUtf8());
     chunk = curl_slist_append(chunk, version_header.toUtf8());
     if(!auth_header.empty()) {
         chunk = curl_slist_append(chunk, auth_header.c_str());

+ 6 - 6
src/App/Osu/BanchoNetworking.h

@@ -6,16 +6,16 @@
 #include "BanchoProtocol.h"
 
 #ifdef _DEBUG
-#define MCOSU_STREAM "dev"
+#define NEOSU_STREAM "dev"
 #else
-#define MCOSU_STREAM "release"
+#define NEOSU_STREAM "release"
 #endif
 
-#define MCOSU_UPDATE_URL "https://mcosu.kiwec.net"
+#define NEOSU_UPDATE_URL "https://neosu.kiwec.net"
 
 // NOTE: Full version can be something like "b20200201.2cuttingedge"
-#define OSU_VERSION "b20240411.1"
-#define OSU_VERSION_DATEONLY 20240411
+#define OSU_VERSION "b20240425.2"
+#define OSU_VERSION_DATEONLY 20240425
 
 enum APIRequestType {
     GET_BEATMAPSET_INFO,
@@ -53,7 +53,7 @@ void send_packet(Packet &packet);
 void receive_api_responses();
 void receive_bancho_packets();
 
-// Initialize networking thread. Should be called once when starting McOsu.
+// Initialize networking thread. Should be called once when starting neosu.
 void init_networking_thread();
 
 size_t curl_write(void *contents, size_t size, size_t nmemb, void *userp);

+ 4 - 0
src/App/Osu/BanchoSubmitter.cpp

@@ -18,6 +18,10 @@ void submit_score(Score score) {
     debugLog("Submitting score...\n");
     const char *GRADES[] = {"XH", "SH", "X", "S", "A", "B", "C", "D", "F", "N"};
 
+    // We set custom mod flags, but not every server supports them.
+    if(!bancho.set_fposu_flag) score.modsLegacy &= ~ModFlags::FPoSu;
+    if(!bancho.set_mirror_flag) score.modsLegacy &= ~ModFlags::Mirror;
+
     uint8_t *compressed_data = NULL;
 
     char score_time[80];

+ 1 - 0
src/App/Osu/Downloader.cpp

@@ -90,6 +90,7 @@ void* do_downloads(void* arg) {
         curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*)&response);
         curl_easy_setopt(curl, CURLOPT_XFERINFODATA, result);
         curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, update_download_progress);
+        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
         curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
 #ifdef _WIN32
         // ABSOLUTELY RETARDED, FUCK WINDOWS

+ 181 - 155
src/App/Osu/Osu.cpp

@@ -65,7 +65,7 @@
 
 // release configuration
 ConVar auto_update("auto_update", true, FCVAR_NONE);
-ConVar osu_version("osu_version", 34.10f, FCVAR_NONE);
+ConVar osu_version("osu_version", 35.00f, FCVAR_NONE);
 
 #ifdef _DEBUG
 ConVar osu_debug("osu_debug", true, FCVAR_NONE);
@@ -143,6 +143,8 @@ ConVar flashlight_always_hard("flashlight_always_hard", false, FCVAR_NONE, "alwa
 ConVar start_first_main_menu_song_at_preview_point("start_first_main_menu_song_at_preview_point", false, FCVAR_NONE);
 ConVar nightcore_enjoyer("nightcore_enjoyer", false, FCVAR_NONE, "automatically select nightcore when speed modifying");
 ConVar scoreboard_animations("scoreboard_animations", true, FCVAR_NONE, "animate in-game scoreboard");
+ConVar instant_replay_duration("instant_replay_duration", 15.f, FCVAR_NONE, "instant replay (F2) duration, in seconds");
+ConVar normalize_loudness("normalize_loudness", false, FCVAR_NONE, "normalize loudness across songs");
 
 ConVar mp_server("mp_server", "ez-pp.farm", FCVAR_NONE);
 ConVar mp_password("mp_password", "", FCVAR_NONE);
@@ -169,9 +171,9 @@ Shader *flashlight_shader = nullptr;
 Osu::Osu(int instanceID) {
     srand(time(NULL));
 
-    bancho.mcosu_version = UString::format("%.2f-" MCOSU_STREAM, osu_version.getFloat());
+    bancho.neosu_version = UString::format("%.2f-" NEOSU_STREAM, osu_version.getFloat());
     bancho.user_agent =
-        UString::format("Mozilla/5.0 (compatible; McOsu/%s; +" MCOSU_UPDATE_URL "/)", bancho.mcosu_version.toUtf8());
+        UString::format("Mozilla/5.0 (compatible; neosu/%s; +" NEOSU_UPDATE_URL "/)", bancho.neosu_version.toUtf8());
 
     m_iInstanceID = instanceID;
 
@@ -226,7 +228,7 @@ Osu::Osu(int instanceID) {
     m_experimentalMods.push_back(convar->getConVarByName("osu_mod_shirone"));
     m_experimentalMods.push_back(convar->getConVarByName("osu_mod_approach_different"));
 
-    env->setWindowTitle("McOsu");
+    env->setWindowTitle("neosu");
     env->setCursorVisible(false);
 
     engine->getConsoleBox()->setRequireShiftToActivate(true);
@@ -241,25 +243,6 @@ Osu::Osu(int instanceID) {
 
     osu_resolution.setValue(UString::format("%ix%i", engine->getScreenWidth(), engine->getScreenHeight()));
 
-    // OS specific engine settings/overrides
-    if(env->getOS() == Environment::OS::OS_HORIZON) {
-        convar->getConVarByName("fps_max")->setValue(60.0f);
-        convar->getConVarByName("ui_scrollview_resistance")->setValue(25.0f);
-        convar->getConVarByName("osu_scores_legacy_enabled")->setValue(0.0f);       // would collide
-        convar->getConVarByName("osu_collections_legacy_enabled")->setValue(0.0f);  // unnecessary
-        convar->getConVarByName("osu_mod_mafham_render_livesize")->setValue(7.0f);
-        convar->getConVarByName("osu_mod_mafham_render_chunksize")->setValue(12.0f);
-        convar->getConVarByName("osu_mod_touchdevice")->setDefaultFloat(1.0f);
-        convar->getConVarByName("osu_mod_touchdevice")->setValue(1.0f);
-        convar->getConVarByName("osu_key_quick_retry")->setValue(15.0f);            // L, SDL_SCANCODE_L
-        convar->getConVarByName("osu_key_seek_time")->setValue(21.0f);              // R, SDL_SCANCODE_R
-        convar->getConVarByName("osu_key_decrease_local_offset")->setValue(29.0f);  // ZL, SDL_SCANCODE_Z
-        convar->getConVarByName("osu_key_increase_local_offset")->setValue(25.0f);  // ZR, SDL_SCANCODE_V
-        convar->getConVarByName("osu_key_left_click")->setValue(0.0f);              // (disabled)
-        convar->getConVarByName("osu_key_right_click")->setValue(0.0f);             // (disabled)
-        convar->getConVarByName("name")->setValue(env->getUsername());
-    }
-
     env->setWindowResizable(false);
 
     // generate default osu! appdata user path
@@ -303,7 +286,6 @@ Osu::Osu(int instanceID) {
 
     // vars
     m_skin = NULL;
-    m_songBrowser2 = NULL;
     m_backgroundImageHandler = NULL;
     m_modSelector = NULL;
     m_updateHandler = NULL;
@@ -410,9 +392,6 @@ Osu::Osu(int instanceID) {
     osu_skin_reload.setCallback(fastdelegate::MakeDelegate(this, &Osu::onSkinReload));
     onSkinChange("", osu_skin.getString());
 
-    // update mod settings
-    updateMods();
-
     // load global resources
     const int baseDPI = 96;
     const int newDPI = Osu::getUIScale(this) * baseDPI;
@@ -429,7 +408,6 @@ Osu::Osu(int instanceID) {
         engine->getResourceManager()->loadFont("SourceSansPro-Bold.otf", "FONT_OSU_SONGBROWSER_BOLD", 30, true, newDPI);
     m_fontIcons = engine->getResourceManager()->loadFont("fontawesome-webfont.ttf", "FONT_OSU_ICONS", OsuIcons::icons,
                                                          26, true, newDPI);
-
     m_fonts.push_back(defaultFont);
     m_fonts.push_back(m_titleFont);
     m_fonts.push_back(m_subTitleFont);
@@ -465,11 +443,11 @@ Osu::Osu(int instanceID) {
     }
 
     // load subsystems, add them to the screens array
+    m_songBrowser2 = new OsuSongBrowser(this);
     m_volumeOverlay = new OsuVolumeOverlay(this);
     m_tooltipOverlay = new OsuTooltipOverlay(this);
     m_mainMenu = new OsuMainMenu(this);
     m_optionsMenu = new OsuOptionsMenu(this);
-    m_songBrowser2 = new OsuSongBrowser(this);
     m_backgroundImageHandler = new OsuBackgroundImageHandler();
     m_modSelector = new OsuModSelector(this);
     m_rankingScreen = new OsuRankingScreen(this);
@@ -505,6 +483,9 @@ Osu::Osu(int instanceID) {
     m_screens.push_back(m_mainMenu);
     m_screens.push_back(m_tooltipOverlay);
 
+    // update mod settings
+    updateMods();
+
     // Init online functionality (multiplayer/leaderboards/etc)
     bancho.osu = this;
     init_networking_thread();
@@ -555,10 +536,9 @@ void Osu::draw(Graphics *g) {
         return;
     }
 
-    // if we are not using the native window resolution, or in vr mode, or playing on a nintendo switch, or multiple
-    // instances are active, draw into the buffer
-    const bool isBufferedDraw =
-        osu_resolution_enabled.getBool() || env->getOS() == Environment::OS::OS_HORIZON || m_iInstanceID > 0;
+    // if we are not using the native window resolution, or in vr mode, or multiple instances are active,
+    // draw into the buffer
+    const bool isBufferedDraw = osu_resolution_enabled.getBool() || m_iInstanceID > 0;
 
     if(isBufferedDraw) m_backBuffer->enable();
 
@@ -737,42 +717,26 @@ void Osu::draw(Graphics *g) {
         }
 
         g->setBlending(false);
-        {
-            if(env->getOS() == Environment::OS::OS_HORIZON) {
-                // NOTE: the nintendo switch always draws in 1080p, even undocked
-                const Vector2 backupResolution = engine->getGraphics()->getResolution();
-                g->onResolutionChange(Vector2(1920, 1080));
-                {
-                    // NOTE: apparently, after testing with libnx 3.0.0, it now requires half 720p offset when undocked?
-                    if(backupResolution.y < 722) offset.y = 720 / 2;
-
-                    m_backBuffer->draw(g, offset.x * (1.0f + osu_letterboxing_offset_x.getFloat()),
-                                       offset.y * (1.0f + osu_letterboxing_offset_y.getFloat()),
-                                       g_vInternalResolution.x, g_vInternalResolution.y);
-                }
-                g->onResolutionChange(backupResolution);
+        if(osu_letterboxing.getBool()) {
+            m_backBuffer->draw(g, offset.x * (1.0f + osu_letterboxing_offset_x.getFloat()),
+                               offset.y * (1.0f + osu_letterboxing_offset_y.getFloat()), g_vInternalResolution.x,
+                               g_vInternalResolution.y);
+        } else {
+            if(osu_resolution_keep_aspect_ratio.getBool()) {
+                const float scale =
+                    getImageScaleToFitResolution(m_backBuffer->getSize(), engine->getGraphics()->getResolution());
+                const float scaledWidth = m_backBuffer->getWidth() * scale;
+                const float scaledHeight = m_backBuffer->getHeight() * scale;
+                m_backBuffer->draw(
+                    g,
+                    std::max(0.0f, engine->getGraphics()->getResolution().x / 2.0f - scaledWidth / 2.0f) *
+                        (1.0f + osu_letterboxing_offset_x.getFloat()),
+                    std::max(0.0f, engine->getGraphics()->getResolution().y / 2.0f - scaledHeight / 2.0f) *
+                        (1.0f + osu_letterboxing_offset_y.getFloat()),
+                    scaledWidth, scaledHeight);
             } else {
-                if(osu_letterboxing.getBool())
-                    m_backBuffer->draw(g, offset.x * (1.0f + osu_letterboxing_offset_x.getFloat()),
-                                       offset.y * (1.0f + osu_letterboxing_offset_y.getFloat()),
-                                       g_vInternalResolution.x, g_vInternalResolution.y);
-                else {
-                    if(osu_resolution_keep_aspect_ratio.getBool()) {
-                        const float scale = getImageScaleToFitResolution(m_backBuffer->getSize(),
-                                                                         engine->getGraphics()->getResolution());
-                        const float scaledWidth = m_backBuffer->getWidth() * scale;
-                        const float scaledHeight = m_backBuffer->getHeight() * scale;
-                        m_backBuffer->draw(
-                            g,
-                            std::max(0.0f, engine->getGraphics()->getResolution().x / 2.0f - scaledWidth / 2.0f) *
-                                (1.0f + osu_letterboxing_offset_x.getFloat()),
-                            std::max(0.0f, engine->getGraphics()->getResolution().y / 2.0f - scaledHeight / 2.0f) *
-                                (1.0f + osu_letterboxing_offset_y.getFloat()),
-                            scaledWidth, scaledHeight);
-                    } else
-                        m_backBuffer->draw(g, 0, 0, engine->getGraphics()->getResolution().x,
-                                           engine->getGraphics()->getResolution().y);
-                }
+                m_backBuffer->draw(g, 0, 0, engine->getGraphics()->getResolution().x,
+                                   engine->getGraphics()->getResolution().y);
             }
         }
         g->setBlending(true);
@@ -1044,36 +1008,128 @@ void Osu::update() {
     }
 }
 
-void Osu::updateMods() {
-    if(getSelectedBeatmap() != nullptr && getSelectedBeatmap()->m_bIsWatchingReplay) {
-        // XXX: clear experimental mods
-        m_bModNC = replay_info.mod_flags & ModFlags::Nightcore;
-        m_bModDT = replay_info.mod_flags & ModFlags::DoubleTime && !m_bModNC;
-        m_bModHT = replay_info.mod_flags & ModFlags::HalfTime;
-        m_bModDC = false;
-        if(m_bModHT && bancho.prefer_daycore) {
-            m_bModHT = false;
-            m_bModDC = true;
+UString getModsStringForConVar(int mods) {
+    UString modsString = "  ";  // double space to reset if emtpy
+
+    // NOTE: the order here is different on purpose, to avoid name collisions during parsing (see Osu::updateMods())
+    // order is the same as in OsuModSelector::updateModConVar()
+    if(mods & ModFlags::Easy) modsString.append("ez");
+    if(mods & ModFlags::HardRock) modsString.append("hr");
+    if(mods & ModFlags::Relax) modsString.append("relax");
+    if(mods & ModFlags::NoFail) modsString.append("nf");
+    if(mods & ModFlags::SuddenDeath) modsString.append("sd");
+    if(mods & ModFlags::Perfect) modsString.append("ss,");
+    if(mods & ModFlags::Autopilot) modsString.append("autopilot");
+    if(mods & ModFlags::HalfTime) modsString.append("ht");
+    if(mods & ModFlags::DoubleTime) modsString.append("dt");
+    if(mods & ModFlags::Nightcore) modsString.append("nc");
+    if(mods & ModFlags::SpunOut) modsString.append("spunout");
+    if(mods & ModFlags::Hidden) modsString.append("hd");
+    if(mods & ModFlags::Autoplay) modsString.append("auto");
+    if(mods & ModFlags::Nightmare) modsString.append("nightmare");
+    if(mods & ModFlags::Target) modsString.append("practicetarget");
+    if(mods & ModFlags::TouchDevice) modsString.append("nerftd");
+    if(mods & ModFlags::ScoreV2) modsString.append("v2");
+
+    return modsString;
+}
+
+bool Osu::useMods(Score *score) {
+    bool nomod = (score->modsLegacy == 0);
+
+    // legacy mods (common to all scores)
+    getModSelector()->resetMods();
+    osu_mods.setValue(getModsStringForConVar(score->modsLegacy));
+
+    if(score->modsLegacy & ModFlags::FPoSu) {
+        convar->getConVarByName("osu_mod_fposu")->setValue(true);
+    }
+
+    // NOTE: We don't know whether the original score was only horizontal, only vertical, or both
+    if(score->isLegacyScore && score->modsLegacy & ModFlags::Mirror) {
+        convar->getConVarByName("osu_playfield_mirror_horizontal")->setValue(true);
+        convar->getConVarByName("osu_playfield_mirror_vertical")->setValue(true);
+    }
+
+    if(!score->isLegacyScore && !score->isImportedLegacyScore) {
+        // neosu score, custom values for everything possible, have to calculate and check whether to apply any
+        // overrides (or leave default)
+        // reason being that just because the speedMultiplier stored in the score = 1.5x doesn't mean that we should
+        // move the override slider to 1.5x especially for CS/AR/OD/HP, because those get stored in the score as
+        // directly coming from OsuBeatmap::getAR() (so with pre-applied difficultyMultiplier etc.)
+
+        // overrides
+
+        // NOTE: if the beatmap is loaded (in db), then use the raw base values from there, otherwise trust potentially
+        // incorrect stored values from score (see explanation above)
+        float tempAR = score->AR;
+        float tempCS = score->CS;
+        float tempOD = score->OD;
+        float tempHP = score->HP;
+        const OsuDatabaseBeatmap *diff2 = getSongBrowser()->getDatabase()->getBeatmapDifficulty(score->md5hash);
+        if(diff2 != NULL) {
+            tempAR = diff2->getAR();
+            tempCS = diff2->getCS();
+            tempOD = diff2->getOD();
+            tempHP = diff2->getHP();
         }
 
-        m_bModNF = replay_info.mod_flags & ModFlags::NoFail;
-        m_bModEZ = replay_info.mod_flags & ModFlags::Easy;
-        m_bModTD = replay_info.mod_flags & ModFlags::TouchDevice;
-        m_bModHD = replay_info.mod_flags & ModFlags::Hidden;
-        m_bModHR = replay_info.mod_flags & ModFlags::HardRock;
-        m_bModSD = replay_info.mod_flags & ModFlags::SuddenDeath;
-        m_bModRelax = replay_info.mod_flags & ModFlags::Relax;
-        m_bModAutopilot = replay_info.mod_flags & ModFlags::Autopilot;
-        m_bModAuto = replay_info.mod_flags & ModFlags::Autoplay && !m_bModAutopilot;
-        m_bModSpunout = replay_info.mod_flags & ModFlags::SpunOut;
-        m_bModSS = replay_info.mod_flags & ModFlags::Perfect;
-        m_bModTarget = replay_info.mod_flags & ModFlags::Target;
-        m_bModScorev2 = replay_info.mod_flags & ModFlags::ScoreV2;
-        m_bModFlashlight = replay_info.mod_flags & ModFlags::Flashlight;
-        m_bModNightmare = false;
+        const OsuReplay::BEATMAP_VALUES legacyValues =
+            OsuReplay::getBeatmapValuesForModsLegacy(score->modsLegacy, tempAR, tempCS, tempOD, tempHP);
+
+        // beatmap values
+        {
+            const float beatmapValueComparisonEpsilon = 0.0001f;
+            if(std::abs(legacyValues.AR - score->AR) >= beatmapValueComparisonEpsilon) {
+                convar->getConVarByName("osu_ar_override")->setValue(score->AR);
+                nomod = false;
+            }
+            if(std::abs(legacyValues.CS - score->CS) >= beatmapValueComparisonEpsilon) {
+                convar->getConVarByName("osu_cs_override")->setValue(score->CS);
+                nomod = false;
+            }
+            if(std::abs(legacyValues.OD - score->OD) >= beatmapValueComparisonEpsilon) {
+                convar->getConVarByName("osu_od_override")->setValue(score->OD);
+                nomod = false;
+            }
+            if(std::abs(legacyValues.HP - score->HP) >= beatmapValueComparisonEpsilon) {
+                convar->getConVarByName("osu_hp_override")->setValue(score->HP);
+                nomod = false;
+            }
+        }
 
-        osu_speed_override.setValue("-1");
-    } else if(bancho.is_in_a_multi_room()) {
+        // speed multiplier
+        {
+            const float speedMultiplierComparisonEpsilon = 0.0001f;
+            if(std::abs(legacyValues.speedMultiplier - score->speedMultiplier) >= speedMultiplierComparisonEpsilon) {
+                osu_speed_override.setValue(score->speedMultiplier);
+                nomod = false;
+            }
+        }
+
+        // experimental mods
+        {
+            auto cv = UString(score->experimentalModsConVars.c_str());
+            const std::vector<UString> experimentalMods = cv.split(";");
+            for(size_t i = 0; i < experimentalMods.size(); i++) {
+                if(experimentalMods[i] == UString("")) continue;
+
+                ConVar *cvar = convar->getConVarByName(experimentalMods[i], false);
+                if(cvar != NULL) {
+                    cvar->setValue(1.0f);  // enable experimental mod (true, 1.0f)
+                    nomod = false;
+                } else {
+                    debugLog("couldn't find \"%s\"\n", experimentalMods[i].toUtf8());
+                }
+            }
+        }
+    }
+
+    return nomod;
+}
+
+void Osu::updateMods() {
+    if(bancho.is_in_a_multi_room()) {
         m_bModNC = bancho.room.mods & ModFlags::Nightcore;
         if(m_bModNC) {
             m_bModDT = false;
@@ -1161,7 +1217,8 @@ void Osu::updateMods() {
 
     // notify the possibly running beatmap of mod changes, for e.g. recalculating stacks dynamically if HR is toggled
     {
-        if(getSelectedBeatmap() != NULL) getSelectedBeatmap()->onModUpdate();
+        getSelectedBeatmap()->onModUpdate();
+        getSelectedBeatmap()->vanilla = false;  // user just cheated, prevent score submission
 
         if(m_songBrowser2 != NULL) m_songBrowser2->recalculateStarsForSelectedBeatmap(true);
     }
@@ -1227,10 +1284,8 @@ void Osu::onKeyDown(KeyboardEvent &key) {
     // boss key (minimize + mute)
     if(key == (KEYCODE)OsuKeyBindings::BOSS_KEY.getInt()) {
         engine->getEnvironment()->minimize();
-        if(getSelectedBeatmap() != NULL) {
-            m_bWasBossKeyPaused = getSelectedBeatmap()->isPreviewMusicPlaying();
-            getSelectedBeatmap()->pausePreviewMusic(false);
-        }
+        m_bWasBossKeyPaused = getSelectedBeatmap()->isPreviewMusicPlaying();
+        getSelectedBeatmap()->pausePreviewMusic(false);
     }
 
     // local hotkeys (and gameplay keys)
@@ -1244,11 +1299,25 @@ void Osu::onKeyDown(KeyboardEvent &key) {
             if(!key.isConsumed() && key == (KEYCODE)OsuKeyBindings::INSTANT_REPLAY.getInt()) {
                 if(!beatmap->m_bIsWatchingReplay) {
                     Score score;
+                    score.isLegacyScore = false;
+                    score.isImportedLegacyScore = false;
                     score.replay = beatmap->live_replay;
                     score.md5hash = beatmap->getSelectedDifficulty2()->getMD5Hash();
                     score.modsLegacy = getScore()->getModsLegacy();
+                    score.speedMultiplier = getSpeedMultiplier();
+                    score.CS = beatmap->getCS();
+                    score.AR = beatmap->getAR();
+                    score.OD = beatmap->getOD();
+                    score.HP = beatmap->getHP();
+
+                    std::vector<ConVar *> allExperimentalMods = getExperimentalMods();
+                    for(int i = 0; i < allExperimentalMods.size(); i++) {
+                        if(allExperimentalMods[i]->getBool()) {
+                            score.experimentalModsConVars.append(allExperimentalMods[i]->getName());
+                            score.experimentalModsConVars.append(";");
+                        }
+                    }
 
-                    // XXX: code reuse from saveAndSubmitScore, also incorrect when using Auto
                     if(bancho.is_online()) {
                         score.player_id = bancho.user_id;
                         score.playerName = bancho.username.toUtf8();
@@ -1259,7 +1328,8 @@ void Osu::onKeyDown(KeyboardEvent &key) {
                     }
 
                     double percentFinished = beatmap->getPercentFinished();
-                    double offsetPercent = 10000.f / beatmap->getLength();
+                    double duration = convar->getConVarByName("instant_replay_duration")->getFloat() * 1000.f;
+                    double offsetPercent = duration / beatmap->getLength();
                     double seekPoint = fmax(0.f, percentFinished - offsetPercent);
                     beatmap->cancelFailing();
                     beatmap->watch(score, seekPoint);
@@ -1605,42 +1675,7 @@ void Osu::saveScreenshot() {
                        screenshot_path);
 }
 
-void Osu::onBeforePlayStart() {
-    debugLog("Osu::onBeforePlayStart()\n");
-
-    engine->getSound()->play(m_skin->getMenuHit());
-
-    updateMods();
-
-    // mp hack
-    {
-        m_mainMenu->setVisible(false);
-        m_modSelector->setVisible(false);
-        m_optionsMenu->setVisible(false);
-        m_pauseMenu->setVisible(false);
-    }
-
-    // HACKHACK: stuck key quickfix
-    {
-        m_bKeyboardKey1Down = false;
-        m_bKeyboardKey12Down = false;
-        m_bKeyboardKey2Down = false;
-        m_bKeyboardKey22Down = false;
-        m_bMouseKey1Down = false;
-        m_bMouseKey2Down = false;
-
-        if(getSelectedBeatmap() != NULL) {
-            getSelectedBeatmap()->keyReleased1(false);
-            getSelectedBeatmap()->keyReleased1(true);
-            getSelectedBeatmap()->keyReleased2(false);
-            getSelectedBeatmap()->keyReleased2(true);
-        }
-    }
-}
-
 void Osu::onPlayStart() {
-    debugLog("Osu::onPlayStart()\n");
-
     m_snd_change_check_interval_ref->setValue(0.0f);
 
     if(m_bModAuto || m_bModAutopilot || getSelectedBeatmap()->m_bIsWatchingReplay) {
@@ -1663,8 +1698,6 @@ void Osu::onPlayStart() {
 }
 
 void Osu::onPlayEnd(bool quit, bool aborted) {
-    debugLog("Osu::onPlayEnd()\n");
-
     m_snd_change_check_interval_ref->setValue(m_snd_change_check_interval_ref->getDefaultFloat());
 
     if(!quit) {
@@ -1788,9 +1821,7 @@ float Osu::getAnimationSpeedMultiplier() {
 
 bool Osu::isInPlayMode() { return (m_songBrowser2 != NULL && m_songBrowser2->hasSelectedAndIsPlaying()); }
 
-bool Osu::isNotInPlayModeOrPaused() {
-    return !isInPlayMode() || (getSelectedBeatmap() != NULL && getSelectedBeatmap()->isPaused());
-}
+bool Osu::isNotInPlayModeOrPaused() { return !isInPlayMode() || getSelectedBeatmap()->isPaused(); }
 
 bool Osu::shouldFallBackToLegacySliderRenderer() {
     return osu_force_legacy_slider_renderer.getBool() || m_osu_mod_wobble_ref->getBool() ||
@@ -1936,7 +1967,7 @@ void Osu::updateWindowsKeyDisable() {
 
     if(osu_win_disable_windows_key_while_playing.getBool()) {
         const bool isPlayerPlaying =
-            engine->hasFocus() && isInPlayMode() && getSelectedBeatmap() != NULL &&
+            engine->hasFocus() && isInPlayMode() &&
             (!getSelectedBeatmap()->isPaused() || getSelectedBeatmap()->isRestartScheduled()) && !m_bModAuto;
         m_win_disable_windows_key_ref->setValue(isPlayerPlaying ? 1.0f : 0.0f);
     }
@@ -1947,7 +1978,7 @@ void Osu::fireResolutionChanged() { onResolutionChanged(g_vInternalResolution);
 void Osu::onCheatsChange(UString oldValue, UString newValue) {
     (void)oldValue;
     (void)newValue;
-    if(bancho.is_online() && ConVars::sv_cheats.getBool()) {
+    if(bancho.is_online() && (bancho.submit_scores() || bancho.is_in_a_multi_room()) && ConVars::sv_cheats.getBool()) {
         ConVars::sv_cheats.setValue(false);
     }
 }
@@ -2002,7 +2033,7 @@ void Osu::onFocusGained() {
 
     if(m_bWasBossKeyPaused) {
         m_bWasBossKeyPaused = false;
-        if(getSelectedBeatmap() != NULL) getSelectedBeatmap()->pausePreviewMusic();
+        getSelectedBeatmap()->pausePreviewMusic();
     }
 
     updateWindowsKeyDisable();
@@ -2031,8 +2062,7 @@ bool Osu::onShutdown() {
     debugLog("Osu::onShutdown()\n");
 
     if(!osu_alt_f4_quits_even_while_playing.getBool() && isInPlayMode()) {
-        if(getSelectedBeatmap() != NULL) getSelectedBeatmap()->stop();
-
+        getSelectedBeatmap()->stop();
         return false;
     }
 
@@ -2073,7 +2103,7 @@ void Osu::onSkinChange(UString oldValue, UString newValue) {
 }
 
 void Osu::updateAnimationSpeed() {
-    if(getSkin() != NULL && getSelectedBeatmap() != NULL) {
+    if(getSkin() != NULL) {
         float speed = getAnimationSpeedMultiplier() / getSpeedMultiplier();
         getSkin()->setAnimationSpeed(speed >= 0.0f ? speed : 0.0f);
     }
@@ -2083,10 +2113,8 @@ void Osu::onAnimationSpeedChange(UString oldValue, UString newValue) { updateAni
 
 void Osu::onSpeedChange(UString oldValue, UString newValue) {
     float speed = newValue.toFloat();
-    if(getSelectedBeatmap() != NULL) {
-        getSelectedBeatmap()->setSpeed(speed >= 0.0f ? speed : getSpeedMultiplier());
-        updateAnimationSpeed();
-    }
+    getSelectedBeatmap()->setSpeed(speed >= 0.0f ? speed : getSpeedMultiplier());
+    updateAnimationSpeed();
 
     if(m_modSelector != nullptr) {
         int btn_state = (getModNC() || getModDC() || bancho.prefer_daycore) ? 1 : 0;
@@ -2121,9 +2149,7 @@ void Osu::onSpeedChange(UString oldValue, UString newValue) {
     }
 }
 
-void Osu::onPlayfieldChange(UString oldValue, UString newValue) {
-    if(getSelectedBeatmap() != NULL) getSelectedBeatmap()->onModUpdate();
-}
+void Osu::onPlayfieldChange(UString oldValue, UString newValue) { getSelectedBeatmap()->onModUpdate(); }
 
 void Osu::onUIScaleChange(UString oldValue, UString newValue) {
     const float oldVal = oldValue.toFloat();

+ 4 - 3
src/App/Osu/Osu.h

@@ -11,6 +11,7 @@
 #include "App.h"
 #include "BanchoNetworking.h"
 #include "MouseListener.h"
+#include "Score.h"
 
 class CWindowManager;
 
@@ -91,8 +92,7 @@ class Osu : public App, public MouseListener {
     virtual void onMinimized();
     virtual bool onShutdown();
 
-    void onBeforePlayStart();  // called just before OsuBeatmap->play()
-    void onPlayStart();        // called when a beatmap has successfully started playing
+    void onPlayStart();  // called when a beatmap has successfully started playing
     void onPlayEnd(bool quit = true,
                    bool aborted = false);  // called when a beatmap is finished playing (or the player quit)
 
@@ -182,6 +182,7 @@ class Osu : public App, public MouseListener {
     bool shouldFallBackToLegacySliderRenderer();  // certain mods or actions require OsuSliders to render dynamically
                                                   // (e.g. wobble or the CS override slider)
 
+    bool useMods(Score *score);
     void updateMods();
     void updateConfineCursor();
     void updateMouseSettings();
@@ -359,7 +360,7 @@ class Osu : public App, public MouseListener {
     CWindowManager *m_windowManager;
 
     // replay
-    ReplayExtraInfo replay_info;
+    Score replay_score;
 
     // custom
     bool m_bScheduleEndlessModNextBeatmap;

+ 123 - 18
src/App/Osu/OsuBeatmap.cpp

@@ -41,6 +41,7 @@
 #include "OsuModFPoSu.h"
 #include "OsuModSelector.h"
 #include "OsuNotificationOverlay.h"
+#include "OsuOptionsMenu.h"
 #include "OsuPauseMenu.h"
 #include "OsuReplay.h"
 #include "OsuRichPresence.h"
@@ -153,7 +154,7 @@ ConVar osu_skip_time("osu_skip_time", 5000.0f, FCVAR_CHEAT,
 ConVar osu_fail_time("osu_fail_time", 2.25f, FCVAR_NONE,
                      "Timeframe in s for the slowdown effect after failing, before the pause menu is shown");
 ConVar osu_notelock_type("osu_notelock_type", 2, FCVAR_CHEAT,
-                         "which notelock algorithm to use (0 = None, 1 = McOsu, 2 = osu!stable, 3 = osu!lazer 2020)");
+                         "which notelock algorithm to use (0 = None, 1 = neosu, 2 = osu!stable, 3 = osu!lazer 2020)");
 ConVar osu_notelock_stable_tolerance2b("osu_notelock_stable_tolerance2b", 3, FCVAR_CHEAT,
                                        "time tolerance in milliseconds to allow hitting simultaneous objects close "
                                        "together (e.g. circle at end of slider)");
@@ -698,7 +699,7 @@ void OsuBeatmap::selectDifficulty2(OsuDatabaseBeatmap *difficulty2) {
 
 void OsuBeatmap::deselect() {
     m_iContinueMusicPos = 0;
-
+    m_selectedDifficulty2 = NULL;
     unloadObjects();
 }
 
@@ -708,10 +709,7 @@ bool OsuBeatmap::watch(Score score, double start_percent) {
         return false;
     }
 
-    bancho.osu->replay_info.diff2_md5 = score.md5hash.hash;
-    bancho.osu->replay_info.mod_flags = score.modsLegacy;
-    bancho.osu->replay_info.username = UString(score.playerName.c_str());
-    bancho.osu->replay_info.player_id = score.player_id;
+    bancho.osu->replay_score = score;
 
     m_bIsPlaying = false;
     m_bIsPaused = false;
@@ -720,7 +718,9 @@ bool OsuBeatmap::watch(Score score, double start_percent) {
     unloadObjects();
 
     m_bIsWatchingReplay = true;
-    m_osu->onBeforePlayStart();
+
+    bancho.osu->getModSelector()->resetMods();
+    bancho.osu->useMods(&score);
 
     // Map failed to load
     if(!play()) {
@@ -733,7 +733,11 @@ bool OsuBeatmap::watch(Score score, double start_percent) {
     m_osu->m_songBrowser2->m_bHasSelectedAndIsPlaying = true;
     m_osu->m_songBrowser2->setVisible(false);
 
-    seekPercent(start_percent);
+    // Don't seek to 0%, since it feels really bad to start immediately
+    if(start_percent > 0.f) {
+        seekPercent(start_percent);
+    }
+
     m_osu->onPlayStart();
 
     return true;
@@ -742,6 +746,33 @@ bool OsuBeatmap::watch(Score score, double start_percent) {
 bool OsuBeatmap::play() {
     if(m_selectedDifficulty2 == NULL) return false;
 
+    engine->getSound()->play(m_osu->m_skin->getMenuHit());
+
+    m_osu->updateMods();
+
+    // mp hack
+    {
+        m_osu->m_mainMenu->setVisible(false);
+        m_osu->m_modSelector->setVisible(false);
+        m_osu->m_optionsMenu->setVisible(false);
+        m_osu->m_pauseMenu->setVisible(false);
+    }
+
+    // HACKHACK: stuck key quickfix
+    {
+        m_osu->m_bKeyboardKey1Down = false;
+        m_osu->m_bKeyboardKey12Down = false;
+        m_osu->m_bKeyboardKey2Down = false;
+        m_osu->m_bKeyboardKey22Down = false;
+        m_osu->m_bMouseKey1Down = false;
+        m_osu->m_bMouseKey2Down = false;
+
+        keyReleased1(false);
+        keyReleased1(true);
+        keyReleased2(false);
+        keyReleased2(true);
+    }
+
     static const int OSU_COORD_WIDTH = 512;
     static const int OSU_COORD_HEIGHT = 384;
     m_osu->flashlight_position = Vector2{OSU_COORD_WIDTH / 2, OSU_COORD_HEIGHT / 2};
@@ -1080,8 +1111,74 @@ void OsuBeatmap::cancelFailing() {
     if(getSkin()->getFailsound()->isPlaying()) engine->getSound()->stop(getSkin()->getFailsound());
 }
 
-void OsuBeatmap::setVolume(float volume) {
-    if(m_music != NULL) m_music->setVolume(volume);
+float OsuBeatmap::getIdealVolume() {
+    if(m_music == nullptr) return 1.f;
+
+    float volume = m_osu_volume_music_ref->getFloat();
+    if(!convar->getConVarByName("normalize_loudness")->getBool()) {
+        return volume;
+    }
+
+    static std::string last_song = "";
+    static float modifier = 1.f;
+
+    if(m_selectedDifficulty2->getFullSoundFilePath() == last_song) {
+        // We already did the calculation
+        return volume * modifier;
+    }
+
+    auto device_id = BASS_GetDevice();
+    BASS_SetDevice(0);
+
+    auto decoder = BASS_StreamCreateFile(false, m_selectedDifficulty2->getFullSoundFilePath().c_str(), 0, 0,
+                                         BASS_STREAM_DECODE | BASS_SAMPLE_FLOAT);
+    if(!decoder) {
+        debugLog("BASS_StreamCreateFile() returned error %d on file %s\n", BASS_ErrorGetCode(),
+                 m_selectedDifficulty2->getFullSoundFilePath().c_str());
+        BASS_SetDevice(device_id);
+        return volume;
+    }
+
+    float preview_point = m_selectedDifficulty2->getPreviewTime();
+    if(preview_point < 0) preview_point = 0;
+    const QWORD position = BASS_ChannelSeconds2Bytes(decoder, preview_point / 1000.0);
+    if(!BASS_ChannelSetPosition(decoder, position, BASS_POS_BYTE)) {
+        debugLog("BASS_ChannelSetPosition() returned error %d\n", BASS_ErrorGetCode());
+        BASS_ChannelFree(decoder);
+        BASS_SetDevice(device_id);
+        return volume;
+    }
+
+    auto loudness = BASS_Loudness_Start(decoder, MAKELONG(BASS_LOUDNESS_INTEGRATED, 3000), 0);
+    if(!loudness) {
+        debugLog("BASS_Loudness_Start() returned error %d\n", BASS_ErrorGetCode());
+        BASS_ChannelFree(decoder);
+        BASS_SetDevice(device_id);
+        return volume;
+    }
+
+    // Process 4 seconds of audio, should be enough for the loudness measurement
+    float buf[44100 * 4];
+    BASS_ChannelGetData(decoder, buf, sizeof(buf));
+
+    float level = -13.f;
+    BASS_Loudness_GetLevel(loudness, BASS_LOUDNESS_INTEGRATED, &level);
+    if(level == -HUGE_VAL) {
+        debugLog("No loudness information available (silent song?)\n");
+    }
+
+    BASS_Loudness_Stop(loudness);
+    BASS_ChannelFree(decoder);
+    BASS_SetDevice(device_id);
+
+    last_song = m_selectedDifficulty2->getFullSoundFilePath();
+    modifier = (level / -16.f);
+
+    if(Osu::debug->getBool()) {
+        debugLog("Volume set to %.2fx for this song\n", modifier);
+    }
+
+    return volume * modifier;
 }
 
 void OsuBeatmap::setSpeed(float speed) {
@@ -1095,7 +1192,7 @@ void OsuBeatmap::seekPercent(double percent) {
     m_fWaitTime = 0.0f;
 
     m_music->setPosition(percent);
-    m_music->setVolume(m_osu_volume_music_ref->getFloat());
+    m_music->setVolume(getIdealVolume());
     m_music->setSpeed(m_osu->getSpeedMultiplier());
 
     resetHitObjects(m_music->getPositionMS());
@@ -1579,7 +1676,7 @@ void OsuBeatmap::handlePreviewPlay() {
             if(m_music->getFrequency() < m_fMusicFrequencyBackup)  // player has died, reset frequency
                 m_music->setFrequency(m_fMusicFrequencyBackup);
 
-            // When McOsu is initialized, it starts playing a random song in the main menu.
+            // When neosu is initialized, it starts playing a random song in the main menu.
             // Users can set a convar to make it start at its preview point instead.
             // The next songs will start at the beginning regardless.
             static bool should_start_song_at_preview_point =
@@ -1597,7 +1694,7 @@ void OsuBeatmap::handlePreviewPlay() {
                                            : m_selectedDifficulty2->getPreviewTime());
             }
 
-            m_music->setVolume(m_osu_volume_music_ref->getFloat());
+            m_music->setVolume(getIdealVolume());
             m_music->setSpeed(m_osu->getSpeedMultiplier());
         }
     }
@@ -1628,7 +1725,7 @@ void OsuBeatmap::loadMusic(bool stream, bool prescan) {
             m_selectedDifficulty2->getFullSoundFilePath(), "OSU_BEATMAP_MUSIC", stream, false, false, false,
             m_bForceStreamPlayback &&
                 prescan);  // m_bForceStreamPlayback = prescan necessary! otherwise big mp3s will go out of sync
-        m_music->setVolume(m_osu_volume_music_ref->getFloat());
+        m_music->setVolume(getIdealVolume());
         m_fMusicFrequencyBackup = m_music->getFrequency();
         m_music->setSpeed(m_osu->getSpeedMultiplier());
     }
@@ -2363,7 +2460,7 @@ void OsuBeatmap::update2() {
 
                     engine->getSound()->play(m_music);
                     m_music->setPositionMS(0);
-                    m_music->setVolume(m_osu_volume_music_ref->getFloat());
+                    m_music->setVolume(getIdealVolume());
                     m_music->setSpeed(m_osu->getSpeedMultiplier());
 
                     // if we are quick restarting, jump just before the first hitobject (even if there is a long waiting
@@ -2375,7 +2472,8 @@ void OsuBeatmap::update2() {
 
                     m_bIsRestartScheduledQuick = false;
 
-                    // if there are calculations in there that need the hitobjects to be loaded, also applies speed/pitch
+                    // if there are calculations in there that need the hitobjects to be loaded, also applies
+                    // speed/pitch
                     onModUpdate(false, false);
                 }
             } else
@@ -2535,8 +2633,15 @@ void OsuBeatmap::update2() {
             long ms_since_last_frame = m_iCurMusicPosWithOffsets - current_frame.cur_music_pos;
             percent = (float)ms_since_last_frame / (float)next_frame.milliseconds_since_last_frame;
         }
+
         m_interpolatedMousePos =
             Vector2{lerp(current_frame.x, next_frame.x, percent), lerp(current_frame.y, next_frame.y, percent)};
+
+        if(osu_playfield_mirror_horizontal.getBool())
+            m_interpolatedMousePos.y = OsuGameRules::OSU_COORD_HEIGHT - m_interpolatedMousePos.y;
+        if(osu_playfield_mirror_vertical.getBool())
+            m_interpolatedMousePos.x = OsuGameRules::OSU_COORD_WIDTH - m_interpolatedMousePos.x;
+
         m_interpolatedMousePos *= OsuGameRules::getPlayfieldScaleFactor(m_osu);
         m_interpolatedMousePos += OsuGameRules::getPlayfieldOffset(m_osu);
     }
@@ -2644,7 +2749,7 @@ void OsuBeatmap::update2() {
             if(notelockType > 0) {
                 m_hitobjects[i]->setBlocked(blockNextNotes);
 
-                if(notelockType == 1)  // McOsu
+                if(notelockType == 1)  // neosu
                 {
                     // (nothing, handled in (2) block)
                 } else if(notelockType == 2)  // osu!stable
@@ -2731,7 +2836,7 @@ void OsuBeatmap::update2() {
 
                 blockNextNotes = false;
 
-                if(notelockType == 1)  // McOsu
+                if(notelockType == 1)  // neosu
                 {
                     // auto miss all previous unfinished hitobjects, always
                     // (can stop reverse iteration once we get to the first finished hitobject)

+ 1 - 1
src/App/Osu/OsuBeatmap.h

@@ -129,7 +129,7 @@ class OsuBeatmap {
     // music/sound
     void loadMusic(bool stream = true, bool prescan = false);
     void unloadMusic();
-    void setVolume(float volume);
+    float getIdealVolume();
     void setSpeed(float speed);
     void seekPercent(double percent);
     void seekPercentPlayable(double percent);

+ 24 - 2
src/App/Osu/OsuChangelog.cpp

@@ -36,10 +36,32 @@ OsuChangelog::OsuChangelog(Osu *osu) : OsuScreenBackable(osu) {
     CHANGELOG latest;
     latest.title =
         UString::format("%.2f (%s, %s)", convar->getConVarByName("osu_version")->getFloat(), __DATE__, __TIME__);
-    latest.changes.push_back("- Fixed replays not saving/submitting correctly");
-    latest.changes.push_back("- Fixed scores, collections and stars/pp cache not saving correctly");
+    latest.changes.push_back("- Renamed 'McOsu Multiplayer' to 'neosu'");
+    latest.changes.push_back("- Added option to normalize loudness across songs");
+    latest.changes.push_back("- Added server logo to main menu button");
+    latest.changes.push_back("- Added instant_replay_duration convar");
+    latest.changes.push_back("- Allowed singleplayer cheats when the server doesn't accept score submissions");
+    latest.changes.push_back("- Changed default instant replay key to F2 to avoid conflicts with mod selector");
+    latest.changes.push_back("- Fixed chat layout updating while chat was hidden");
+    latest.changes.push_back("- Fixed pause button not working after cancelling database load");
+    latest.changes.push_back("- Fixed level bar always being at 0%");
+    latest.changes.push_back("- Fixed experimental mods not getting set while watching replays");
+    latest.changes.push_back("- Fixed FPoSu camera not following cursor while watching replays");
+    latest.changes.push_back("- Fixed FPoSu mod not being included in score data");
+    latest.changes.push_back("- Fixed replay playback starting too fast");
+    latest.changes.push_back("- Fixed restarting SoundEngine not kicking the player out of play mode");
+    latest.changes.push_back("- Fixed ALT key not working on linux");
+    latest.changes.push_back("- Disabled score submission when mods are toggled mid-game");
+    latest.changes.push_back("- Removed support for the Nintendo Switch");
+    latest.changes.push_back("- Updated protocol version");
     changelogs.push_back(latest);
 
+    CHANGELOG v34_10;
+    v34_10.title = "34.10 (2024-04-14)";
+    v34_10.changes.push_back("- Fixed replays not saving/submitting correctly");
+    v34_10.changes.push_back("- Fixed scores, collections and stars/pp cache not saving correctly");
+    changelogs.push_back(v34_10);
+
     CHANGELOG v34_09;
     v34_09.title = "34.09 (2024-04-13)";
     v34_09.changes.push_back("- Added replay viewer");

+ 11 - 0
src/App/Osu/OsuChat.cpp

@@ -428,6 +428,12 @@ void OsuChat::removeChannel(UString channel_name) {
 }
 
 void OsuChat::updateLayout(Vector2 newResolution) {
+    // We don't want to update while the chat is hidden, to avoid lagspikes during gameplay
+    if(!m_bVisible) {
+        layout_update_scheduled = true;
+        return;
+    }
+
     // In the lobby and in multi rooms (e.g. when visibility is forced), don't
     // take the full horizontal width to allow for cleaner UI designs.
     if(isVisibilityForced()) {
@@ -453,6 +459,7 @@ void OsuChat::updateLayout(Vector2 newResolution) {
 
     updateButtonLayout(newResolution);
     updateButtonLayout(newResolution);  // idk
+    layout_update_scheduled = false;
 }
 
 void OsuChat::updateButtonLayout(Vector2 screen) {
@@ -609,6 +616,10 @@ CBaseUIContainer *OsuChat::setVisible(bool visible) {
         if(m_selected_channel != nullptr && !m_selected_channel->read) {
             mark_as_read(m_selected_channel);
         }
+
+        if(layout_update_scheduled) {
+            updateLayout(m_osu->getScreenSize());
+        }
     } else {
         anim->moveQuadOut(&m_fAnimation, 0.0f, 0.25f * m_fAnimation, true);
     }

+ 1 - 0
src/App/Osu/OsuChat.h

@@ -73,6 +73,7 @@ class OsuChat : public OsuScreen {
     float m_fAnimation = 0.f;
     bool user_wants_chat = false;
     bool visibility_was_forced = false;
+    bool layout_update_scheduled = false;
 
     const float input_box_height = 30.f;
     const float button_height = 26.f;

+ 11 - 12
src/App/Osu/OsuDatabase.cpp

@@ -32,10 +32,6 @@ ConVar osu_folder("osu_folder", "C:/Program Files (x86)/osu!/", FCVAR_NONE);
 
 ConVar osu_folder("osu_folder", "/osu!/", FCVAR_NONE);
 
-#elif defined __SWITCH__
-
-ConVar osu_folder("osu_folder", "sdmc:/switch/McOsu/", FCVAR_NONE);
-
 #else
 
 ConVar osu_folder("osu_folder", "", FCVAR_NONE);
@@ -49,7 +45,7 @@ ConVar osu_database_enabled("osu_database_enabled", true, FCVAR_NONE);
 ConVar osu_database_version("osu_database_version", OSU_VERSION_DATEONLY, FCVAR_NONE,
                             "maximum supported osu!.db version, above this will use fallback loader");
 ConVar osu_database_ignore_version_warnings("osu_database_ignore_version_warnings", false, FCVAR_NONE);
-ConVar osu_database_ignore_version("osu_database_ignore_version", false, FCVAR_NONE,
+ConVar osu_database_ignore_version("osu_database_ignore_version", true, FCVAR_NONE,
                                    "ignore upper version limit and force load the db file (may crash)");
 ConVar osu_database_stars_cache_enabled("osu_database_stars_cache_enabled", false, FCVAR_NONE);
 ConVar osu_scores_enabled("osu_scores_enabled", true, FCVAR_NONE);
@@ -517,7 +513,7 @@ int OsuDatabase::addScore(MD5Hash beatmapMD5Hash, Score score) {
 void OsuDatabase::addScoreRaw(const MD5Hash &beatmapMD5Hash, const Score &score) {
     m_scores[beatmapMD5Hash].push_back(score);
 
-    // cheap dynamic recalculations for mcosu scores
+    // cheap dynamic recalculations for neosu scores
     if(!score.isLegacyScore) {
         // as soon as we have >= 1 score with maxPossibleCombo info, all scores of that beatmap (even older ones without
         // the info) can get the 'perfect' flag set all scores >= 20180722 already have this populated during load, so
@@ -1792,7 +1788,7 @@ void OsuDatabase::loadScores() {
     // load custom scores
     // NOTE: custom scores are loaded before legacy scores because we want to be able to skip loading legacy scores
     // which were already previously imported at some point
-    int nb_mcosu_scores = 0;
+    int nb_neosu_scores = 0;
     size_t customScoresFileSize = 0;
     if(osu_scores_custom_enabled.getBool()) {
         const unsigned char hackIsImportedLegacyScoreFlag =
@@ -1897,7 +1893,7 @@ void OsuDatabase::loadScores() {
                             sc.md5hash = md5hash;
 
                             addScoreRaw(md5hash, sc);
-                            nb_mcosu_scores++;
+                            nb_neosu_scores++;
                         }
                     }
                 }
@@ -1913,11 +1909,13 @@ void OsuDatabase::loadScores() {
     int nb_peppy_scores = 0;
     if(osu_scores_legacy_enabled.getBool()) {
         std::string scoresPath = osu_folder.getString().toUtf8();
+        // XXX: name it something else than "scores.db" to prevent conflict with osu!stable/steam mcosu
         scoresPath.append("scores.db");
 
         Packet db = load_db(scoresPath);
+
         // HACKHACK: heuristic sanity check (some people have their osu!folder
-        // point directly to McOsu, which would break legacy score db loading
+        // point directly to neosu, which would break legacy score db loading
         // here since there is no magic number)
         if(db.size > 0 && db.size != customScoresFileSize) {
             const int dbVersion = read_int32(&db);
@@ -2046,7 +2044,8 @@ void OsuDatabase::loadScores() {
         free(db.memory);
     }
 
-    debugLog("Loaded %i scores from McOsu and %i scores from osu!stable.\n", nb_mcosu_scores, nb_peppy_scores);
+    // XXX: Also load steam mcosu scores?
+    debugLog("Loaded %i scores from neosu and %i scores from osu!stable.\n", nb_neosu_scores, nb_peppy_scores);
 
     if(m_scores.size() > 0) m_bScoresLoaded = true;
 }
@@ -2420,9 +2419,9 @@ void OsuDatabase::saveCollections() {
 
         // check how much we actually have to save
         // note that we are only saving non-legacy collections and entries (i.e. things which were added/deleted inside
-        // mcosu) reason being that it is more annoying to have osu!-side collections modifications be completely
+        // neosu) reason being that it is more annoying to have osu!-side collections modifications be completely
         // ignored (because we would make a full copy initially) if a collection or entry is deleted in osu!, then you
-        // would expect it to also be deleted here but, if a collection or entry is added in mcosu, then deleting the
+        // would expect it to also be deleted here but, if a collection or entry is added in neosu, then deleting the
         // collection in osu! should only delete all osu!-side entries
         int32_t numNonLegacyCollectionsOrCollectionsWithNonLegacyEntries = 0;
         for(size_t c = 0; c < m_collections.size(); c++) {

+ 50 - 1
src/App/Osu/OsuDatabaseBeatmap.cpp

@@ -1813,7 +1813,7 @@ void OsuDatabaseBeatmapStarCalculator::init() {
     // NOTE: this accesses runtime mods, so must be run sync (not async)
     // technically the getSelectedBeatmap() call here is a bit unsafe, since the beatmap could have changed already
     // between async and sync, but in that case we recalculate immediately after anyways
-    if(!m_bDead.load() && m_iErrorCode == 0 && m_diff2->m_osu->getSelectedBeatmap() != NULL)
+    if(!m_bDead.load() && m_iErrorCode == 0)
         m_pp = OsuDifficultyCalculator::calculatePPv2(m_diff2->m_osu, m_diff2->m_osu->getSelectedBeatmap(),
                                                       m_aimStars.load(), m_aimSliderFactor.load(), m_speedStars.load(),
                                                       m_speedNotes.load(), m_iNumObjects.load(), m_iNumCircles.load(),
@@ -1888,3 +1888,52 @@ void OsuDatabaseBeatmapStarCalculator::setBeatmapDifficulty(OsuDatabaseBeatmap *
     m_bRelax = relax;
     m_bTouchDevice = touchDevice;
 }
+
+std::string OsuDatabaseBeatmap::getFullSoundFilePath() {
+    // On linux, paths are case sensitive, so we retry different variations
+    if(env->getOS() != Environment::OS::OS_LINUX || env->fileExists(m_sFullSoundFilePath)) {
+        return m_sFullSoundFilePath;
+    }
+
+    // try uppercasing file extension
+    for(int s = m_sFullSoundFilePath.size(); s >= 0; s--) {
+        if(m_sFullSoundFilePath[s] == '.') {
+            for(int i = s + 1; i < m_sFullSoundFilePath.size(); i++) {
+                m_sFullSoundFilePath[i] = std::toupper(m_sFullSoundFilePath[i]);
+            }
+            break;
+        }
+    }
+    if(env->fileExists(m_sFullSoundFilePath)) {
+        return m_sFullSoundFilePath;
+    }
+
+    // try lowercasing filename, uppercasing file extension
+    bool foundFilenameStart = false;
+    for(int s = m_sFullSoundFilePath.size(); s >= 0; s--) {
+        if(foundFilenameStart) {
+            if(m_sFullSoundFilePath[s] == '/') break;
+            m_sFullSoundFilePath[s] = std::tolower(m_sFullSoundFilePath[s]);
+        }
+        if(m_sFullSoundFilePath[s] == '.') {
+            foundFilenameStart = true;
+        }
+    }
+    if(env->fileExists(m_sFullSoundFilePath)) {
+        return m_sFullSoundFilePath;
+    }
+
+    // try lowercasing everything
+    for(int s = m_sFullSoundFilePath.size(); s >= 0; s--) {
+        if(m_sFullSoundFilePath[s] == '/') {
+            break;
+        }
+        m_sFullSoundFilePath[s] = std::tolower(m_sFullSoundFilePath[s]);
+    }
+    if(env->fileExists(m_sFullSoundFilePath)) {
+        return m_sFullSoundFilePath;
+    }
+
+    // give up
+    return m_sFullSoundFilePath;
+}

+ 4 - 2
src/App/Osu/OsuDatabaseBeatmap.h

@@ -167,9 +167,9 @@ class OsuDatabaseBeatmap {
 
     inline const std::vector<TIMINGPOINT> &getTimingpoints() const { return m_timingpoints; }
 
-    // redundant data
+    std::string getFullSoundFilePath();
 
-    inline const std::string &getFullSoundFilePath() const { return m_sFullSoundFilePath; }
+    // redundant data
     inline const std::string &getFullBackgroundImageFilePath() const { return m_sFullBackgroundImageFilePath; }
 
     // precomputed data
@@ -192,6 +192,8 @@ class OsuDatabaseBeatmap {
     inline long getLocalOffset() const { return m_iLocalOffset; }
     inline long getOnlineOffset() const { return m_iOnlineOffset; }
 
+    bool do_not_store = false;
+
    private:
     // raw metadata
 

+ 5 - 5
src/App/Osu/OsuDifficultyCalculator.cpp

@@ -22,7 +22,7 @@ ConVar osu_stars_slider_curve_points_separation(
     "massively reduce curve accuracy for star calculations to save memory/performance");
 ConVar osu_stars_and_pp_lazer_relax_autopilot_nerf_disabled(
     "osu_stars_and_pp_lazer_relax_autopilot_nerf_disabled", true, FCVAR_NONE,
-    "generally disables all nerfs for relax/autopilot in both star/pp algorithms. since mcosu has always allowed "
+    "generally disables all nerfs for relax/autopilot in both star/pp algorithms. since neosu has always allowed "
     "these, the default is to not nerf them.");
 
 unsigned long long OsuDifficultyHitObject::sortHackCounter = 0;
@@ -254,7 +254,7 @@ double OsuDifficultyCalculator::calculateStarDiffForHitObjects(
                 // online ranking pp/stars now)
     float circleRadiusInOsuPixels =
         OsuGameRules::getRawHitCircleDiameter(clamp<float>(CS, 0.0f, 12.142f), applyBrokenGamefieldRoundingAllowance) /
-        2.0f;  // NOTE: clamped CS because McOsu allows CS > ~12.1429 (at which point the diameter becomes negative)
+        2.0f;  // NOTE: clamped CS because neosu allows CS > ~12.1429 (at which point the diameter becomes negative)
     const float hitWindow300 = 2.0f * OsuGameRules::getRawHitWindow300(OD) / speedMultiplier;
 
     // ******************************************************************************************************************************************
@@ -341,7 +341,7 @@ double OsuDifficultyCalculator::calculateStarDiffForHitObjects(
         double travelTime;      // precalc temp
 
         const std::vector<DiffObject>
-            &objects;  // NOTE: McOsu stores the first object in this array while lazer doesn't. newer lazer algorithms
+            &objects;  // NOTE: neosu stores the first object in this array while lazer doesn't. newer lazer algorithms
                        // require referencing objects "randomly", so we just keep the entire vector around.
         int prevObjectIndex;  // WARNING: this will be -1 for the first object (as the name implies), see note above
 
@@ -1260,7 +1260,7 @@ double OsuDifficultyCalculator::computeAimValue(const ScoreData &score,
     // hidden
     if(score.modsLegacy & ModFlags::Hidden)
         aimValue *= 1.0 + 0.04 * (std::max(12.0 - attributes.ApproachRate,
-                                           0.0));  // NOTE: clamped to 0 because McOsu allows AR > 12
+                                           0.0));  // NOTE: clamped to 0 because neosu allows AR > 12
 
     // "We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator."
     double estimateDifficultSliders = attributes.SliderCount * 0.15;
@@ -1320,7 +1320,7 @@ double OsuDifficultyCalculator::computeSpeedValue(const ScoreData &score, const
     // hidden
     if(score.modsLegacy & ModFlags::Hidden)
         speedValue *= 1.0 + 0.04 * (std::max(12.0 - attributes.ApproachRate,
-                                             0.0));  // NOTE: clamped to 0 because McOsu allows AR > 12
+                                             0.0));  // NOTE: clamped to 0 because neosu allows AR > 12
 
     // "Calculate accuracy assuming the worst case scenario"
     double relevantTotalDiff = score.totalHits - attributes.SpeedNoteCount;

+ 5 - 7
src/App/Osu/OsuHUD.cpp

@@ -268,8 +268,6 @@ OsuHUD::OsuHUD(Osu *osu) : OsuScreen(osu) {
     m_tempFont = engine->getResourceManager()->getFont("FONT_DEFAULT");
     m_cursorTrailShader = engine->getResourceManager()->loadShader("cursortrail.vsh", "cursortrail.fsh", "cursortrail");
     m_cursorTrail.reserve(osu_cursor_trail_max_size.getInt() * 2);
-    if(env->getOS() == Environment::OS::OS_HORIZON) m_cursorTrail2.reserve(osu_cursor_trail_max_size.getInt() * 2);
-
     m_cursorTrailVAO = engine->getResourceManager()->createVertexArrayObject(Graphics::PRIMITIVE::PRIMITIVE_QUADS,
                                                                              Graphics::USAGE_TYPE::USAGE_DYNAMIC);
 
@@ -798,9 +796,9 @@ void OsuHUD::drawFps(Graphics *g, McFont *font, float fps) {
     g->popTransform();
 
     // top
-    if(fps >= 200 || (env->getOS() == Environment::OS::OS_HORIZON && fps >= 50))
+    if(fps >= 200)
         g->setColor(0xffffffff);
-    else if(fps >= 120 || (env->getOS() == Environment::OS::OS_HORIZON && fps >= 40))
+    else if(fps >= 120)
         g->setColor(0xffdddd00);
     else {
         const float pulse = std::abs(std::sin(engine->getTime() * 4));
@@ -1535,10 +1533,10 @@ std::vector<SCORE_ENTRY> OsuHUD::getCurrentScores() {
 
         SCORE_ENTRY playerScoreEntry;
         if(m_osu->getModAuto() || (m_osu->getModAutopilot() && m_osu->getModRelax())) {
-            playerScoreEntry.name = "McOsu";
+            playerScoreEntry.name = "neosu";
         } else if(beatmap->m_bIsWatchingReplay) {
-            playerScoreEntry.name = m_osu->replay_info.username;
-            playerScoreEntry.player_id = m_osu->replay_info.player_id;
+            playerScoreEntry.name = m_osu->replay_score.playerName.c_str();
+            playerScoreEntry.player_id = m_osu->replay_score.player_id;
         } else {
             playerScoreEntry.name = m_name_ref->getString();
             playerScoreEntry.player_id = bancho.user_id;

+ 1 - 1
src/App/Osu/OsuKeyBindings.cpp

@@ -31,7 +31,7 @@ ConVar OsuKeyBindings::SEEK_TIME_FORWARD("osu_key_seek_time_forward", (int)KEY_R
 ConVar OsuKeyBindings::QUICK_RETRY("osu_key_quick_retry", (int)KEY_BACKSPACE, FCVAR_NONE);
 ConVar OsuKeyBindings::QUICK_SAVE("osu_key_quick_save", (int)KEY_F6, FCVAR_NONE);
 ConVar OsuKeyBindings::QUICK_LOAD("osu_key_quick_load", (int)KEY_F7, FCVAR_NONE);
-ConVar OsuKeyBindings::INSTANT_REPLAY("osu_key_instant_replay", (int)KEY_F1, FCVAR_NONE);
+ConVar OsuKeyBindings::INSTANT_REPLAY("osu_key_instant_replay", (int)KEY_F2, FCVAR_NONE);
 ConVar OsuKeyBindings::TOGGLE_CHAT("osu_key_toggle_chat", (int)KEY_F8, FCVAR_NONE);
 ConVar OsuKeyBindings::SAVE_SCREENSHOT("osu_key_save_screenshot", (int)KEY_F12, FCVAR_NONE);
 ConVar OsuKeyBindings::DISABLE_MOUSE_BUTTONS("osu_key_disable_mouse_buttons", (int)KEY_F10, FCVAR_NONE);

+ 120 - 341
src/App/Osu/OsuMainMenu.cpp

@@ -12,9 +12,9 @@
 #include "CBaseUIButton.h"
 #include "CBaseUIContainer.h"
 #include "ConVar.h"
+#include "Downloader.h"
 #include "Engine.h"
 #include "File.h"
-#include "HorizonSDLEnvironment.h"
 #include "Keyboard.h"
 #include "Mouse.h"
 #include "Osu.h"
@@ -29,9 +29,6 @@
 #include "OsuRichPresence.h"
 #include "OsuSkin.h"
 #include "OsuSkinImage.h"
-#include "OsuSlider.h"
-#include "OsuSliderCurves.h"
-#include "OsuSliderRenderer.h"
 #include "OsuSongBrowser.h"
 #include "OsuUIButton.h"
 #include "OsuUpdateHandler.h"
@@ -39,44 +36,10 @@
 #include "SoundEngine.h"
 #include "VertexArrayObject.h"
 
-#define MCOSU_VERSION_TEXT "Version"
-#define MCOSU_BANNER_TEXT ""
-UString OsuMainMenu::MCOSU_MAIN_BUTTON_TEXT = UString("McOsu");
-UString OsuMainMenu::MCOSU_MAIN_BUTTON_SUBTEXT = UString("Multiplayer Client");
-
-#define MCOSU_NEWVERSION_NOTIFICATION_TRIGGER_FILE "version.txt"
-
-static const char *s_sliderTextBeatmap =
-    "osu file format v14\r\n"
-    "\r\n"
-    "[General]\r\n"
-    "AudioFilename: nothing.mp3\r\n"
-    "\r\n"
-    "[Metadata]\r\n"
-    "Title:McOsu Slider Text\r\n"
-    "TitleUnicode:McOsu Slider Text\r\n"
-    "Artist:McKay\r\n"
-    "ArtistUnicode:McKay\r\n"
-    "Creator:McKay\r\n"
-    "Version:McOsu\r\n"
-    "\r\n"
-    "[Difficulty]\r\n"
-    "HPDrainRate:6\r\n"
-    "CircleSize:6\r\n"
-    "OverallDifficulty:5\r\n"
-    "ApproachRate:0\r\n"
-    "SliderMultiplier:10\r\n"
-    "SliderTickRate:1\r\n"
-    "\r\n"
-    "[TimingPoints]\r\n"
-    "1717.04358603418,1200,4,1,0,27,1,0\r\n"
-    "\r\n"
-    "[HitObjects]\r\n"
-    "-143,275,0,6,0,B|-143:115|-143:115|-79:179|-79:179|-20:115|-20:115|-20:275,1,498.750001415609\r\n"
-    "112,197,0,2,0,P|45:236|115:262,1,183.75000052154\r\n"
-    "263,111,0,2,0,P|282:272|251:111,1,520\r\n"
-    "480,179,0,2,0,B|288:179|480:243|544:275|384:275,1,262.500000745058\r\n"
-    "543,182,0,2,0,B|511:309|671:309|616:165,1,236.250000670552";
+UString OsuMainMenu::NEOSU_MAIN_BUTTON_TEXT = UString("neosu");
+UString OsuMainMenu::NEOSU_MAIN_BUTTON_SUBTEXT = UString("Multiplayer Client");
+
+#define NEOSU_NEWVERSION_NOTIFICATION_TRIGGER_FILE "version.txt"
 
 class OsuMainMenuCubeButton : public CBaseUIButton {
    public:
@@ -172,11 +135,6 @@ ConVar osu_toggle_preview_music("osu_toggle_preview_music");
 
 ConVar osu_draw_menu_background("osu_draw_menu_background", false, FCVAR_NONE);
 ConVar osu_main_menu_startup_anim_duration("osu_main_menu_startup_anim_duration", 0.25f, FCVAR_NONE);
-ConVar osu_main_menu_use_slider_text("osu_main_menu_use_slider_text", true, FCVAR_NONE);
-ConVar osu_main_menu_slider_text_alpha("osu_main_menu_slider_text_alpha", 1.0f, FCVAR_NONE);
-ConVar osu_main_menu_slider_text_scale("osu_main_menu_slider_text_scale", 1.0f, FCVAR_NONE);
-ConVar osu_main_menu_slider_text_offset_x("osu_main_menu_slider_text_offset_x", 15.0f, FCVAR_NONE);
-ConVar osu_main_menu_slider_text_offset_y("osu_main_menu_slider_text_offset_y", 0.0f, FCVAR_NONE);
 ConVar osu_main_menu_alpha("osu_main_menu_alpha", 1.0f, FCVAR_NONE);
 ConVar osu_main_menu_friend("osu_main_menu_friend", true, FCVAR_NONE);
 
@@ -192,13 +150,10 @@ ConVar *OsuMainMenu::m_osu_universal_offset_ref = NULL;
 ConVar *OsuMainMenu::m_osu_universal_offset_hardcoded_ref = NULL;
 ConVar *OsuMainMenu::m_osu_old_beatmap_offset_ref = NULL;
 ConVar *OsuMainMenu::m_osu_universal_offset_hardcoded_fallback_dsound_ref = NULL;
-ConVar *OsuMainMenu::m_osu_slider_border_feather_ref = NULL;
 ConVar *OsuMainMenu::m_osu_mod_random_ref = NULL;
 ConVar *OsuMainMenu::m_osu_songbrowser_background_fade_in_duration_ref = NULL;
 
 OsuMainMenu::OsuMainMenu(Osu *osu) : OsuScreen(osu) {
-    if(env->getOS() == Environment::OS::OS_HORIZON) MCOSU_MAIN_BUTTON_TEXT.append(" NX");
-
     if(m_osu_universal_offset_ref == NULL) m_osu_universal_offset_ref = convar->getConVarByName("osu_universal_offset");
     if(m_osu_universal_offset_hardcoded_ref == NULL)
         m_osu_universal_offset_hardcoded_ref = convar->getConVarByName("osu_universal_offset_hardcoded");
@@ -207,8 +162,6 @@ OsuMainMenu::OsuMainMenu(Osu *osu) : OsuScreen(osu) {
     if(m_osu_universal_offset_hardcoded_fallback_dsound_ref == NULL)
         m_osu_universal_offset_hardcoded_fallback_dsound_ref =
             convar->getConVarByName("osu_universal_offset_hardcoded_fallback_dsound");
-    if(m_osu_slider_border_feather_ref == NULL)
-        m_osu_slider_border_feather_ref = convar->getConVarByName("osu_slider_border_feather");
     if(m_osu_mod_random_ref == NULL) m_osu_mod_random_ref = convar->getConVarByName("osu_mod_random");
     if(m_osu_songbrowser_background_fade_in_duration_ref == NULL)
         m_osu_songbrowser_background_fade_in_duration_ref =
@@ -258,14 +211,19 @@ OsuMainMenu::OsuMainMenu(Osu *osu) : OsuScreen(osu) {
 
     m_fBackgroundFadeInTime = 0.0f;
 
+    const int baseDPI = 96;
+    const int newDPI = Osu::getUIScale(osu) * baseDPI;
+    m_titleFont =
+        engine->getResourceManager()->loadFont("SourceSansPro-Semibold.otf", "FONT_OSU_MAINMENU", 150, true, newDPI);
+
     // check if the user has never clicked the changelog for this update
     m_bDidUserUpdateFromOlderVersion = false;
     m_bDidUserUpdateFromOlderVersionLe3300 = false;
     m_bDidUserUpdateFromOlderVersionLe3303 = false;
     {
         m_bDrawVersionNotificationArrow = false;
-        if(env->fileExists(MCOSU_NEWVERSION_NOTIFICATION_TRIGGER_FILE)) {
-            File versionFile(MCOSU_NEWVERSION_NOTIFICATION_TRIGGER_FILE);
+        if(env->fileExists(NEOSU_NEWVERSION_NOTIFICATION_TRIGGER_FILE)) {
+            File versionFile(NEOSU_NEWVERSION_NOTIFICATION_TRIGGER_FILE);
             if(versionFile.canRead()) {
                 float version = std::stof(versionFile.readLine());
                 if(version < Osu::version->getFloat() - 0.0001f) m_bDrawVersionNotificationArrow = true;
@@ -307,51 +265,15 @@ OsuMainMenu::OsuMainMenu(Osu *osu) : OsuScreen(osu) {
     m_updateAvailableButton->setTextColor(0x22ffffff);
 
     m_versionButton = new CBaseUIButton(0, 0, 0, 0, "", "");
-    UString versionString = MCOSU_VERSION_TEXT;
-    versionString.append(" ");
-    versionString.append(UString::format("%.2f", Osu::version->getFloat()));
+    UString versionString = UString::format("Version %.2f", Osu::version->getFloat());
     m_versionButton->setText(versionString);
     m_versionButton->setDrawBackground(false);
     m_versionButton->setDrawFrame(false);
     m_versionButton->setClickCallback(fastdelegate::MakeDelegate(this, &OsuMainMenu::onVersionPressed));
     addBaseUIElement(m_versionButton);
-
-    m_mainMenuSliderTextDatabaseBeatmap = NULL;
-    m_mainMenuSliderTextBeatmapStandard = NULL;
-    m_fMainMenuSliderTextRawHitCircleDiameter = 1.0f;
-    if(osu_main_menu_use_slider_text.getBool()) {
-        m_mainMenuSliderTextDatabaseBeatmap = new OsuDatabaseBeatmap(m_osu, s_sliderTextBeatmap, "", true);
-        m_mainMenuSliderTextBeatmapStandard = new OsuBeatmap(m_osu);
-
-        // HACKHACK: temporary workaround to avoid this breaking the main menu logo text sliders (1/2)
-        const bool wasModRandomEnabled = m_osu_mod_random_ref->getBool();
-        if(wasModRandomEnabled) m_osu_mod_random_ref->setValue(0.0f);
-
-        OsuDatabaseBeatmap::LOAD_GAMEPLAY_RESULT result =
-            OsuDatabaseBeatmap::loadGameplay(m_mainMenuSliderTextDatabaseBeatmap, m_mainMenuSliderTextBeatmapStandard);
-        if(result.errorCode == 0) {
-            m_fMainMenuSliderTextRawHitCircleDiameter = m_mainMenuSliderTextBeatmapStandard->getRawHitcircleDiameter();
-            m_mainMenuSliderTextBeatmapHitObjects = result.hitobjects;
-            for(size_t i = 0; i < m_mainMenuSliderTextBeatmapHitObjects.size(); i++) {
-                OsuHitObject *hitObject = m_mainMenuSliderTextBeatmapHitObjects[i];
-                OsuSlider *sliderPointer = dynamic_cast<OsuSlider *>(hitObject);
-                if(sliderPointer != NULL)
-                    sliderPointer->rebuildVertexBuffer(
-                        true);  // we are working in osu coordinate space for this (no mods, just raw curve coords)
-            }
-        }
-
-        // HACKHACK: temporary workaround to avoid this breaking the main menu logo text sliders (2/2)
-        if(wasModRandomEnabled) m_osu_mod_random_ref->setValue(1.0f);
-    }
 }
 
 OsuMainMenu::~OsuMainMenu() {
-    for(auto slider : m_mainMenuSliderTextBeatmapHitObjects) {
-        delete slider;
-    }
-    m_mainMenuSliderTextBeatmapHitObjects.clear();
-
     anim->deleteExistingAnimation(&m_fUpdateButtonAnim);
 
     anim->deleteExistingAnimation(&m_fMainMenuAnimFriendEyeFollowX);
@@ -370,16 +292,38 @@ OsuMainMenu::~OsuMainMenu() {
 
     // if the user didn't click on the update notification during this session, quietly remove it so it's not annoying
     if(m_bWasCleanShutdown) writeVersionFile();
-
-    SAFE_DELETE(m_mainMenuSliderTextBeatmapStandard);
-    SAFE_DELETE(m_mainMenuSliderTextDatabaseBeatmap);
 }
 
 void OsuMainMenu::draw(Graphics *g) {
     if(!m_bVisible) return;
 
-    McFont *smallFont = m_osu->getSubTitleFont();
-    McFont *titleFont = m_osu->getTitleFont();
+    // load server icon
+    if(bancho.is_online() && bancho.server_icon_url.length() > 0 && bancho.server_icon == nullptr) {
+        std::stringstream ss;
+        ss << MCENGINE_DATA_DIR "avatars/" << bancho.endpoint.toUtf8();
+        auto icon_path = ss.str();
+        if(!env->directoryExists(icon_path)) {
+            env->createDirectory(icon_path);
+        }
+        icon_path.append("/server_icon");
+
+        float progress = -1.f;
+        std::vector<uint8_t> data;
+        int response_code;
+        download(bancho.server_icon_url.toUtf8(), &progress, data, &response_code);
+        if(progress == -1.f) bancho.server_icon_url = "";
+        if(!data.empty()) {
+            FILE *file = fopen(icon_path.c_str(), "wb");
+            if(file != NULL) {
+                fwrite(data.data(), data.size(), 1, file);
+                fflush(file);
+                fclose(file);
+            }
+
+            bancho.server_icon = engine->getResourceManager()->loadImageAbs(icon_path, icon_path);
+            bancho.server_icon_url = "";
+        }
+    }
 
     // menu-background
     if(osu_draw_menu_background.getBool()) {
@@ -401,35 +345,32 @@ void OsuMainMenu::draw(Graphics *g) {
     }
 
     // XXX: Should do fade transition between beatmap backgrounds when switching to next song
-    if(m_osu->getSelectedBeatmap() != NULL) {
-        float alpha = 1.0f;
-        if(m_osu_songbrowser_background_fade_in_duration_ref->getFloat() > 0.0f) {
-            // handle fadein trigger after handler is finished loading
-            const bool ready = m_osu->getSelectedBeatmap() != NULL &&
-                               m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL &&
-                               m_osu->getBackgroundImageHandler()->getLoadBackgroundImage(
-                                   m_osu->getSelectedBeatmap()->getSelectedDifficulty2()) != NULL &&
-                               m_osu->getBackgroundImageHandler()
-                                   ->getLoadBackgroundImage(m_osu->getSelectedBeatmap()->getSelectedDifficulty2())
-                                   ->isReady();
-
-            if(!ready)
-                m_fBackgroundFadeInTime = engine->getTime();
-            else if(m_fBackgroundFadeInTime > 0.0f && engine->getTime() > m_fBackgroundFadeInTime) {
-                alpha = clamp<float>((engine->getTime() - m_fBackgroundFadeInTime) /
-                                         m_osu_songbrowser_background_fade_in_duration_ref->getFloat(),
-                                     0.0f, 1.0f);
-                alpha = 1.0f - (1.0f - alpha) * (1.0f - alpha);
-            }
+    float alpha = 1.0f;
+    if(m_osu_songbrowser_background_fade_in_duration_ref->getFloat() > 0.0f) {
+        // handle fadein trigger after handler is finished loading
+        const bool ready = m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL &&
+                           m_osu->getBackgroundImageHandler()->getLoadBackgroundImage(
+                               m_osu->getSelectedBeatmap()->getSelectedDifficulty2()) != NULL &&
+                           m_osu->getBackgroundImageHandler()
+                               ->getLoadBackgroundImage(m_osu->getSelectedBeatmap()->getSelectedDifficulty2())
+                               ->isReady();
+
+        if(!ready)
+            m_fBackgroundFadeInTime = engine->getTime();
+        else if(m_fBackgroundFadeInTime > 0.0f && engine->getTime() > m_fBackgroundFadeInTime) {
+            alpha = clamp<float>((engine->getTime() - m_fBackgroundFadeInTime) /
+                                     m_osu_songbrowser_background_fade_in_duration_ref->getFloat(),
+                                 0.0f, 1.0f);
+            alpha = 1.0f - (1.0f - alpha) * (1.0f - alpha);
         }
-        OsuSongBrowser::drawSelectedBeatmapBackgroundImage(g, m_osu, alpha);
     }
+    OsuSongBrowser::drawSelectedBeatmapBackgroundImage(g, m_osu, alpha);
 
     // main button stuff
     bool haveTimingpoints = false;
     const float div = 1.25f;
     float pulse = 0.0f;
-    if(m_osu->getSelectedBeatmap() != NULL && m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL &&
+    if(m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL &&
        m_osu->getSelectedBeatmap()->getMusic() != NULL && m_osu->getSelectedBeatmap()->getMusic()->isPlaying()) {
         haveTimingpoints = true;
 
@@ -458,7 +399,6 @@ void OsuMainMenu::draw(Graphics *g) {
     } else
         pulse = (div - fmod(engine->getTime(), div)) / div;
 
-    // pulse *= pulse; // quadratic
     Vector2 size = m_vSize;
     const float pulseSub = 0.05f * pulse;
     size -= size * pulseSub;
@@ -467,60 +407,6 @@ void OsuMainMenu::draw(Graphics *g) {
     McRect mainButtonRect =
         McRect(m_vCenter.x - size.x / 2.0f - m_fCenterOffsetAnim, m_vCenter.y - size.y / 2.0f, size.x, size.y);
 
-    bool drawBanner = true;
-
-#ifdef __SWITCH__
-
-    drawBanner = ((HorizonSDLEnvironment *)env)->getMemAvailableMB() < 1024;
-
-#endif
-
-    if(drawBanner) {
-        UString bannerText = MCOSU_BANNER_TEXT;
-
-        if(osu_main_menu_banner_always_text.getString().length() > 0)
-            bannerText = osu_main_menu_banner_always_text.getString();
-        else if(m_bDidUserUpdateFromOlderVersion &&
-                osu_main_menu_banner_ifupdatedfromoldversion_text.getString().length() > 0)
-            bannerText = osu_main_menu_banner_ifupdatedfromoldversion_text.getString();
-        else if(m_bDidUserUpdateFromOlderVersionLe3300 &&
-                osu_main_menu_banner_ifupdatedfromoldversion_le3300_text.getString().length() > 0)
-            bannerText = osu_main_menu_banner_ifupdatedfromoldversion_le3300_text.getString();
-        else if(m_bDidUserUpdateFromOlderVersionLe3303 &&
-                osu_main_menu_banner_ifupdatedfromoldversion_le3303_text.getString().length() > 0)
-            bannerText = osu_main_menu_banner_ifupdatedfromoldversion_le3303_text.getString();
-
-        if(bannerText.length() > 0) {
-            McFont *bannerFont = m_osu->getSubTitleFont();
-            float bannerStringWidth = bannerFont->getStringWidth(bannerText);
-            int bannerDiff = 20;
-            int bannerMargin = 5;
-            int numBanners = (int)std::round(m_osu->getScreenWidth() / (bannerStringWidth + bannerDiff)) + 2;
-
-            g->setColor(0xffee7777);
-            g->pushTransform();
-            g->translate(1, 1);
-            for(int i = -1; i < numBanners; i++) {
-                g->pushTransform();
-                g->translate(i * bannerStringWidth + i * bannerDiff +
-                                 fmod(engine->getTime() * 30, bannerStringWidth + bannerDiff),
-                             bannerFont->getHeight() + bannerMargin);
-                g->drawString(bannerFont, bannerText);
-                g->popTransform();
-            }
-            g->popTransform();
-            g->setColor(0xff555555);
-            for(int i = -1; i < numBanners; i++) {
-                g->pushTransform();
-                g->translate(i * bannerStringWidth + i * bannerDiff +
-                                 fmod(engine->getTime() * 30, bannerStringWidth + bannerDiff),
-                             bannerFont->getHeight() + bannerMargin);
-                g->drawString(bannerFont, bannerText);
-                g->popTransform();
-            }
-        }
-    }
-
     // draw notification arrow for changelog (version button)
     if(m_bDrawVersionNotificationArrow) {
         float animation = fmod((float)(engine->getTime()) * 3.2f, 2.0f);
@@ -538,6 +424,7 @@ void OsuMainMenu::draw(Graphics *g) {
         g->setColor(0xffffffff);
         g->pushTransform();
         {
+            McFont *smallFont = m_osu->getSubTitleFont();
             g->translate(arrowPos.x - smallFont->getStringWidth(notificationText) / 2.0f,
                          (-offset * 2) * scale + arrowPos.y -
                              (m_osu->getSkin()->getPlayWarningArrow2()->getSizeBaseRaw().y * scale) / 1.5f,
@@ -571,52 +458,6 @@ void OsuMainMenu::draw(Graphics *g) {
             g->pop3DScene();
     }
 
-    // pre-render all slider text into the one single sliderFrameBuffer (up here before any of the 3dscene stuff)
-    if(osu_main_menu_use_slider_text.getBool() && m_mainMenuSliderTextBeatmapHitObjects.size() > 0) {
-        static std::vector<Vector2> alwaysPoints;
-        const float scale =
-            (mainButtonRect.getWidth() / 1100.0f) * osu_main_menu_slider_text_scale.getFloat() * m_fStartupAnim;
-        const Vector2 osuCoordsToCenteredAtOrigin =
-            Vector2(-OsuGameRules::OSU_COORD_WIDTH / 2 + osu_main_menu_slider_text_offset_x.getFloat(),
-                    -OsuGameRules::OSU_COORD_HEIGHT / 2 + osu_main_menu_slider_text_offset_y.getFloat()) *
-            scale;
-        const Vector2 screenCenterOffset =
-            Vector2(m_osu->getScreenWidth() / 2 - m_fCenterOffsetAnim, m_osu->getScreenHeight() / 2);
-        const Vector2 translation = osuCoordsToCenteredAtOrigin + screenCenterOffset;
-        const float from = 0.0f;
-        const float to = m_fStartupAnim2;
-
-        const float prevSliderBorderFeatherBackup = m_osu_slider_border_feather_ref->getFloat();
-        m_osu_slider_border_feather_ref->setValue(0.04f);  // heuristic to avoid aliasing
-        {
-            const size_t numHitObjects = m_mainMenuSliderTextBeatmapHitObjects.size();
-            for(size_t i = 0; i < numHitObjects; i++) {
-                OsuSlider *sliderPointer = dynamic_cast<OsuSlider *>(m_mainMenuSliderTextBeatmapHitObjects[i]);
-                if(sliderPointer != NULL) {
-                    alwaysPoints.clear();
-                    if(to < 1.0f)
-                        alwaysPoints.push_back((sliderPointer->getCurve()->pointAt(to)) * scale +
-                                               translation);  // alwaysPoints are always drawn in engine coords, so
-                                                              // compensate by applying scale and translation manually
-
-                    const bool doEnableRenderTarget = (i == 0);
-                    const bool doDisableRenderTarget = (i + 1 >= numHitObjects);
-                    const bool doDrawSliderFrameBufferToScreen = false;
-
-                    OsuSliderRenderer::draw(g, m_osu, sliderPointer->getVAO(), alwaysPoints, translation, scale,
-                                            (to < 1.0f ? m_fMainMenuSliderTextRawHitCircleDiameter * scale
-                                                       : OsuSliderRenderer::UNIT_CIRCLE_VAO_DIAMETER),
-                                            from, to,
-                                            m_osu->getSkin()->getComboColorForCounter(sliderPointer->getColorCounter(),
-                                                                                      sliderPointer->getColorOffset()),
-                                            1.0f, 1.0f, 0, doEnableRenderTarget, doDisableRenderTarget,
-                                            doDrawSliderFrameBufferToScreen);
-                }
-            }
-        }
-        m_osu_slider_border_feather_ref->setValue(prevSliderBorderFeatherBackup);
-    }
-
     // draw main button
     float inset = 0.0f;
     if((m_fMainMenuAnim > 0.0f && m_fMainMenuAnim != 1.0f) ||
@@ -922,107 +763,40 @@ void OsuMainMenu::draw(Graphics *g) {
     }
 
     // main text
-    const float fontScale = (1.0f - pulseSub + m_fSizeAddAnim) * m_fStartupAnim;
-    {
+    if(bancho.server_icon == nullptr || !bancho.server_icon->isReady()) {
+        const float title_width = m_titleFont->getStringWidth(NEOSU_MAIN_BUTTON_TEXT);
+        const float title_scale = mainButtonRect.getWidth() / title_width * 0.6f;
+        const float font_scale = title_scale * (1.0f - pulseSub + m_fSizeAddAnim) * m_fStartupAnim;
+
         float alpha = (1.0f - m_fMainMenuAnimFriendPercent) * (1.0f - m_fMainMenuAnimFriendPercent) *
                       (1.0f - m_fMainMenuAnimFriendPercent);
 
-        if(!osu_main_menu_use_slider_text.getBool() || m_mainMenuSliderTextBeatmapHitObjects.size() < 1) {
-            g->setColor(0xffffffff);
-            g->setAlpha(alpha);
-            g->pushTransform();
-            {
-                g->scale(fontScale, fontScale);
-                g->translate(m_vCenter.x - m_fCenterOffsetAnim -
-                                 (titleFont->getStringWidth(MCOSU_MAIN_BUTTON_TEXT) / 2.0f) * fontScale,
-                             m_vCenter.y + (titleFont->getHeight() * fontScale) / 2.25f, -1.0f);
-                g->drawString(titleFont, MCOSU_MAIN_BUTTON_TEXT);
-            }
-            g->popTransform();
-        } else {
-            alpha *= m_fStartupAnim * m_fStartupAnim * m_fStartupAnim * m_fStartupAnim;
-
-            m_osu->getSliderFrameBuffer()->setColor(
-                COLORf(alpha * osu_main_menu_slider_text_alpha.getFloat(), 1.0f, 1.0f, 1.0f));
-            m_osu->getSliderFrameBuffer()->drawRect(g, mainButtonRect.getX() + inset, mainButtonRect.getY() + inset,
-                                                    mainButtonRect.getWidth() - 2 * inset,
-                                                    mainButtonRect.getHeight() - 2 * inset);
-        }
-    }
-
-    // subtitle
-    UString subtext = MCOSU_MAIN_BUTTON_SUBTEXT;
-    if(bancho.is_online()) {
-        subtext = bancho.endpoint;
-    }
-
-    if(subtext.length() > 0) {
-        float invertedPulse = 1.0f - pulse;
-
-        if(haveTimingpoints)
-            g->setColor(COLORf(1.0f, 0.10f + 0.15f * invertedPulse, 0.10f + 0.15f * invertedPulse,
-                               0.10f + 0.15f * invertedPulse));
-        else
-            g->setColor(0xff444444);
-
-        g->setAlpha((1.0f - m_fMainMenuAnimFriendPercent) * (1.0f - m_fMainMenuAnimFriendPercent) *
-                    (1.0f - m_fMainMenuAnimFriendPercent) *
-                    (1.0f - (1.0f - m_fStartupAnim2) * (1.0f - m_fStartupAnim2)) * osu_main_menu_alpha.getFloat());
-
+        g->setColor(0xffffffff);
+        g->setAlpha(alpha);
         g->pushTransform();
         {
-            g->scale(fontScale, fontScale);
-            g->translate(
-                m_vCenter.x - m_fCenterOffsetAnim - (smallFont->getStringWidth(subtext) / 2.0f) * fontScale,
-                m_vCenter.y + (mainButtonRect.getHeight() / 2.0f) / 2.0f + (smallFont->getHeight() * fontScale) / 2.0f,
-                -1.0f);
-            g->drawString(smallFont, subtext);
+            g->scale(font_scale, font_scale);
+            g->translate(m_vCenter.x - m_fCenterOffsetAnim -
+                             (m_titleFont->getStringWidth(NEOSU_MAIN_BUTTON_TEXT) / 2.0f) * font_scale,
+                         m_vCenter.y + (m_titleFont->getHeight() * font_scale) / 2.25f, -1.0f);
+            g->drawString(m_titleFont, NEOSU_MAIN_BUTTON_TEXT);
         }
         g->popTransform();
+    } else {
+        float alpha = (1.0f - m_fMainMenuAnimFriendPercent) * (1.0f - m_fMainMenuAnimFriendPercent) *
+                      (1.0f - m_fMainMenuAnimFriendPercent);
 
-        if(bancho.is_online()) {
-            UString score_submission("Scores: ");
-            UString submission_status(bancho.submit_scores() ? "ENABLED" : "DISABLED");
-
-            auto full_text = score_submission;
-            full_text.append(submission_status);
-            float x_start = m_vCenter.x - m_fCenterOffsetAnim - smallFont->getStringWidth(full_text) / 2.f * fontScale;
-
-            g->pushTransform();
-            {
-                if(haveTimingpoints)
-                    g->setColor(COLORf(1.0f, 0.10f + 0.15f * invertedPulse, 0.10f + 0.15f * invertedPulse,
-                                       0.10f + 0.15f * invertedPulse));
-                else
-                    g->setColor(0xff444444);
-
-                g->scale(fontScale, fontScale);
-                g->translate(
-                    x_start,
-                    m_vCenter.y + (mainButtonRect.getHeight() / 2.0f) / 2.0f + 2 * (smallFont->getHeight() * fontScale),
-                    -1.0f);
-                g->drawString(smallFont, score_submission);
-            }
-            g->popTransform();
+        float xscale = mainButtonRect.getWidth() / bancho.server_icon->getWidth();
+        float yscale = mainButtonRect.getHeight() / bancho.server_icon->getHeight();
+        float scale = std::min(xscale, yscale) * 0.8f;
 
-            g->pushTransform();
-            {
-                if(haveTimingpoints)
-                    g->setColor(COLORf(1.0f, (bancho.submit_scores() ? 0.1 : 0.3) + 0.15f * invertedPulse,
-                                       (bancho.submit_scores() ? 0.3 : 0.1) + 0.15f * invertedPulse,
-                                       0.10f + 0.15f * invertedPulse));
-                else
-                    g->setColor(bancho.submit_scores() ? 0xff44bb44 : 0xffbb4444);
-
-                g->scale(fontScale, fontScale);
-                g->translate(
-                    x_start + smallFont->getStringWidth(score_submission) * fontScale,
-                    m_vCenter.y + (mainButtonRect.getHeight() / 2.0f) / 2.0f + 2 * (smallFont->getHeight() * fontScale),
-                    -1.0f);
-                g->drawString(smallFont, submission_status);
-            }
-            g->popTransform();
-        }
+        g->pushTransform();
+        g->setColor(0xffffffff);
+        g->setAlpha(alpha);
+        g->scale(scale, scale);
+        g->translate(m_vCenter.x - m_fCenterOffsetAnim, m_vCenter.y);
+        g->drawImage(bancho.server_icon);
+        g->popTransform();
     }
 
     if((m_fMainMenuAnim > 0.0f && m_fMainMenuAnim != 1.0f) ||
@@ -1252,7 +1026,7 @@ void OsuMainMenu::mouse_update(bool *propagate_clicks) {
                 if(m_updateAvailableButton->getText().find("ready") != -1)
                     m_updateAvailableButton->setText("Click here to restart now!");
                 else
-                    m_updateAvailableButton->setText("A new version of McOsu is ready!");
+                    m_updateAvailableButton->setText("A new version of neosu is ready!");
             }
             break;
         case OsuUpdateHandler::STATUS::STATUS_ERROR:
@@ -1268,35 +1042,37 @@ void OsuMainMenu::mouse_update(bool *propagate_clicks) {
     }
 
     // Update pause button and shuffle songs
-    if(m_osu->getSelectedBeatmap() != NULL) {
-        if(m_osu->getSelectedBeatmap()->isPreviewMusicPlaying()) {
-            m_osu->getSelectedBeatmap()->getMusic()->setLoop(false);
+    m_pauseButton->setPaused(true);
+    auto music = m_osu->getSelectedBeatmap()->getMusic();
+    if(music == nullptr) {
+        selectRandomBeatmap();
+    } else {
+        if(music->isFinished()) {
+            selectRandomBeatmap();
+        }
+
+        if(music->isPlaying()) {
             m_pauseButton->setPaused(false);
-        } else {
-            if(shuffling) {
-                selectRandomBeatmap();
-            } else {
-                m_pauseButton->setPaused(true);
-            }
+
+            // NOTE: We set this every frame, because music loading isn't instant
+            music->setLoop(false);
         }
     }
 }
 
 void OsuMainMenu::selectRandomBeatmap() {
-    shuffling = true;
-
-    if(m_osu->getSongBrowser()->getDatabase()->isFinished()) {
+    auto sb = m_osu->getSongBrowser();
+    if(sb->getDatabase()->isFinished() && !sb->m_beatmaps.empty()) {
         m_osu->getSongBrowser()->selectRandomBeatmap();
     } else {
         // Database is not loaded yet, load a random map and select it
-        // XXX: Also pick from McOsu maps/ directory
+        // XXX: Also pick from neosu maps/ directory
         auto songs_folder = m_osu->getSongBrowser()->getDatabase()->getOsuSongsFolder();
         auto mapset_folders = env->getFoldersInFolder(songs_folder);
         auto nb_mapsets = mapset_folders.size();
         if(nb_mapsets == 0) return;
 
-        auto cur_beatmap = m_osu->getSongBrowser()->getSelectedBeatmap();
-        if(cur_beatmap) cur_beatmap->deselect();
+        m_osu->getSongBrowser()->getSelectedBeatmap()->deselect();
         SAFE_DELETE(preloaded_beatmap);
 
         for(int i = 0; i < 10; i++) {
@@ -1317,6 +1093,7 @@ void OsuMainMenu::selectRandomBeatmap() {
             }
 
             preloaded_beatmap = beatmap_diffs[rand() % beatmap_diffs.size()];
+            preloaded_beatmap->do_not_store = true;
             m_osu->getSongBrowser()->onDifficultySelected(preloaded_beatmap, false);
             return;
         }
@@ -1371,23 +1148,23 @@ void OsuMainMenu::onResolutionChange(Vector2 newResolution) {
 CBaseUIContainer *OsuMainMenu::setVisible(bool visible) {
     m_bVisible = visible;
 
-    if(!m_bVisible) {
-        setMenuElementsVisible(false, false);
-    } else {
+    if(visible) {
         OsuRichPresence::onMainMenu(m_osu);
 
         updateLayout();
 
         m_fMainMenuAnimDuration = 15.0f;
         m_fMainMenuAnimTime = engine->getTime() + m_fMainMenuAnimDuration;
-    }
 
-    if(visible && m_bStartupAnim) {
-        m_bStartupAnim = false;
-        anim->moveQuadOut(&m_fStartupAnim, 1.0f, osu_main_menu_startup_anim_duration.getFloat(),
-                          (float)engine->getTimeReal());
-        anim->moveQuartOut(&m_fStartupAnim2, 1.0f, osu_main_menu_startup_anim_duration.getFloat() * 6.0f,
-                           (float)engine->getTimeReal() + osu_main_menu_startup_anim_duration.getFloat() * 0.5f);
+        if(m_bStartupAnim) {
+            m_bStartupAnim = false;
+            anim->moveQuadOut(&m_fStartupAnim, 1.0f, osu_main_menu_startup_anim_duration.getFloat(),
+                              (float)engine->getTimeReal());
+            anim->moveQuartOut(&m_fStartupAnim2, 1.0f, osu_main_menu_startup_anim_duration.getFloat() * 6.0f,
+                               (float)engine->getTimeReal() + osu_main_menu_startup_anim_duration.getFloat() * 0.5f);
+        }
+    } else {
+        setMenuElementsVisible(false, false);
     }
 
     return this;
@@ -1541,7 +1318,7 @@ void OsuMainMenu::setMenuElementsVisible(bool visible, bool animate) {
 
 void OsuMainMenu::writeVersionFile() {
     // remember, don't show the notification arrow until the version changes again
-    std::ofstream versionFile(MCOSU_NEWVERSION_NOTIFICATION_TRIGGER_FILE, std::ios::out | std::ios::trunc);
+    std::ofstream versionFile(NEOSU_NEWVERSION_NOTIFICATION_TRIGGER_FILE, std::ios::out | std::ios::trunc);
     if(versionFile.good()) versionFile << Osu::version->getFloat();
 }
 
@@ -1632,11 +1409,13 @@ void OsuMainMenu::onExitButtonPressed() {
 void OsuMainMenu::onPausePressed() {
     if(m_osu->getInstanceID() > 1) return;
 
-    if(m_osu->getSelectedBeatmap() != NULL) {
+    if(m_osu->getSelectedBeatmap()->isPreviewMusicPlaying()) {
         m_osu->getSelectedBeatmap()->pausePreviewMusic();
-        shuffling = false;
     } else {
-        shuffling = true;
+        auto music = m_osu->getSelectedBeatmap()->getMusic();
+        if(music != nullptr) {
+            engine->getSound()->play(music);
+        }
     }
 }
 

+ 5 - 9
src/App/Osu/OsuMainMenu.h

@@ -12,6 +12,7 @@
 #include "MouseListener.h"
 #include "OsuScreen.h"
 
+class McFont;
 class Osu;
 
 class OsuBeatmap;
@@ -43,8 +44,8 @@ class OsuMainMenuPauseButton : public CBaseUIButton {
 
 class OsuMainMenu : public OsuScreen, public MouseListener {
    public:
-    static UString MCOSU_MAIN_BUTTON_TEXT;
-    static UString MCOSU_MAIN_BUTTON_SUBTEXT;
+    static UString NEOSU_MAIN_BUTTON_TEXT;
+    static UString NEOSU_MAIN_BUTTON_SUBTEXT;
 
     friend class OsuMainMenuCubeButton;
     friend class OsuMainMenuButton;
@@ -85,7 +86,6 @@ class OsuMainMenu : public OsuScreen, public MouseListener {
     static ConVar *m_osu_universal_offset_hardcoded_ref;
     static ConVar *m_osu_old_beatmap_offset_ref;
     static ConVar *m_osu_universal_offset_hardcoded_fallback_dsound_ref;
-    static ConVar *m_osu_slider_border_feather_ref;
     static ConVar *m_osu_mod_random_ref;
     static ConVar *m_osu_songbrowser_background_fade_in_duration_ref;
 
@@ -163,14 +163,10 @@ class OsuMainMenu : public OsuScreen, public MouseListener {
     bool m_bStartupAnim;
     float m_fStartupAnim;
     float m_fStartupAnim2;
-
-    OsuDatabaseBeatmap *m_mainMenuSliderTextDatabaseBeatmap;
-    OsuBeatmap *m_mainMenuSliderTextBeatmapStandard;
-    std::vector<OsuHitObject *> m_mainMenuSliderTextBeatmapHitObjects;
-    float m_fMainMenuSliderTextRawHitCircleDiameter;
-
     float m_fPrevShuffleTime;
     float m_fBackgroundFadeInTime;
+
+    McFont *m_titleFont;
 };
 
 #endif

+ 7 - 9
src/App/Osu/OsuModFPoSu.cpp

@@ -273,7 +273,7 @@ void OsuModFPoSu::update() {
                            .getFloat());  // NOTE: slightly move back by default to avoid aliasing with background cube
 
         if(fposu_mod_strafing.getBool()) {
-            if(m_osu->isInPlayMode() && m_osu->getSelectedBeatmap() != NULL) {
+            if(m_osu->isInPlayMode()) {
                 const long curMusicPos = m_osu->getSelectedBeatmap()->getCurMusicPos();
 
                 const float speedMultiplierCompensation = 1.0f / m_osu->getSelectedBeatmap()->getSpeedMultiplier();
@@ -293,7 +293,8 @@ void OsuModFPoSu::update() {
         }
     }
 
-    const bool isAutoCursor = (m_osu->getModAuto() || m_osu->getModAutopilot());
+    const bool isAutoCursor =
+        (m_osu->getModAuto() || m_osu->getModAutopilot() || m_osu->getSelectedBeatmap()->m_bIsWatchingReplay);
 
     m_bCrosshairIntersectsScreen = true;
     if(!fposu_absolute_mode.getBool() && !isAutoCursor &&
@@ -340,16 +341,13 @@ void OsuModFPoSu::update() {
         }
     } else {
         // absolute mouse position mode (or auto)
-
-        m_bCrosshairIntersectsScreen = true;
-
-        // auto support, because it looks pretty cool
         Vector2 mousePos = engine->getMouse()->getPos();
-        if(isAutoCursor && m_osu->isInPlayMode() && m_osu->getSelectedBeatmap() != NULL) {
-            OsuBeatmap *beatmap = m_osu->getSelectedBeatmap();
-            if(beatmap != NULL && !beatmap->isPaused()) mousePos = beatmap->getCursorPos();
+        auto beatmap = m_osu->getSelectedBeatmap();
+        if(isAutoCursor && !beatmap->isPaused()) {
+            mousePos = beatmap->getCursorPos();
         }
 
+        m_bCrosshairIntersectsScreen = true;
         m_camera->lookAt(calculateUnProjectedVector(mousePos));
     }
 }

+ 27 - 48
src/App/Osu/OsuModSelector.cpp

@@ -207,18 +207,15 @@ OsuModSelector::OsuModSelector(Osu *osu) : OsuScreen(osu) {
     m_ARLock = overrideAR.lock;
     m_ODLock = overrideOD.lock;
 
-    if(env->getOS() != Environment::OS::OS_HORIZON) {
-        OVERRIDE_SLIDER overrideSpeed =
-            addOverrideSlider("Speed/BPM Multiplier", "x", convar->getConVarByName("osu_speed_override"), 0.0f, 2.5f);
+    OVERRIDE_SLIDER overrideSpeed =
+        addOverrideSlider("Speed/BPM Multiplier", "x", convar->getConVarByName("osu_speed_override"), 0.0f, 2.5f);
 
-        overrideSpeed.slider->setChangeCallback(
-            fastdelegate::MakeDelegate(this, &OsuModSelector::onOverrideSliderChange));
-        // overrideSpeed.slider->setValue(-1.0f, false);
-        overrideSpeed.slider->setAnimated(false);  // same quick fix as above
-        overrideSpeed.slider->setLiveUpdate(false);
+    overrideSpeed.slider->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuModSelector::onOverrideSliderChange));
+    // overrideSpeed.slider->setValue(-1.0f, false);
+    overrideSpeed.slider->setAnimated(false);  // same quick fix as above
+    overrideSpeed.slider->setLiveUpdate(false);
 
-        m_speedSlider = overrideSpeed.slider;
-    }
+    m_speedSlider = overrideSpeed.slider;
 
     // build experimental buttons
     addExperimentalLabel(" Experimental Mods (!)");
@@ -236,11 +233,8 @@ OsuModSelector::OsuModSelector(Osu *osu) : OsuScreen(osu) {
                             "Customize the approach circle animation.\nSee osu_mod_approach_different_style.\nSee "
                             "osu_mod_approach_different_initial_size.",
                             convar->getConVarByName("osu_mod_approach_different"));
-
-    if(env->getOS() != Environment::OS::OS_HORIZON)
-        addExperimentalCheckbox("Timewarp", "Speed increases from 100% to 150% over the course of the beatmap.",
-                                convar->getConVarByName("osu_mod_timewarp"));
-
+    addExperimentalCheckbox("Timewarp", "Speed increases from 100% to 150% over the course of the beatmap.",
+                            convar->getConVarByName("osu_mod_timewarp"));
     addExperimentalCheckbox("AR Timewarp", "Approach rate decreases from 100% to 50% over the course of the beatmap.",
                             convar->getConVarByName("osu_mod_artimewarp"));
     addExperimentalCheckbox("Minimize", "Circle size decreases from 100% to 50% over the course of the beatmap.",
@@ -400,11 +394,6 @@ void OsuModSelector::updateButtons(bool initial) {
     getModButtonOnGrid(4, 0)->setAvailable(true);
     getModButtonOnGrid(5, 2)->setAvailable(true);
 
-    if(env->getOS() == Environment::OS::OS_HORIZON) {
-        getModButtonOnGrid(2, 1)->setAvailable(false);
-        getModButtonOnGrid(2, 0)->setAvailable(false);
-    }
-
     if(bancho.is_in_a_multi_room()) {
         if(bancho.room.freemods && !bancho.room.is_host()) {
             getModButtonOnGrid(2, 0)->setAvailable(false);  // Disable DC/HT
@@ -642,7 +631,7 @@ void OsuModSelector::mouse_update(bool *propagate_clicks) {
         }
 
         // some experimental mod tooltip overrides
-        if(m_experimentalModRandomCheckbox->isChecked() && m_osu->getSelectedBeatmap() != NULL)
+        if(m_experimentalModRandomCheckbox->isChecked())
             m_experimentalModRandomCheckbox->setTooltipText(
                 UString::format("Seed = %i", m_osu->getSelectedBeatmap()->getRandomSeed()));
 
@@ -683,9 +672,7 @@ void OsuModSelector::mouse_update(bool *propagate_clicks) {
             m_bWaitForHPChangeFinished = false;
 
             {
-                if(m_osu->isInPlayMode() && m_osu->getSelectedBeatmap() != NULL)
-                    m_osu->getSelectedBeatmap()->onModUpdate();
-
+                if(m_osu->isInPlayMode()) m_osu->getSelectedBeatmap()->onModUpdate();
                 m_osu->getSongBrowser()->recalculateStarsForSelectedBeatmap(true);
             }
         }
@@ -699,9 +686,7 @@ void OsuModSelector::mouse_update(bool *propagate_clicks) {
             m_bWaitForHPChangeFinished = false;
 
             {
-                if(m_osu->isInPlayMode() && m_osu->getSelectedBeatmap() != NULL)
-                    m_osu->getSelectedBeatmap()->onModUpdate();
-
+                if(m_osu->isInPlayMode()) m_osu->getSelectedBeatmap()->onModUpdate();
                 m_osu->getSongBrowser()->recalculateStarsForSelectedBeatmap(true);
             }
         }
@@ -713,7 +698,7 @@ void OsuModSelector::mouse_update(bool *propagate_clicks) {
             m_bWaitForCSChangeFinished = false;
             m_bWaitForSpeedChangeFinished = false;
             m_bWaitForHPChangeFinished = false;
-            if(m_osu->isInPlayMode() && m_osu->getSelectedBeatmap() != NULL) m_osu->getSelectedBeatmap()->onModUpdate();
+            if(m_osu->isInPlayMode()) m_osu->getSelectedBeatmap()->onModUpdate();
         }
     }
 }
@@ -1214,6 +1199,8 @@ void OsuModSelector::resetModsUserInitiated() {
 }
 
 void OsuModSelector::resetMods() {
+    convar->getConVarByName("osu_mod_fposu")->setValue(false);
+
     for(int i = 0; i < m_overrideSliders.size(); i++) {
         if(m_overrideSliders[i].lock != NULL) m_overrideSliders[i].lock->setChecked(false);
     }
@@ -1319,8 +1306,7 @@ void OsuModSelector::onOverrideSliderChange(CBaseUISlider *slider) {
                 m_overrideSliders[i].label->setWidthToContent(0);
 
                 // HACKHACK: dirty
-                if(m_osu->getSelectedBeatmap() != NULL &&
-                   m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL) {
+                if(m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL) {
                     if(m_overrideSliders[i].label->getName().find("BPM") != -1) {
                         // reset AR and OD override sliders if the bpm slider was reset
                         if(!m_ARLock->isChecked()) m_ARSlider->setValue(0.0f, false);
@@ -1341,8 +1327,7 @@ void OsuModSelector::onOverrideSliderChange(CBaseUISlider *slider) {
                 }
 
                 // HACKHACK: dirty
-                if(m_osu->getSelectedBeatmap() != NULL &&
-                   m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL) {
+                if(m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL) {
                     if(m_overrideSliders[i].label->getName().find("BPM") != -1) {
                         // HACKHACK: force BPM slider to have a min value of 0.05 instead of 0 (because that's the
                         // minimum for BASS) note that the BPM slider is just a 'fake' slider, it directly controls the
@@ -1394,18 +1379,14 @@ void OsuModSelector::onOverrideSliderLockChange(CBaseUICheckbox *checkbox) {
             // usability: if we just got locked, and the override slider value is < 0.0f (disabled), then set override
             // to current value
             if(locked && !wasLocked) {
-                if(m_osu->getSelectedBeatmap() != NULL) {
-                    if(checkbox == m_ARLock) {
-                        if(m_ARSlider->getFloat() < 1.0f)
-                            m_ARSlider->setValue(
-                                m_osu->getSelectedBeatmap()->getRawAR() + 1.0f,
-                                false);  // '+1' to compensate for turn-off area of the override sliders
-                    } else if(checkbox == m_ODLock) {
-                        if(m_ODSlider->getFloat() < 1.0f)
-                            m_ODSlider->setValue(
-                                m_osu->getSelectedBeatmap()->getRawOD() + 1.0f,
-                                false);  // '+1' to compensate for turn-off area of the override sliders
-                    }
+                if(checkbox == m_ARLock) {
+                    if(m_ARSlider->getFloat() < 1.0f)
+                        m_ARSlider->setValue(m_osu->getSelectedBeatmap()->getRawAR() + 1.0f,
+                                             false);  // '+1' to compensate for turn-off area of the override sliders
+                } else if(checkbox == m_ODLock) {
+                    if(m_ODSlider->getFloat() < 1.0f)
+                        m_ODSlider->setValue(m_osu->getSelectedBeatmap()->getRawOD() + 1.0f,
+                                             false);  // '+1' to compensate for turn-off area of the override sliders
                 }
             }
 
@@ -1457,7 +1438,7 @@ UString OsuModSelector::getOverrideSliderLabelText(OsuModSelector::OVERRIDE_SLID
     float convarValue = s.cvar->getFloat();
 
     UString newLabelText = s.label->getName();
-    if(m_osu->getSelectedBeatmap() != NULL && m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL) {
+    if(m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL) {
         // used for compensating speed changing experimental mods (which cause m_osu->getSpeedMultiplier() !=
         // beatmap->getSpeedMultiplier()) to keep the AR/OD display correct
         const float speedMultiplierLive =
@@ -1575,9 +1556,7 @@ void OsuModSelector::onCheckboxChange(CBaseUICheckbox *checkbox) {
 
             // force mod update
             {
-                if(m_osu->isInPlayMode() && m_osu->getSelectedBeatmap() != NULL)
-                    m_osu->getSelectedBeatmap()->onModUpdate();
-
+                if(m_osu->isInPlayMode()) m_osu->getSelectedBeatmap()->onModUpdate();
                 m_osu->getSongBrowser()->recalculateStarsForSelectedBeatmap(true);
             }
 

+ 91 - 124
src/App/Osu/OsuOptionsMenu.cpp

@@ -502,7 +502,7 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
     m_fSearchOnCharKeybindHackTime = 0.0f;
 
     m_notelockTypes.push_back("None");
-    m_notelockTypes.push_back("McOsu");
+    m_notelockTypes.push_back("neosu");
     m_notelockTypes.push_back("osu!stable (default)");
     m_notelockTypes.push_back("osu!lazer 2020");
 
@@ -567,16 +567,13 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
     addCheckbox("Use osu!.db database (read-only)",
                 "If you have an existing osu! installation,\nthen this will speed up the initial loading process.",
                 convar->getConVarByName("osu_database_enabled"));
-    if(env->getOS() != Environment::OS::OS_HORIZON)
-        addCheckbox(
-            "Load osu! collection.db (read-only)",
-            "If you have an existing osu! installation,\nalso load and display your created collections from there.",
-            convar->getConVarByName("osu_collections_legacy_enabled"));
-    if(env->getOS() != Environment::OS::OS_HORIZON)
-        addCheckbox(
-            "Load osu! scores.db (read-only)",
-            "If you have an existing osu! installation,\nalso load and display your achieved scores from there.",
-            convar->getConVarByName("osu_scores_legacy_enabled"));
+    addCheckbox(
+        "Load osu! collection.db (read-only)",
+        "If you have an existing osu! installation,\nalso load and display your created collections from there.",
+        convar->getConVarByName("osu_collections_legacy_enabled"));
+    addCheckbox("Load osu! scores.db (read-only)",
+                "If you have an existing osu! installation,\nalso load and display your achieved scores from there.",
+                convar->getConVarByName("osu_scores_legacy_enabled"));
 
     addSpacer();
     addCheckbox("Include Relax/Autopilot for total weighted pp/acc",
@@ -584,10 +581,10 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
                 "included in the weighted pp/acc calculation?",
                 convar->getConVarByName("osu_user_include_relax_and_autopilot_for_stats"));
     addCheckbox("Disable osu!lazer star/pp Relax/Autopilot nerfs",
-                "Disabled: osu!lazer algorithm default. Relax/Autopilot scores are nerfed.\nEnabled: McOsu default. "
+                "Disabled: osu!lazer algorithm default. Relax/Autopilot scores are nerfed.\nEnabled: neosu default. "
                 "All Relax/Autopilot nerfs are disabled.",
                 convar->getConVarByName("osu_stars_and_pp_lazer_relax_autopilot_nerf_disabled"));
-    addCheckbox("Show pp instead of score in scorebrowser", "Only McOsu scores will show pp.",
+    addCheckbox("Show pp instead of score in scorebrowser", "Only neosu scores will show pp.",
                 convar->getConVarByName("osu_scores_sort_by_pp"));
     addCheckbox("Always enable touch device pp nerf mod",
                 "Keep touch device pp nerf mod active even when resetting all mods.",
@@ -622,48 +619,45 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
                     convar->getConVarByName("win_processpriority"));
 
     addCheckbox("Show FPS Counter", convar->getConVarByName("osu_draw_fps"));
-    if(env->getOS() != Environment::OS::OS_HORIZON) {
-        addSpacer();
-
-        addCheckbox("Unlimited FPS", convar->getConVarByName("fps_unlimited"));
-
-        CBaseUISlider *fpsSlider =
-            addSlider("FPS Limiter:", 60.0f, 1000.0f, convar->getConVarByName("fps_max"), -1.0f, true);
-        fpsSlider->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onSliderChangeInt));
-        fpsSlider->setKeyDelta(1);
-
-        addSubSection("Layout");
-        OPTIONS_ELEMENT resolutionSelect =
-            addButton("Select Resolution", UString::format("%ix%i", m_osu->getScreenWidth(), m_osu->getScreenHeight()));
-        m_resolutionSelectButton = (CBaseUIButton *)resolutionSelect.elements[0];
-        m_resolutionSelectButton->setClickCallback(
-            fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onResolutionSelect));
-        m_resolutionLabel = (CBaseUILabel *)resolutionSelect.elements[1];
-        m_fullscreenCheckbox = addCheckbox("Fullscreen");
-        m_fullscreenCheckbox->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onFullscreenChange));
-        addCheckbox("Borderless",
-                    "May cause extra input lag if enabled.\nDepends on your operating system version/updates.",
-                    convar->getConVarByName("fullscreen_windowed_borderless"))
-            ->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onBorderlessWindowedChange));
-        addCheckbox("Keep Aspect Ratio",
-                    "Black borders instead of a stretched image.\nOnly relevant if fullscreen is enabled, and "
-                    "letterboxing is disabled.\nUse the two position sliders below to move the viewport around.",
-                    convar->getConVarByName("osu_resolution_keep_aspect_ratio"));
-        addCheckbox("Letterboxing",
-                    "Useful to get the low latency of fullscreen with a smaller game resolution.\nUse the two position "
-                    "sliders below to move the viewport around.",
-                    convar->getConVarByName("osu_letterboxing"));
-        m_letterboxingOffsetXSlider =
-            addSlider("Horizontal position", -1.0f, 1.0f, convar->getConVarByName("osu_letterboxing_offset_x"), 170)
-                ->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onSliderChangeLetterboxingOffset))
-                ->setKeyDelta(0.01f)
-                ->setAnimated(false);
-        m_letterboxingOffsetYSlider =
-            addSlider("Vertical position", -1.0f, 1.0f, convar->getConVarByName("osu_letterboxing_offset_y"), 170)
-                ->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onSliderChangeLetterboxingOffset))
-                ->setKeyDelta(0.01f)
-                ->setAnimated(false);
-    }
+    addSpacer();
+
+    addCheckbox("Unlimited FPS", convar->getConVarByName("fps_unlimited"));
+
+    CBaseUISlider *fpsSlider =
+        addSlider("FPS Limiter:", 60.0f, 1000.0f, convar->getConVarByName("fps_max"), -1.0f, true);
+    fpsSlider->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onSliderChangeInt));
+    fpsSlider->setKeyDelta(1);
+
+    addSubSection("Layout");
+    OPTIONS_ELEMENT resolutionSelect =
+        addButton("Select Resolution", UString::format("%ix%i", m_osu->getScreenWidth(), m_osu->getScreenHeight()));
+    m_resolutionSelectButton = (CBaseUIButton *)resolutionSelect.elements[0];
+    m_resolutionSelectButton->setClickCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onResolutionSelect));
+    m_resolutionLabel = (CBaseUILabel *)resolutionSelect.elements[1];
+    m_fullscreenCheckbox = addCheckbox("Fullscreen");
+    m_fullscreenCheckbox->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onFullscreenChange));
+    addCheckbox("Borderless",
+                "May cause extra input lag if enabled.\nDepends on your operating system version/updates.",
+                convar->getConVarByName("fullscreen_windowed_borderless"))
+        ->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onBorderlessWindowedChange));
+    addCheckbox("Keep Aspect Ratio",
+                "Black borders instead of a stretched image.\nOnly relevant if fullscreen is enabled, and "
+                "letterboxing is disabled.\nUse the two position sliders below to move the viewport around.",
+                convar->getConVarByName("osu_resolution_keep_aspect_ratio"));
+    addCheckbox("Letterboxing",
+                "Useful to get the low latency of fullscreen with a smaller game resolution.\nUse the two position "
+                "sliders below to move the viewport around.",
+                convar->getConVarByName("osu_letterboxing"));
+    m_letterboxingOffsetXSlider =
+        addSlider("Horizontal position", -1.0f, 1.0f, convar->getConVarByName("osu_letterboxing_offset_x"), 170)
+            ->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onSliderChangeLetterboxingOffset))
+            ->setKeyDelta(0.01f)
+            ->setAnimated(false);
+    m_letterboxingOffsetYSlider =
+        addSlider("Vertical position", -1.0f, 1.0f, convar->getConVarByName("osu_letterboxing_offset_y"), 170)
+            ->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onSliderChangeLetterboxingOffset))
+            ->setKeyDelta(0.01f)
+            ->setAnimated(false);
 
     addSubSection("UI Scaling");
     addCheckbox(
@@ -697,29 +691,24 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
                 "performance a tiny bit, since there will be less to draw overall.",
                 convar->getConVarByName("osu_slider_shrink"));
     addSpacer();
-    if(env->getOS() != Environment::OS::OS_HORIZON) {
-        addCheckbox("Legacy Slider Renderer (!)",
-                    "WARNING: Only try enabling this on shitty old computers!\nMay or may not improve fps while few "
-                    "sliders are visible.\nGuaranteed lower fps while many sliders are visible!",
-                    convar->getConVarByName("osu_force_legacy_slider_renderer"));
-        addCheckbox("Higher Quality Sliders (!)", "Disable this if your fps drop too low while sliders are visible.",
-                    convar->getConVarByName("osu_options_high_quality_sliders"))
-            ->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onHighQualitySlidersCheckboxChange));
-        m_sliderQualitySlider =
-            addSlider("Slider Quality", 0.0f, 1.0f, convar->getConVarByName("osu_options_slider_quality"));
-        m_sliderQualitySlider->setChangeCallback(
-            fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onSliderChangeSliderQuality));
-    }
+    addCheckbox("Legacy Slider Renderer (!)",
+                "WARNING: Only try enabling this on shitty old computers!\nMay or may not improve fps while few "
+                "sliders are visible.\nGuaranteed lower fps while many sliders are visible!",
+                convar->getConVarByName("osu_force_legacy_slider_renderer"));
+    addCheckbox("Higher Quality Sliders (!)", "Disable this if your fps drop too low while sliders are visible.",
+                convar->getConVarByName("osu_options_high_quality_sliders"))
+        ->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onHighQualitySlidersCheckboxChange));
+    m_sliderQualitySlider =
+        addSlider("Slider Quality", 0.0f, 1.0f, convar->getConVarByName("osu_options_slider_quality"));
+    m_sliderQualitySlider->setChangeCallback(
+        fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onSliderChangeSliderQuality));
 
     //**************************************************************************************************************************//
 
     CBaseUIElement *sectionAudio = addSection("Audio");
 
     addSubSection("Devices");
-    if(env->getOS() == Environment::OS::OS_HORIZON) {
-        addButton("Restart SoundEngine (fix crackling)")
-            ->setClickCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onOutputDeviceRestart));
-    } else {
+    {
         OPTIONS_ELEMENT outputDeviceSelect = addButton("Select Output Device", "Default", true);
         m_outputDeviceResetButton = outputDeviceSelect.resetButton;
         m_outputDeviceResetButton->setClickCallback(
@@ -793,6 +782,10 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
     }
 
     addSubSection("Volume");
+
+    addCheckbox("Normalize loudness across songs", convar->getConVarByName("normalize_loudness"))
+        ->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onLoudnessNormalizationToggle));
+
     CBaseUISlider *masterVolumeSlider =
         addSlider("Master:", 0.0f, 1.0f, convar->getConVarByName("osu_volume_master"), 70.0f);
     masterVolumeSlider->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onSliderChangePercent));
@@ -816,12 +809,10 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
     offsetSlider->setChangeCallback(fastdelegate::MakeDelegate(this, &OsuOptionsMenu::onSliderChangeIntMS));
     offsetSlider->setKeyDelta(1);
 
-    if(env->getOS() != Environment::OS::OS_HORIZON) {
-        addSubSection("Songbrowser");
-        addCheckbox("Apply speed/pitch mods while browsing",
-                    "Whether to always apply all mods, or keep the preview music normal.",
-                    convar->getConVarByName("osu_beatmap_preview_mods_live"));
-    }
+    addSubSection("Songbrowser");
+    addCheckbox("Apply speed/pitch mods while browsing",
+                "Whether to always apply all mods, or keep the preview music normal.",
+                convar->getConVarByName("osu_beatmap_preview_mods_live"));
 
     addSubSection("Gameplay");
     addCheckbox("Prefer Nightcore over Double Time",
@@ -920,15 +911,8 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
     CBaseUIElement *sectionInput = addSection("Input");
 
     addSubSection("Mouse", "scroll");
-    if(env->getOS() == Environment::OS::OS_WINDOWS || env->getOS() == Environment::OS::OS_MACOS ||
-       env->getOS() == Environment::OS::OS_HORIZON) {
-        addSlider("Sensitivity:", (env->getOS() == Environment::OS::OS_HORIZON ? 1.0f : 0.1f), 6.0f,
-                  convar->getConVarByName("mouse_sensitivity"))
-            ->setKeyDelta(0.01f);
-
-        if(env->getOS() == Environment::OS::OS_HORIZON)
-            addSlider("Joystick S.:", 0.1f, 6.0f, convar->getConVarByName("sdl_joystick_mouse_sensitivity"))
-                ->setKeyDelta(0.01f);
+    if(env->getOS() == Environment::OS::OS_WINDOWS || env->getOS() == Environment::OS::OS_MACOS) {
+        addSlider("Sensitivity:", 0.1f, 6.0f, convar->getConVarByName("mouse_sensitivity"))->setKeyDelta(0.01f);
 
         if(env->getOS() == Environment::OS::OS_MACOS) {
             addLabel("");
@@ -956,11 +940,9 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
         addLabel("Use xinput or xsetwacom to change the tablet area.")->setTextColor(0xff555555);
         addLabel("");
     }
-    if(env->getOS() != Environment::OS::OS_HORIZON) {
-        addCheckbox("Confine Cursor (Windowed)", convar->getConVarByName("osu_confine_cursor_windowed"));
-        addCheckbox("Confine Cursor (Fullscreen)", convar->getConVarByName("osu_confine_cursor_fullscreen"));
-        addCheckbox("Disable Mouse Wheel in Play Mode", convar->getConVarByName("osu_disable_mousewheel"));
-    }
+    addCheckbox("Confine Cursor (Windowed)", convar->getConVarByName("osu_confine_cursor_windowed"));
+    addCheckbox("Confine Cursor (Fullscreen)", convar->getConVarByName("osu_confine_cursor_fullscreen"));
+    addCheckbox("Disable Mouse Wheel in Play Mode", convar->getConVarByName("osu_disable_mousewheel"));
     addCheckbox("Disable Mouse Buttons in Play Mode", convar->getConVarByName("osu_disable_mousebuttons"));
     addCheckbox("Cursor ripples", "The cursor will ripple outwards on clicking.",
                 convar->getConVarByName("osu_draw_cursor_ripples"));
@@ -1116,7 +1098,7 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
     addLabel("");
     addLabel("Info about different notelock algorithms:")->setTextColor(0xff666666);
     addLabel("");
-    addLabel("- McOsu: Auto miss previous circle, always.")->setTextColor(0xff666666);
+    addLabel("- neosu: Auto miss previous circle, always.")->setTextColor(0xff666666);
     addLabel("- osu!stable: Locked until previous circle is miss.")->setTextColor(0xff666666);
     addLabel("- osu!lazer 2020: Auto miss previous circle if > time.")->setTextColor(0xff666666);
     addLabel("");
@@ -1143,7 +1125,7 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
                 convar->getConVarByName("osu_draw_hud"));
     addCheckbox(
         "SHIFT + TAB toggles everything",
-        "Enabled: McOsu default (toggle \"Draw HUD\")\nDisabled: osu! default (always show hiterrorbar + key overlay)",
+        "Enabled: neosu default (toggle \"Draw HUD\")\nDisabled: osu! default (always show hiterrorbar + key overlay)",
         convar->getConVarByName("osu_hud_shift_tab_toggles_everything"));
     addSpacer();
     addCheckbox("Draw Score", convar->getConVarByName("osu_draw_score"));
@@ -1334,11 +1316,6 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
                     "WARNING: Do NOT enable this if you are using a mouse!\nIf this is enabled, then DPI and cm per "
                     "360 will be ignored!",
                     convar->getConVarByName("fposu_absolute_mode"));
-    } else if(env->getOS() == Environment::OS::OS_LINUX) {
-        addSubSection("[Beta] FPoSu 4D Mode - Mouse");
-        addSlider("Sensitivity:", (env->getOS() == Environment::OS::OS_HORIZON ? 1.0f : 0.1f), 6.0f,
-                  convar->getConVarByName("mouse_sensitivity"))
-            ->setKeyDelta(0.01f);
     }
 
     //**************************************************************************************************************************//
@@ -1346,7 +1323,7 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
     sectionOnline = addSection("Online");
 
     addSubSection("Online server");
-    addLabel("If the server admins don't explicitly allow McOsu,")->setTextColor(0xff666666);
+    addLabel("If the server admins don't explicitly allow neosu,")->setTextColor(0xff666666);
     addLabel("you might get banned!")->setTextColor(0xff666666);
     addLabel("");
     m_serverTextbox =
@@ -1364,13 +1341,11 @@ OsuOptionsMenu::OsuOptionsMenu(Osu *osu) : OsuScreenBackable(osu) {
     logInButton->setColor(0xff00ff00);
     logInButton->setTextColor(0xffffffff);
 
-    if(env->getOS() != Environment::OS::OS_HORIZON) {
-        addSubSection("Integration");
-        addCheckbox("Rich Presence (Discord + Steam)",
-                    "Shows your current game state in your friends' friendslists.\ne.g.: Playing Gavin G - Reach Out "
-                    "[Cherry Blossom's Insane]",
-                    convar->getConVarByName("osu_rich_presence"));
-    }
+    addSubSection("Integration");
+    addCheckbox("Rich Presence (Discord + Steam)",
+                "Shows your current game state in your friends' friendslists.\ne.g.: Playing Gavin G - Reach Out "
+                "[Cherry Blossom's Insane]",
+                convar->getConVarByName("osu_rich_presence"));
 
     //**************************************************************************************************************************//
 
@@ -2627,33 +2602,16 @@ void OsuOptionsMenu::onLogInClicked() {
 }
 
 void OsuOptionsMenu::onDownloadOsuClicked() {
-    if(env->getOS() == Environment::OS::OS_HORIZON) {
-        m_osu->getNotificationOverlay()->addNotification("Go to https://osu.ppy.sh/", 0xffffffff, false, 0.75f);
-        return;
-    }
-
     m_osu->getNotificationOverlay()->addNotification("Opening browser, please wait ...", 0xffffffff, false, 0.75f);
     env->openURLInDefaultBrowser("https://osu.ppy.sh/");
 }
 
 void OsuOptionsMenu::onManuallyManageBeatmapsClicked() {
-    if(env->getOS() == Environment::OS::OS_HORIZON) {
-        m_osu->getNotificationOverlay()->addNotification("Google \"How to use McOsu without osu!\"", 0xffffffff, false,
-                                                         0.75f);
-        return;
-    }
-
     m_osu->getNotificationOverlay()->addNotification("Opening browser, please wait ...", 0xffffffff, false, 0.75f);
     env->openURLInDefaultBrowser("https://steamcommunity.com/sharedfiles/filedetails/?id=880768265");
 }
 
 void OsuOptionsMenu::onCM360CalculatorLinkClicked() {
-    if(env->getOS() == Environment::OS::OS_HORIZON) {
-        m_osu->getNotificationOverlay()->addNotification("Go to https://www.mouse-sensitivity.com/", 0xffffffff, false,
-                                                         0.75f);
-        return;
-    }
-
     m_osu->getNotificationOverlay()->addNotification("Opening browser, please wait ...", 0xffffffff, false, 0.75f);
     env->openURLInDefaultBrowser("https://www.mouse-sensitivity.com/");
 }
@@ -3128,6 +3086,15 @@ void OsuOptionsMenu::onWASAPIPeriodChange(CBaseUISlider *slider) {
     }
 }
 
+void OsuOptionsMenu::onLoudnessNormalizationToggle(CBaseUICheckbox *checkbox) {
+    onCheckboxChange(checkbox);
+
+    auto music = m_osu->getSelectedBeatmap()->getMusic();
+    if(music != nullptr) {
+        music->setVolume(m_osu->getSelectedBeatmap()->getIdealVolume());
+    }
+}
+
 void OsuOptionsMenu::onUseSkinsSoundSamplesChange(UString oldValue, UString newValue) { m_osu->reloadSkin(); }
 
 void OsuOptionsMenu::onHighQualitySlidersCheckboxChange(CBaseUICheckbox *checkbox) {

+ 1 - 0
src/App/Osu/OsuOptionsMenu.h

@@ -167,6 +167,7 @@ class OsuOptionsMenu : public OsuScreenBackable, public OsuNotificationOverlayKe
     void onASIOBufferChange(CBaseUISlider *slider);
     void onWASAPIBufferChange(CBaseUISlider *slider);
     void onWASAPIPeriodChange(CBaseUISlider *slider);
+    void onLoudnessNormalizationToggle(CBaseUICheckbox *checkbox);
 
     void onUseSkinsSoundSamplesChange(UString oldValue, UString newValue);
     void onHighQualitySlidersCheckboxChange(CBaseUICheckbox *checkbox);

+ 3 - 14
src/App/Osu/OsuPauseMenu.cpp

@@ -136,14 +136,6 @@ void OsuPauseMenu::mouse_update(bool *propagate_clicks) {
     }
 
     if(anim->isAnimating(&m_fWarningArrowsAnimX)) m_fWarningArrowsAnimStartTime = engine->getTime();
-
-    // HACKHACK: handle joystick mouse select, inject enter keydown
-    if(env->getOS() == Environment::OS::OS_HORIZON) {
-        if(engine->getMouse()->isLeftDown()) {
-            KeyboardEvent e(KEY_ENTER);
-            onKeyDown(e);
-        }
-    }
 }
 
 void OsuPauseMenu::onContinueClicked() {
@@ -151,8 +143,7 @@ void OsuPauseMenu::onContinueClicked() {
     if(anim->isAnimating(&m_fDimAnim)) return;
 
     engine->getSound()->play(m_osu->getSkin()->getMenuHit());
-
-    if(m_osu->getSelectedBeatmap() != NULL) m_osu->getSelectedBeatmap()->pause();
+    m_osu->getSelectedBeatmap()->pause();
 
     scheduleVisibilityChange(false);
 }
@@ -161,8 +152,7 @@ void OsuPauseMenu::onRetryClicked() {
     if(anim->isAnimating(&m_fDimAnim)) return;
 
     engine->getSound()->play(m_osu->getSkin()->getMenuHit());
-
-    if(m_osu->getSelectedBeatmap() != NULL) m_osu->getSelectedBeatmap()->restart();
+    m_osu->getSelectedBeatmap()->restart();
 
     scheduleVisibilityChange(false);
 }
@@ -171,8 +161,7 @@ void OsuPauseMenu::onBackClicked() {
     if(anim->isAnimating(&m_fDimAnim)) return;
 
     engine->getSound()->play(m_osu->getSkin()->getMenuHit());
-
-    if(m_osu->getSelectedBeatmap() != NULL) m_osu->getSelectedBeatmap()->stop();
+    m_osu->getSelectedBeatmap()->stop();
 
     scheduleVisibilityChange(false);
 }

+ 1 - 1
src/App/Osu/OsuRankingScreen.cpp

@@ -572,7 +572,7 @@ void OsuRankingScreen::setScore(Score score, UString dateTime) {
 
 void OsuRankingScreen::setBeatmapInfo(OsuBeatmap *beatmap, OsuDatabaseBeatmap *diff2) {
     m_songInfo->setFromBeatmap(beatmap, diff2);
-    m_songInfo->setPlayer(m_bIsUnranked ? "McOsu" : convar->getConVarByName("name")->getString());
+    m_songInfo->setPlayer(m_bIsUnranked ? "neosu" : convar->getConVarByName("name")->getString());
 
     // round all here to 2 decimal places
     m_fSpeedMultiplier = std::round(m_osu->getSpeedMultiplier() * 100.0f) / 100.0f;

+ 2 - 2
src/App/Osu/OsuReplay.h

@@ -6,8 +6,8 @@ struct Score;
 
 namespace OsuReplay {
 struct Frame {
-    int64_t cur_music_pos;
-    int64_t milliseconds_since_last_frame;
+    long long int cur_music_pos;
+    long long int milliseconds_since_last_frame;
 
     float x;  // 0 - 512
     float y;  // 0 - 384

+ 5 - 5
src/App/Osu/OsuRichPresence.cpp

@@ -35,7 +35,7 @@ const UString OsuRichPresence::KEY_STEAM_STATUS = "status";
 const UString OsuRichPresence::KEY_DISCORD_STATUS = "state";
 const UString OsuRichPresence::KEY_DISCORD_DETAILS = "details";
 
-UString last_status = "[McOsu]\nWaking up";
+UString last_status = "[neosu]\nWaking up";
 Action last_action = IDLE;
 
 void OsuRichPresence::setBanchoStatus(Osu *osu, const char *info_text, Action action) {
@@ -54,7 +54,7 @@ void OsuRichPresence::setBanchoStatus(Osu *osu, const char *info_text, Action ac
     }
 
     char fancy_text[1024] = {0};
-    snprintf(fancy_text, 1023, "[McOsu]\n%s", info_text);
+    snprintf(fancy_text, 1023, "[neosu]\n%s", info_text);
 
     last_status = fancy_text;
     last_action = action;
@@ -114,7 +114,7 @@ void OsuRichPresence::onSongBrowser(Osu *osu) {
     }
 
     // also update window title
-    if(osu_rich_presence_dynamic_windowtitle.getBool()) env->setWindowTitle("McOsu");
+    if(osu_rich_presence_dynamic_windowtitle.getBool()) env->setWindowTitle("neosu");
 }
 
 void OsuRichPresence::onPlayStart(Osu *osu) {
@@ -132,7 +132,7 @@ void OsuRichPresence::onPlayStart(Osu *osu) {
     // also update window title
     if(osu_rich_presence_dynamic_windowtitle.getBool()) {
         UString windowTitle = UString(playingInfo);
-        windowTitle.insert(0, "McOsu - ");
+        windowTitle.insert(0, "neosu - ");
         env->setWindowTitle(windowTitle);
     }
 }
@@ -173,7 +173,7 @@ void OsuRichPresence::setStatus(Osu *osu, UString status, bool force) {
     discord->setRichPresence("smallImageKey", "logo_discord_512_blackfill", true);
     discord->setRichPresence("largeImageText",
                              osu_rich_presence_discord_show_totalpp.getBool()
-                                 ? "Top = Status / Recent Play; Bottom = Total weighted pp (McOsu scores only!)"
+                                 ? "Top = Status / Recent Play; Bottom = Total weighted pp (neosu scores only!)"
                                  : "",
                              true);
     discord->setRichPresence("smallImageText",

+ 7 - 11
src/App/Osu/OsuRoom.cpp

@@ -212,7 +212,6 @@ void OsuRoom::draw(Graphics *g) {
     }
 
     // Not technically drawing below this line, just checking for map download progress
-    if(m_osu->getSelectedBeatmap() != NULL) return;
     if(bancho.room.map_id == 0) return;
     if(bancho.room.map_id == -1) {
         m_map_title->setText("Host is selecting a map...");
@@ -272,10 +271,7 @@ void OsuRoom::mouse_update(bool *propagate_clicks) {
         send_packet(packet);
     }
 
-    if(m_osu->getSelectedBeatmap() != NULL)
-        m_pauseButton->setPaused(!m_osu->getSelectedBeatmap()->isPreviewMusicPlaying());
-    else
-        m_pauseButton->setPaused(true);
+    m_pauseButton->setPaused(!m_osu->getSelectedBeatmap()->isPreviewMusicPlaying());
 
     m_contextMenu->mouse_update(propagate_clicks);
     if(!*propagate_clicks) return;
@@ -498,6 +494,8 @@ void OsuRoom::ragequit() {
     m_osu->m_mainMenu->setVisible(true);
     m_osu->m_chat->removeChannel("#multiplayer");
     m_osu->m_chat->updateVisibility();
+
+    ConVars::sv_cheats.setValue(!bancho.submit_scores());
 }
 
 void OsuRoom::process_beatmapset_info_response(Packet packet) {
@@ -553,11 +551,8 @@ void OsuRoom::on_map_change(bool download) {
     m_ready_btn->is_loading = true;
 
     // Deselect current map
-    if(m_osu->getSelectedBeatmap() != nullptr) {
-        m_pauseButton->setPaused(true);
-        m_osu->m_songBrowser2->m_selectedBeatmap->deselect();
-        m_osu->m_songBrowser2->m_selectedBeatmap = nullptr;
-    }
+    m_pauseButton->setPaused(true);
+    m_osu->m_songBrowser2->m_selectedBeatmap->deselect();
 
     if(bancho.room.map_id == 0) {
         m_map_title->setText("(no map selected)");
@@ -621,6 +616,8 @@ void OsuRoom::on_map_change(bool download) {
 }
 
 void OsuRoom::on_room_joined(Room room) {
+    ConVars::sv_cheats.setValue(false);
+
     bancho.room = room;
     debugLog("Joined room #%d\nPlayers:\n", room.id);
     for(int i = 0; i < 16; i++) {
@@ -691,7 +688,6 @@ void OsuRoom::on_match_started(Room room) {
 
     last_packet_tms = time(NULL);
 
-    m_osu->onBeforePlayStart();
     if(m_osu->getSelectedBeatmap()->play()) {
         m_bVisible = false;
         bancho.match_started = true;

+ 4 - 8
src/App/Osu/OsuScore.cpp

@@ -323,7 +323,7 @@ void OsuScore::addHitResult(OsuBeatmap *beatmap, OsuHitObject *hitObject, HIT hi
                 // NOTE: all pre-lazer-20220902 star/pp algorithm versions only ever considered everything a circle
                 // (except for spinners ofc), which is why simply "cheating" and adding a hardcoded +1 to the
                 // beatmap->getNumCirclesForCurrentTime() worked for live pp NOTE: the reason for the +1 is that in the
-                // mcosu update loop, when we get called where we are inside addHitResult(), the beatmap hitobject
+                // neosu update loop, when we get called where we are inside addHitResult(), the beatmap hitobject
                 // counters have not yet been updated. NOTE: we can not early update the counters since we don't yet
                 // know whether the hitobject will actually be "finished" in that part of the update loop. NOTE: now,
                 // since the algorithms do require each individual hitobject type counts (and not JUST the "circle"
@@ -564,13 +564,9 @@ int OsuScore::getModsLegacy() {
 
     // Set some unused (in osu!std) mod flags for non-vanilla mods
     // (these flags don't seem to cause issues on osu!stable or bancho.py)
-    if(bancho.set_fposu_flag) {
-        if(convar->getConVarByName("osu_mod_fposu")->getBool()) modsLegacy |= ModFlags::FPoSu;
-    }
-    if(bancho.set_mirror_flag) {
-        if(convar->getConVarByName("osu_playfield_mirror_horizontal")->getBool()) modsLegacy |= ModFlags::Mirror;
-        if(convar->getConVarByName("osu_playfield_mirror_vertical")->getBool()) modsLegacy |= ModFlags::Mirror;
-    }
+    if(convar->getConVarByName("osu_mod_fposu")->getBool()) modsLegacy |= ModFlags::FPoSu;
+    if(convar->getConVarByName("osu_playfield_mirror_horizontal")->getBool()) modsLegacy |= ModFlags::Mirror;
+    if(convar->getConVarByName("osu_playfield_mirror_vertical")->getBool()) modsLegacy |= ModFlags::Mirror;
 
     return modsLegacy;
 }

+ 6 - 12
src/App/Osu/OsuSkin.cpp

@@ -344,13 +344,11 @@ void OsuSkin::update() {
     }
 
     // shitty check to not animate while paused with hitobjects in background
-    if(m_osu->isInPlayMode() && m_osu->getSelectedBeatmap() != NULL && !m_osu->getSelectedBeatmap()->isPlaying() &&
-       !osu_skin_animation_force.getBool())
+    if(m_osu->isInPlayMode() && !m_osu->getSelectedBeatmap()->isPlaying() && !osu_skin_animation_force.getBool())
         return;
 
     const bool useEngineTimeForAnimations = !m_osu->isInPlayMode();
-    const long curMusicPos =
-        m_osu->getSelectedBeatmap() != NULL ? m_osu->getSelectedBeatmap()->getCurMusicPosWithOffsets() : 0;
+    const long curMusicPos = m_osu->getSelectedBeatmap()->getCurMusicPosWithOffsets();
     for(int i = 0; i < m_images.size(); i++) {
         m_images[i]->update(m_animationSpeedMultiplier, useEngineTimeForAnimations, curMusicPos);
     }
@@ -431,8 +429,7 @@ void OsuSkin::load() {
     // skin ini
     randomizeFilePath();
     m_sSkinIniFilePath = m_sFilePath;
-    UString defaultSkinIniFilePath =
-        UString(env->getOS() == Environment::OS::OS_HORIZON ? "romfs:/materials/" : MCENGINE_DATA_DIR "/materials/");
+    UString defaultSkinIniFilePath = MCENGINE_DATA_DIR "/materials/";
     defaultSkinIniFilePath.append(OSUSKIN_DEFAULT_SKIN_PATH);
     defaultSkinIniFilePath.append("skin.ini");
     m_sSkinIniFilePath.append("skin.ini");
@@ -1306,15 +1303,13 @@ void OsuSkin::checkLoadImage(Image **addressOfPointer, std::string skinElementNa
     // NOTE: only the default skin is loaded with a resource name (it must never be unloaded by other instances), and it
     // is NOT added to the resources vector
 
-    std::string defaultFilePath1 =
-        env->getOS() == Environment::OS::OS_HORIZON ? "romfs:/materials/" : MCENGINE_DATA_DIR "/materials/";
+    std::string defaultFilePath1 = MCENGINE_DATA_DIR "/materials/";
     defaultFilePath1.append(OSUSKIN_DEFAULT_SKIN_PATH);
     defaultFilePath1.append(skinElementName);
     defaultFilePath1.append("@2x.");
     defaultFilePath1.append(fileExtension);
 
-    std::string defaultFilePath2 =
-        env->getOS() == Environment::OS::OS_HORIZON ? "romfs:/materials/" : MCENGINE_DATA_DIR "/materials/";
+    std::string defaultFilePath2 = MCENGINE_DATA_DIR "/materials/";
     defaultFilePath2.append(OSUSKIN_DEFAULT_SKIN_PATH);
     defaultFilePath2.append(skinElementName);
     defaultFilePath2.append(".");
@@ -1460,8 +1455,7 @@ void OsuSkin::checkLoadSound(Sound **addressOfPointer, std::string skinElementNa
     };
 
     // load default skin
-    std::string defaultpath =
-        env->getOS() == Environment::OS::OS_HORIZON ? "romfs:/materials/" : MCENGINE_DATA_DIR "./materials/";
+    std::string defaultpath = MCENGINE_DATA_DIR "./materials/";
     defaultpath.append(OSUSKIN_DEFAULT_SKIN_PATH);
     defaultpath.append(skinElementName);
     std::string defaultResourceName = resourceName;

+ 2 - 2
src/App/Osu/OsuSkinImage.cpp

@@ -105,12 +105,12 @@ bool OsuSkinImage::loadImage(std::string skinElementName, bool ignoreDefaultSkin
     filepath2.append(skinElementName);
     filepath2.append(".png");
 
-    std::string defaultFilePath1 = env->getOS() == Environment::OS::OS_HORIZON ? "romfs:/materials/" : "./materials/";
+    std::string defaultFilePath1 = "./materials/";
     defaultFilePath1.append(OsuSkin::OSUSKIN_DEFAULT_SKIN_PATH);
     defaultFilePath1.append(skinElementName);
     defaultFilePath1.append("@2x.png");
 
-    std::string defaultFilePath2 = env->getOS() == Environment::OS::OS_HORIZON ? "romfs:/materials/" : "./materials/";
+    std::string defaultFilePath2 = "./materials/";
     defaultFilePath2.append(OsuSkin::OSUSKIN_DEFAULT_SKIN_PATH);
     defaultFilePath2.append(skinElementName);
     defaultFilePath2.append(".png");

+ 1 - 1
src/App/Osu/OsuSlider.cpp

@@ -1218,7 +1218,7 @@ void OsuSlider::onHit(OsuScore::HIT result, long delta, bool startOrEnd, float t
 
         if(!isEndResultFromStrictTrackingMod) {
             // special case: osu!lazer 2020 only returns 1 judgement for the whole slider, but via the startcircle. i.e.
-            // we are not allowed to drain again here in mcosu logic (because startcircle judgement is handled at the
+            // we are not allowed to drain again here in neosu logic (because startcircle judgement is handled at the
             // end here)
             const bool isLazer2020Drain = (m_osu_drain_type_ref->getInt() == 3);  // osu!lazer 2020
 

+ 19 - 33
src/App/Osu/OsuSongBrowser.cpp

@@ -568,9 +568,7 @@ OsuSongBrowser::OsuSongBrowser(Osu *osu) : OsuScreenBackable(osu) {
     m_scoreBrowser->setDrawFrame(false);
     m_scoreBrowser->setHorizontalScrolling(false);
     m_scoreBrowser->setScrollbarSizeMultiplier(0.25f);
-    m_scoreBrowser->setScrollResistance((env->getOS() == Environment::OS::OS_HORIZON)
-                                            ? convar->getConVarByName("ui_scrollview_resistance")->getInt()
-                                            : 15);  // a bit shitty this check + convar, but works well enough
+    m_scoreBrowser->setScrollResistance(15);
     m_scoreBrowserScoresStillLoadingElement = new OsuUISongBrowserScoresStillLoadingElement(m_osu, "Loading...");
     m_scoreBrowserNoRecordsYetElement = new OsuUISongBrowserNoRecordsSetElement(m_osu, "No records set!");
     m_scoreBrowser->getContainer()->addBaseUIElement(m_scoreBrowserNoRecordsYetElement);
@@ -589,9 +587,7 @@ OsuSongBrowser::OsuSongBrowser(Osu *osu) : OsuScreenBackable(osu) {
     m_songBrowser->setDrawBackground(false);
     m_songBrowser->setDrawFrame(false);
     m_songBrowser->setHorizontalScrolling(false);
-    m_songBrowser->setScrollResistance((env->getOS() == Environment::OS::OS_HORIZON)
-                                           ? convar->getConVarByName("ui_scrollview_resistance")->getInt()
-                                           : 15);  // a bit shitty this check + convar, but works well enough
+    m_songBrowser->setScrollResistance(15);
 
     // beatmap database
     m_db = new OsuDatabase(m_osu);
@@ -599,7 +595,7 @@ OsuSongBrowser::OsuSongBrowser(Osu *osu) : OsuScreenBackable(osu) {
 
     // behaviour
     m_bHasSelectedAndIsPlaying = false;
-    m_selectedBeatmap = NULL;
+    m_selectedBeatmap = new OsuBeatmap(m_osu);
     m_fPulseAnimation = 0.0f;
     m_fBackgroundFadeInTime = 0.0f;
 
@@ -636,7 +632,7 @@ OsuSongBrowser::~OsuSongBrowser() {
     engine->getResourceManager()->destroyResource(m_dynamicStarCalculator);
     engine->getResourceManager()->destroyResource(m_backgroundSearchMatcher);
 
-    // leak memory, who cares we're closing mcosu anyway
+    // leak memory, who cares we're closing neosu anyway
 }
 
 void OsuSongBrowser::draw(Graphics *g) {
@@ -669,8 +665,7 @@ void OsuSongBrowser::draw(Graphics *g) {
         float alpha = 1.0f;
         if(osu_songbrowser_background_fade_in_duration.getFloat() > 0.0f) {
             // handle fadein trigger after handler is finished loading
-            const bool ready = m_osu->getSelectedBeatmap() != NULL &&
-                               m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL &&
+            const bool ready = m_osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL &&
                                m_osu->getBackgroundImageHandler()->getLoadBackgroundImage(
                                    m_osu->getSelectedBeatmap()->getSelectedDifficulty2()) != NULL &&
                                m_osu->getBackgroundImageHandler()
@@ -710,8 +705,8 @@ void OsuSongBrowser::draw(Graphics *g) {
     m_scoreBrowser->draw(g);
 
     // draw strain graph of currently selected beatmap
-    if(osu_draw_songbrowser_strain_graph.getBool() && getSelectedBeatmap() != NULL &&
-       getSelectedBeatmap()->getSelectedDifficulty2() != NULL && m_dynamicStarCalculator->isAsyncReady()) {
+    if(osu_draw_songbrowser_strain_graph.getBool() && getSelectedBeatmap()->getSelectedDifficulty2() != NULL &&
+       m_dynamicStarCalculator->isAsyncReady()) {
         // this is still WIP
 
         /// const std::vector<double> &aimStrains = getSelectedBeatmap()->getAimStrains();
@@ -952,7 +947,7 @@ void OsuSongBrowser::draw(Graphics *g) {
 }
 
 void OsuSongBrowser::drawSelectedBeatmapBackgroundImage(Graphics *g, Osu *osu, float alpha) {
-    if(osu->getSelectedBeatmap() != NULL && osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL) {
+    if(osu->getSelectedBeatmap()->getSelectedDifficulty2() != NULL) {
         Image *backgroundImage = osu->getBackgroundImageHandler()->getLoadBackgroundImage(
             osu->getSelectedBeatmap()->getSelectedDifficulty2());
         if(backgroundImage != NULL && backgroundImage->isReady()) {
@@ -1623,25 +1618,15 @@ void OsuSongBrowser::onSelectionChange(OsuUISongBrowserButton *button, bool rebu
 }
 
 void OsuSongBrowser::onDifficultySelected(OsuDatabaseBeatmap *diff2, bool play) {
-    // legacy logic (deselect = unload)
-    const bool wasSelectedBeatmapNULL = (m_selectedBeatmap == NULL);
-    if(m_selectedBeatmap != NULL) m_selectedBeatmap->deselect();
-
-    // create/recreate/cache runtime beatmap object
-    if(m_selectedBeatmap == NULL) {
-        m_selectedBeatmap = new OsuBeatmap(m_osu);
+    // deselect = unload
+    auto prev_diff2 = m_selectedBeatmap->getSelectedDifficulty2();
+    m_selectedBeatmap->deselect();
+    if(diff2 != prev_diff2 && !diff2->do_not_store) {
+        m_previousRandomBeatmaps.push_back(diff2);
     }
 
-    // remember it
-    if(diff2 != m_selectedBeatmap->getSelectedDifficulty2()) m_previousRandomBeatmaps.push_back(diff2);
-
-    // select diff on runtime beatmap object
+    // select = play preview music
     m_selectedBeatmap->selectDifficulty2(diff2);
-    if(wasSelectedBeatmapNULL) {
-        // force update music through songbrowser refreshes (db reloads)
-        m_selectedBeatmap->deselect();
-        m_selectedBeatmap->select();
-    }
 
     // update song info
     m_songInfo->setFromBeatmap(m_selectedBeatmap, diff2);
@@ -1666,7 +1651,6 @@ void OsuSongBrowser::onDifficultySelected(OsuDatabaseBeatmap *diff2, bool play)
             // CTRL + click = auto
             if(engine->getKeyboard()->isControlDown()) m_osu->getModSelector()->enableAuto();
 
-            m_osu->onBeforePlayStart();
             if(m_selectedBeatmap->play()) {
                 m_bHasSelectedAndIsPlaying = true;
                 setVisible(false);
@@ -1698,7 +1682,9 @@ void OsuSongBrowser::refreshBeatmaps() {
     checkHandleKillDynamicStarCalculator(false);
     checkHandleKillBackgroundSearchMatcher();
 
-    m_selectedBeatmap = NULL;
+    m_selectedBeatmap->pausePreviewMusic();
+    m_selectedBeatmap->deselect();
+    m_selectedBeatmap = new OsuBeatmap(m_osu);
 
     m_selectionPreviousSongButton = NULL;
     m_selectionPreviousSongDiffButton = NULL;
@@ -4017,7 +4003,7 @@ void OsuSongBrowser::highlightScore(uint64_t unixTimestamp) {
 
 void OsuSongBrowser::recalculateStarsForSelectedBeatmap(bool force) {
     if(!osu_songbrowser_dynamic_star_recalc.getBool()) return;
-    if(m_selectedBeatmap == NULL || m_selectedBeatmap->getSelectedDifficulty2() == NULL) return;
+    if(m_selectedBeatmap->getSelectedDifficulty2() == NULL) return;
 
     // HACKHACK: temporarily deactivated, see OsuSongBrowser::update(), but only if drawing scrubbing timeline strain
     // graph is enabled (or "Draw Stats: Stars* (Total)", or "Draw Stats: pp (SS)")
@@ -4091,7 +4077,7 @@ void OsuSongBrowser::selectRandomBeatmap() {
 #ifdef _WIN32
     HCRYPTPROV hCryptProv;
     CryptAcquireContext(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
-    CryptGenRandom(hCryptProv, sizeof(rng), (BYTE*)&rng);
+    CryptGenRandom(hCryptProv, sizeof(rng), (BYTE *)&rng);
     CryptReleaseContext(hCryptProv, 0);
 #else
     getrandom(&rng, sizeof(rng), 0);

+ 6 - 3
src/App/Osu/OsuUIAvatar.cpp

@@ -31,9 +31,6 @@ bool download_avatar(uint32_t user_id) {
     ss << MCENGINE_DATA_DIR "avatars/" << bancho.endpoint.toUtf8();
     auto server_dir = ss.str();
     if(!env->directoryExists(server_dir)) {
-        if(!env->directoryExists(MCENGINE_DATA_DIR "avatars")) {
-            env->createDirectory(MCENGINE_DATA_DIR "avatars");
-        }
         env->createDirectory(server_dir);
     }
 
@@ -83,6 +80,12 @@ OsuUIAvatar::OsuUIAvatar(uint32_t player_id, float xPos, float yPos, float xSize
     }
 }
 
+OsuUIAvatar::~OsuUIAvatar() {
+    if(avatar != nullptr) {
+        engine->getResourceManager()->destroyResource(avatar);
+    }
+}
+
 void OsuUIAvatar::draw(Graphics *g, float alpha) {
     if(!on_screen) return;  // Comment when you need to debug on_screen logic
 

+ 1 - 0
src/App/Osu/OsuUIAvatar.h

@@ -6,6 +6,7 @@
 class OsuUIAvatar : public CBaseUIButton {
    public:
     OsuUIAvatar(uint32_t player_id, float xPos, float yPos, float xSize, float ySize);
+    ~OsuUIAvatar();
 
     virtual void draw(Graphics *g, float alpha = 1.f);
 

+ 1 - 4
src/App/Osu/OsuUISongBrowserCollectionButton.cpp

@@ -170,10 +170,7 @@ void OsuUISongBrowserCollectionButton::onContextMenu(UString text, int id) {
                 spacer->setTextColor(0xff888888);
                 spacer->setTextDarkColor(0xff000000);
 
-                label =
-                    m_contextMenu->addButton(env->getOS() == Environment::OS::OS_HORIZON ? "(Click HERE to confirm)"
-                                                                                         : "(Press ENTER to confirm.)",
-                                             id);
+                label = m_contextMenu->addButton("(Press ENTER to confirm.)", id);
                 label->setTextLeft(false);
                 label->setTextColor(0xff555555);
                 label->setTextDarkColor(0xff000000);

+ 1 - 122
src/App/Osu/OsuUISongBrowserScoreButton.cpp

@@ -36,12 +36,6 @@
 #include "SoundEngine.h"
 
 ConVar *OsuUISongBrowserScoreButton::m_osu_scores_sort_by_pp_ref = NULL;
-ConVar *OsuUISongBrowserScoreButton::m_osu_mods_ref = NULL;
-ConVar *OsuUISongBrowserScoreButton::m_osu_speed_override_ref = NULL;
-ConVar *OsuUISongBrowserScoreButton::m_osu_ar_override_ref = NULL;
-ConVar *OsuUISongBrowserScoreButton::m_osu_cs_override_ref = NULL;
-ConVar *OsuUISongBrowserScoreButton::m_osu_od_override_ref = NULL;
-ConVar *OsuUISongBrowserScoreButton::m_osu_hp_override_ref = NULL;
 UString OsuUISongBrowserScoreButton::recentScoreIconString;
 
 OsuUISongBrowserScoreButton::OsuUISongBrowserScoreButton(Osu *osu, OsuUIContextMenu *contextMenu, float xPos,
@@ -53,12 +47,6 @@ OsuUISongBrowserScoreButton::OsuUISongBrowserScoreButton(Osu *osu, OsuUIContextM
 
     if(m_osu_scores_sort_by_pp_ref == NULL)
         m_osu_scores_sort_by_pp_ref = convar->getConVarByName("osu_scores_sort_by_pp");
-    if(m_osu_mods_ref == NULL) m_osu_mods_ref = convar->getConVarByName("osu_mods");
-    if(m_osu_speed_override_ref == NULL) m_osu_speed_override_ref = convar->getConVarByName("osu_speed_override");
-    if(m_osu_ar_override_ref == NULL) m_osu_ar_override_ref = convar->getConVarByName("osu_ar_override");
-    if(m_osu_cs_override_ref == NULL) m_osu_cs_override_ref = convar->getConVarByName("osu_cs_override");
-    if(m_osu_od_override_ref == NULL) m_osu_od_override_ref = convar->getConVarByName("osu_od_override");
-    if(m_osu_hp_override_ref == NULL) m_osu_hp_override_ref = convar->getConVarByName("osu_hp_override");
 
     if(recentScoreIconString.length() < 1) recentScoreIconString.insert(0, OsuIcons::ARROW_CIRCLE_UP);
 
@@ -579,90 +567,7 @@ void OsuUISongBrowserScoreButton::onContextMenu(UString text, int id) {
 }
 
 void OsuUISongBrowserScoreButton::onUseModsClicked() {
-    bool nomod = (m_score.modsLegacy == 0);
-
-    // legacy mods (common to all scores)
-    {
-        m_osu->getModSelector()->resetMods();
-        m_osu_mods_ref->setValue(getModsStringForConVar(m_score.modsLegacy));
-    }
-
-    if(m_score.isLegacyScore || m_score.isImportedLegacyScore) {
-        // legacy score (or imported legacy score), no custom beatmap values necessary, can get everything directly from
-        // modsLegacy
-
-        // (nothing to do here, everything is already handled above in the "legacy mods" block)
-    } else {
-        // mcosu score, custom values for everything possible, have to calculate and check whether to apply any
-        // overrides (or leave default) reason being that just because the speedMultiplier stored in the score = 1.5x
-        // doesn't mean that we should move the override slider to 1.5x especially for CS/AR/OD/HP, because those get
-        // stored in the score as directly coming from OsuBeatmap::getAR() (so with pre-applied difficultyMultiplier
-        // etc.)
-
-        // overrides
-
-        // NOTE: if the beatmap is loaded (in db), then use the raw base values from there, otherwise trust potentially
-        // incorrect stored values from score (see explanation above)
-        float tempAR = m_score.AR;
-        float tempCS = m_score.CS;
-        float tempOD = m_score.OD;
-        float tempHP = m_score.HP;
-        const OsuDatabaseBeatmap *diff2 = m_osu->getSongBrowser()->getDatabase()->getBeatmapDifficulty(m_score.md5hash);
-        if(diff2 != NULL) {
-            tempAR = diff2->getAR();
-            tempCS = diff2->getCS();
-            tempOD = diff2->getOD();
-            tempHP = diff2->getHP();
-        }
-
-        const OsuReplay::BEATMAP_VALUES legacyValues =
-            OsuReplay::getBeatmapValuesForModsLegacy(m_score.modsLegacy, tempAR, tempCS, tempOD, tempHP);
-
-        // beatmap values
-        {
-            const float beatmapValueComparisonEpsilon = 0.0001f;
-            if(std::abs(legacyValues.AR - m_score.AR) >= beatmapValueComparisonEpsilon) {
-                m_osu_ar_override_ref->setValue(m_score.AR);
-                nomod = false;
-            }
-            if(std::abs(legacyValues.CS - m_score.CS) >= beatmapValueComparisonEpsilon) {
-                m_osu_cs_override_ref->setValue(m_score.CS);
-                nomod = false;
-            }
-            if(std::abs(legacyValues.OD - m_score.OD) >= beatmapValueComparisonEpsilon) {
-                m_osu_od_override_ref->setValue(m_score.OD);
-                nomod = false;
-            }
-            if(std::abs(legacyValues.HP - m_score.HP) >= beatmapValueComparisonEpsilon) {
-                m_osu_hp_override_ref->setValue(m_score.HP);
-                nomod = false;
-            }
-        }
-
-        // speed multiplier
-        {
-            const float speedMultiplierComparisonEpsilon = 0.0001f;
-            if(std::abs(legacyValues.speedMultiplier - m_score.speedMultiplier) >= speedMultiplierComparisonEpsilon) {
-                m_osu_speed_override_ref->setValue(m_score.speedMultiplier);
-                nomod = false;
-            }
-        }
-
-        // experimental mods
-        {
-            auto cv = UString(m_score.experimentalModsConVars.c_str());
-            const std::vector<UString> experimentalMods = cv.split(";");
-            for(size_t i = 0; i < experimentalMods.size(); i++) {
-                ConVar *cvar = convar->getConVarByName(experimentalMods[i], false);
-                if(cvar != NULL) {
-                    cvar->setValue(1.0f);  // enable experimental mod (true, 1.0f)
-                    nomod = false;
-                } else
-                    debugLog("couldn't find \"%s\"\n", experimentalMods[i].toUtf8());
-            }
-        }
-    }
-
+    bool nomod = m_osu->useMods(&m_score);
     engine->getSound()->play(nomod ? m_osu->getSkin()->getCheckOff() : m_osu->getSkin()->getCheckOn());
 }
 
@@ -919,29 +824,3 @@ UString OsuUISongBrowserScoreButton::getModsStringForDisplay(int mods) {
 
     return modsString;
 }
-
-UString OsuUISongBrowserScoreButton::getModsStringForConVar(int mods) {
-    UString modsString = "  ";  // double space to reset if emtpy
-
-    // NOTE: the order here is different on purpose, to avoid name collisions during parsing (see Osu::updateMods())
-    // order is the same as in OsuModSelector::updateModConVar()
-    if(mods & ModFlags::Easy) modsString.append("ez");
-    if(mods & ModFlags::HardRock) modsString.append("hr");
-    if(mods & ModFlags::Relax) modsString.append("relax");
-    if(mods & ModFlags::NoFail) modsString.append("nf");
-    if(mods & ModFlags::SuddenDeath) modsString.append("sd");
-    if(mods & ModFlags::Perfect) modsString.append("ss,");
-    if(mods & ModFlags::Autopilot) modsString.append("autopilot");
-    if(mods & ModFlags::HalfTime) modsString.append("ht");
-    if(mods & ModFlags::DoubleTime) modsString.append("dt");
-    if(mods & ModFlags::Nightcore) modsString.append("nc");
-    if(mods & ModFlags::SpunOut) modsString.append("spunout");
-    if(mods & ModFlags::Hidden) modsString.append("hd");
-    if(mods & ModFlags::Autoplay) modsString.append("auto");
-    if(mods & ModFlags::Nightmare) modsString.append("nightmare");
-    if(mods & ModFlags::Target) modsString.append("practicetarget");
-    if(mods & ModFlags::TouchDevice) modsString.append("nerftd");
-    if(mods & ModFlags::ScoreV2) modsString.append("v2");
-
-    return modsString;
-}

+ 0 - 1
src/App/Osu/OsuUISongBrowserScoreButton.h

@@ -22,7 +22,6 @@ class OsuUISongBrowserScoreButton : public CBaseUIButton {
    public:
     static OsuSkinImage *getGradeImage(Osu *osu, Score::Grade grade);
     static UString getModsStringForDisplay(int mods);
-    static UString getModsStringForConVar(int mods);
 
     enum class STYLE { SCORE_BROWSER, TOP_RANKS };
 

+ 1 - 3
src/App/Osu/OsuUISongBrowserSongButton.cpp

@@ -568,9 +568,7 @@ void OsuUISongBrowserSongButton::onAddToCollectionConfirmed(UString text, int id
             spacer->setTextColor(0xff888888);
             spacer->setTextDarkColor(0xff000000);
 
-            label = m_contextMenu->addButton(
-                env->getOS() == Environment::OS::OS_HORIZON ? "(Click HERE to confirm)" : "(Press ENTER to confirm.)",
-                id);
+            label = m_contextMenu->addButton("(Press ENTER to confirm.)", id);
             label->setTextLeft(false);
             label->setTextColor(0xff555555);
             label->setTextDarkColor(0xff000000);

+ 2 - 2
src/App/Osu/OsuUISongBrowserUserButton.cpp

@@ -239,8 +239,8 @@ void OsuUISongBrowserUserButton::updateUserStats() {
         uint32_t score_for_current_level = OsuDatabase::getRequiredScoreForLevel(level);
         uint32_t score_for_next_level = OsuDatabase::getRequiredScoreForLevel(level + 1);
         if(score_for_next_level > score_for_current_level) {
-            percentToNextLevel =
-                (my->total_score - score_for_current_level) / (score_for_next_level - score_for_current_level);
+            percentToNextLevel = (float)(my->total_score - score_for_current_level) /
+                                 (float)(score_for_next_level - score_for_current_level);
         }
 
         stats = OsuDatabase::PlayerStats{

+ 2 - 7
src/App/Osu/OsuUIUserContextMenu.cpp

@@ -108,13 +108,8 @@ void OsuUIUserContextMenuScreen::on_action(UString text, int user_action) {
         m_osu->m_chat->addChannel(user_info->name, true);
     } else if(user_action == VIEW_PROFILE) {
         auto url = UString::format("https://%s/u/%d", bancho.endpoint.toUtf8(), m_user_id);
-        if(env->getOS() == Environment::OS::OS_HORIZON) {
-            m_osu->getNotificationOverlay()->addNotification(url, 0xffffffff, false, 0.75f);
-        } else {
-            m_osu->getNotificationOverlay()->addNotification("Opening browser, please wait ...", 0xffffffff, false,
-                                                             0.75f);
-            env->openURLInDefaultBrowser(url.toUtf8());
-        }
+        m_osu->getNotificationOverlay()->addNotification("Opening browser, please wait ...", 0xffffffff, false, 0.75f);
+        env->openURLInDefaultBrowser(url.toUtf8());
     } else if(user_action == UA_ADD_FRIEND) {
         Packet packet;
         packet.id = FRIEND_ADD;

+ 4 - 2
src/App/Osu/OsuUpdateHandler.cpp

@@ -107,7 +107,7 @@ void OsuUpdateHandler::_requestUpdate() {
     debugLog("OsuUpdateHandler::requestUpdate()\n");
     m_status = STATUS::STATUS_CHECKING_FOR_UPDATE;
 
-    UString latestVersion = engine->getNetworkHandler()->httpGet(MCOSU_UPDATE_URL "/latest-version.txt");
+    UString latestVersion = engine->getNetworkHandler()->httpGet(NEOSU_UPDATE_URL "/latest-version.txt");
     float fLatestVersion = strtof(latestVersion.toUtf8(), NULL);
     if(fLatestVersion == 0.f) {
         m_status = STATUS::STATUS_UP_TO_DATE;
@@ -128,8 +128,10 @@ void OsuUpdateHandler::_requestUpdate() {
 #else
     const char *os = "linux";
 #endif
+
+    // XXX: Change update URL to /update/ or something
     debugLog("Downloading latest update... (current v%.2f, latest v%.2f)\n", current_version, fLatestVersion);
-    update_url = UString::format(MCOSU_UPDATE_URL "/mcosu-multiplayer-%s-v%.2f.zip", os, fLatestVersion);
+    update_url = UString::format(NEOSU_UPDATE_URL "/neosu-multiplayer-%s-v%.2f.zip", os, fLatestVersion);
 }
 
 bool OsuUpdateHandler::_downloadUpdate() {

+ 4 - 8
src/App/Osu/OsuUserStatsScreen.cpp

@@ -195,7 +195,7 @@ class OsuUserStatsScreenBackgroundPPRecalculator : public Resource {
                         score.version = OsuScore::VERSION;
 
                         if(m_bImportLegacyScores && score.isLegacyScore) {
-                            score.isLegacyScore = false;  // convert to McOsu (pp) score
+                            score.isLegacyScore = false;  // convert to neosu (pp) score
                             score.isImportedLegacyScore =
                                 true;  // but remember that this score does not have all play data
                             {
@@ -246,15 +246,11 @@ OsuUserStatsScreen::OsuUserStatsScreen(Osu *osu) : OsuScreenBackable(osu) {
 
     m_ppVersionInfoLabel = new OsuUIUserStatsScreenLabel(m_osu);
     m_ppVersionInfoLabel->setText(UString::format("pp Version: %i", OsuDifficultyCalculator::PP_ALGORITHM_VERSION));
-    // m_ppVersionInfoLabel->setTooltipText("WARNING: McOsu's star/pp algorithm is currently lagging behind the
-    // \"official\" version.\n \nReason being that keeping up-to-date requires a LOT of changes now.\nThe next goal is
-    // rewriting the algorithm architecture to be more similar to osu!lazer,\nas that will make porting star/pp changes
-    // infinitely easier for the foreseeable future.\n \nNo promises as to when all of that will be finished.");
     m_ppVersionInfoLabel->setTooltipText(
         "NOTE: This version number does NOT mean your scores have already been recalculated!\nNOTE: Click the gear "
         "button on the right and \"Recalculate pp\".\n \nThis version number reads as the year YYYY and then month MM "
         "and then day DD.\nThat date specifies when the last pp/star algorithm changes were done/released by "
-        "peppy.\nMcOsu always uses the in-use-for-public-global-online-rankings algorithms if possible.");
+        "peppy.\nneosu always uses the in-use-for-public-global-online-rankings algorithms if possible.");
     m_ppVersionInfoLabel->setTextColor(0x77888888 /*0xbbbb0000*/);
     m_ppVersionInfoLabel->setDrawBackground(false);
     m_ppVersionInfoLabel->setDrawFrame(false);
@@ -673,7 +669,7 @@ void OsuUserStatsScreen::onCopyAllScoresConfirmed(UString text, int id) {
         for(size_t i = 0; i < kv.second.size(); i++) {
             const Score &existingScore = kv.second[i];
 
-            // NOTE: only ever copy McOsu scores
+            // NOTE: only ever copy neosu scores
             if(!existingScore.isLegacyScore) {
                 if(existingScore.playerName == sCopyAllScoresFromUser) {
                     // check if this user already has this exact same score (copied previously) and don't copy if that
@@ -759,7 +755,7 @@ void OsuUserStatsScreen::onDeleteAllScoresConfirmed(UString text, int id) {
         for(size_t i = 0; i < kv.second.size(); i++) {
             const Score &score = kv.second[i];
 
-            // NOTE: only ever delete McOsu scores
+            // NOTE: only ever delete neosu scores
             if(!score.isLegacyScore) {
                 if(score.playerName == splayerName) {
                     kv.second.erase(kv.second.begin() + i);

+ 4 - 5
src/App/Osu/OsuVolumeOverlay.cpp

@@ -34,10 +34,6 @@ OsuVolumeOverlay::OsuVolumeOverlay(Osu *osu) : OsuScreen(osu) {
     osu_volume_master_inactive = convar->getConVarByName("osu_volume_master_inactive");
     osu_volume_change_interval = convar->getConVarByName("osu_volume_change_interval");
 
-    if(env->getOS() == Environment::OS::OS_HORIZON) {
-        osu_volume_music->setValue(0.3f);
-    }
-
     m_fVolumeChangeTime = 0.0f;
     m_fVolumeChangeFade = 1.0f;
     m_fLastVolume = osu_volume_master->getFloat();
@@ -325,5 +321,8 @@ void OsuVolumeOverlay::onEffectVolumeChange() {
 }
 
 void OsuVolumeOverlay::onMusicVolumeChange(UString oldValue, UString newValue) {
-    if(m_osu->getSelectedBeatmap() != NULL) m_osu->getSelectedBeatmap()->setVolume(newValue.toFloat());
+    auto music = m_osu->getSelectedBeatmap()->getMusic();
+    if(music != nullptr) {
+        music->setVolume(m_osu->getSelectedBeatmap()->getIdealVolume());
+    }
 }

+ 1 - 1
src/Engine/Environment.h

@@ -19,7 +19,7 @@ class ContextMenu;
 
 class Environment {
    public:
-    enum class OS { OS_NULL, OS_WINDOWS, OS_LINUX, OS_MACOS, OS_HORIZON };
+    enum class OS { OS_NULL, OS_WINDOWS, OS_LINUX, OS_MACOS };
 
    public:
     Environment();

+ 1 - 2
src/Engine/Font.cpp

@@ -96,8 +96,7 @@ void McFont::init() {
     FT_Set_Char_Size(face, m_iFontSize * 64, m_iFontSize * 64, m_iFontDPI, m_iFontDPI);
 
     // create texture atlas
-    const int atlasSize = (m_iFontDPI > 96 ? (m_iFontDPI > 2 * 96 ? 2048 : 1024)
-                                           : 512);  // HACKHACK: hardcoded max atlas size, and heuristic
+    const int atlasSize = m_iFontSize * m_iFontDPI / 10;  // XXX: incorrect calculation
     engine->getResourceManager()->requestNextLoadUnmanaged();
     m_textureAtlas = engine->getResourceManager()->createTextureAtlas(atlasSize, atlasSize);
 

+ 2 - 0
src/Engine/Input/Keyboard.cpp

@@ -49,6 +49,7 @@ void Keyboard::onKeyDown(KEYCODE keyCode) {
         case KEY_CONTROL:
             m_bControlDown = true;
             break;
+        case 65511:  // linux
         case KEY_ALT:
             m_bAltDown = true;
             break;
@@ -73,6 +74,7 @@ void Keyboard::onKeyUp(KEYCODE keyCode) {
         case KEY_CONTROL:
             m_bControlDown = false;
             break;
+        case 65511:  // linux
         case KEY_ALT:
             m_bAltDown = false;
             break;

+ 0 - 1
src/Engine/Input/Mouse.cpp

@@ -139,7 +139,6 @@ void Mouse::update() {
     Vector2 nextPos = osMousePos;
 
     if(osCursorVisible || (!sensitivityAdjustmentNeeded && !mouse_raw_input.getBool()) || m_bAbsolute ||
-       env->getOS() == Environment::OS::OS_HORIZON ||
        env->getOS() == Environment::OS::OS_LINUX)  // HACKHACK: linux hack
     {
         // this block handles visible/active OS cursor movement without sensitivity adjustments, and absolute input

+ 0 - 89
src/Engine/Main/main_Horizon.cpp

@@ -1,89 +0,0 @@
-//================ Copyright (c) 2019, PG, All rights reserved. =================//
-//
-// Purpose:		main entry point (nintendo switch)
-//
-// $NoKeywords: $main
-//===============================================================================//
-
-#include "cbase.h"
-
-#ifdef __SWITCH__
-
-// #define MCENGINE_HORIZON_NXLINK
-
-#ifndef MCENGINE_FEATURE_SDL
-#error SDL2 is currently required for switch builds
-#endif
-
-#include <switch.h>
-#include <unistd.h>  // for close()
-
-#include "ConVar.h"
-#include "HorizonSDLEnvironment.h"
-
-static int s_nxlinkSock = -1;
-
-static void initNxLink() {
-    if(R_FAILED(socketInitializeDefault())) return;
-
-    s_nxlinkSock = nxlinkStdio();
-
-    if(s_nxlinkSock >= 0)
-        printf("printf output now goes to nxlink server");
-    else
-        socketExit();
-}
-
-static void deinitNxLink() {
-    if(s_nxlinkSock >= 0) {
-        close(s_nxlinkSock);
-        socketExit();
-        s_nxlinkSock = -1;
-    }
-}
-
-extern "C" void userAppInit() {
-#ifdef MCENGINE_HORIZON_NXLINK
-
-    initNxLink();
-
-#endif
-}
-
-extern "C" void userAppExit() {
-#ifdef MCENGINE_HORIZON_NXLINK
-
-    deinitNxLink();
-
-#endif
-}
-
-extern int mainSDL(int argc, char *argv[], SDLEnvironment *customSDLEnvironment);
-
-int main(int argc, char *argv[]) {
-    int ret = 0;
-
-    // TODO: user selection applet popup, somehow
-    /*
-    u128 userID;
-    accountGetPreselectedUser(&userID);
-    */
-
-    romfsInit();
-    {
-        appletSetScreenShotPermission(AppletScreenShotPermission_Enable);
-        appletSetFocusHandlingMode(
-            AppletFocusHandlingMode_SuspendHomeSleepNotify);  // TODO: seems broken? no notification is received when
-                                                              // going to sleep
-
-        // NOTE: yuzu emulator timing bug workaround (armGetSystemTick() is way too fast), uncomment for testing
-        // convar->getConVarByName("host_timescale")->setValue(0.018f);
-
-        ret = mainSDL(argc, argv, new HorizonSDLEnvironment());
-    }
-    romfsExit();
-
-    return ret;
-}
-
-#endif

+ 16 - 97
src/Engine/Main/main_SDL.cpp

@@ -19,7 +19,6 @@
 #include "ConVar.h"
 #include "ConsoleBox.h"
 #include "Engine.h"
-#include "HorizonSDLEnvironment.h"
 #include "Mouse.h"
 #include "Profiler.h"
 #include "SDL.h"
@@ -191,14 +190,6 @@ int mainSDL(int argc, char *argv[], SDLEnvironment *customSDLEnvironment) {
     while(g_bRunning) {
         VPROF_MAIN();
 
-        // HACKHACK: switch hack (usb mouse/keyboard support)
-#ifdef __SWITCH__
-
-        HorizonSDLEnvironment *horizonSDLenv = dynamic_cast<HorizonSDLEnvironment *>(environment);
-        if(horizonSDLenv != NULL) horizonSDLenv->update_before_winproc();
-
-#endif
-
         // handle window message queue
         {
             VPROF_BUDGET("SDL", VPROF_BUDGETGROUP_WNDPROC);
@@ -384,9 +375,7 @@ int mainSDL(int argc, char *argv[], SDLEnvironment *customSDLEnvironment) {
                         if(e.jbutton.button == 0)  // KEY_A
                         {
                             g_engine->onMouseLeftChange(true);
-                        } else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 10
-                                                                               : e.jbutton.button == 7) ||
-                                  e.jbutton.button == 1)  // KEY_PLUS/KEY_START || KEY_B
+                        } else if(e.jbutton.button == 7 || e.jbutton.button == 1)  // KEY_PLUS/KEY_START || KEY_B
                             g_engine->onKeyboardKeyDown(SDL_SCANCODE_ESCAPE);
                         else if(e.jbutton.button == 2)  // KEY_X
                         {
@@ -394,53 +383,20 @@ int mainSDL(int argc, char *argv[], SDLEnvironment *customSDLEnvironment) {
                             xDown = true;
                         } else if(e.jbutton.button == 3)  // KEY_Y
                             g_engine->onKeyboardKeyDown(SDL_SCANCODE_Y);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 21 : false) ||
-                                (env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 13
-                                                                             : false))  // right stick up || dpad up
-                            g_engine->onKeyboardKeyDown(SDL_SCANCODE_UP);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 23 : false) ||
-                                (env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 15
-                                                                             : false))  // right stick down || dpad down
-                            g_engine->onKeyboardKeyDown(SDL_SCANCODE_DOWN);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 20 : false) ||
-                                (env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 12
-                                                                             : false))  // right stick left || dpad left
-                            g_engine->onKeyboardKeyDown(SDL_SCANCODE_LEFT);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 22 : false) ||
-                                (env->getOS() == Environment::OS::OS_HORIZON
-                                     ? e.jbutton.button == 14
-                                     : false))  // right stick right || dpad right
-                            g_engine->onKeyboardKeyDown(SDL_SCANCODE_RIGHT);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 6
-                                                                             : e.jbutton.button == 4))  // KEY_L
-                            g_engine->onKeyboardKeyDown((
-                                env->getOS() == Environment::OS::OS_HORIZON ? SDL_SCANCODE_L : SDL_SCANCODE_BACKSPACE));
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 7
-                                                                             : e.jbutton.button == 5))  // KEY_R
-                            g_engine->onKeyboardKeyDown(
-                                (env->getOS() == Environment::OS::OS_HORIZON ? SDL_SCANCODE_R : SDL_SCANCODE_LSHIFT));
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 8
-                                                                             : false))  // KEY_ZL
-                            g_engine->onKeyboardKeyDown(SDL_SCANCODE_Z);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 9
-                                                                             : false))  // KEY_ZR
-                            g_engine->onKeyboardKeyDown(SDL_SCANCODE_V);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON
-                                     ? e.jbutton.button == 11
-                                     : e.jbutton.button == 6))  // KEY_MINUS/KEY_SELECT
+                        else if(e.jbutton.button == 4)  // KEY_L
+                            g_engine->onKeyboardKeyDown(SDL_SCANCODE_BACKSPACE);
+                        else if(e.jbutton.button == 5)  // KEY_R
+                            g_engine->onKeyboardKeyDown(SDL_SCANCODE_LSHIFT);
+                        else if(e.jbutton.button == 6)  // KEY_MINUS/KEY_SELECT
                             g_engine->onKeyboardKeyDown(SDL_SCANCODE_F1);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON
-                                     ? e.jbutton.button == 4
-                                     : e.jbutton.button == 8))  // left stick press
+                        else if(e.jbutton.button == 8)  // left stick press
                         {
                             // toggle options (CTRL + O)
                             g_engine->onKeyboardKeyDown(SDL_SCANCODE_LCTRL);
                             g_engine->onKeyboardKeyDown(SDL_SCANCODE_O);
                             g_engine->onKeyboardKeyUp(SDL_SCANCODE_LCTRL);
                             g_engine->onKeyboardKeyUp(SDL_SCANCODE_O);
-                        } else if((env->getOS() == Environment::OS::OS_HORIZON
-                                       ? e.jbutton.button == 5
-                                       : e.jbutton.button == 9))  // right stick press
+                        } else if(e.jbutton.button == 9)  // right stick press
                         {
                             if(xDown) {
                                 // toggle console
@@ -448,12 +404,6 @@ int mainSDL(int argc, char *argv[], SDLEnvironment *customSDLEnvironment) {
                                 g_engine->onKeyboardKeyDown(SDL_SCANCODE_F1);
                                 g_engine->onKeyboardKeyUp(SDL_SCANCODE_LSHIFT);
                                 g_engine->onKeyboardKeyUp(SDL_SCANCODE_F1);
-                            } else {
-#ifdef __SWITCH__
-
-                                ((HorizonSDLEnvironment *)environment)->showKeyboard();
-
-#endif
                             }
                         }
                         break;
@@ -461,9 +411,7 @@ int mainSDL(int argc, char *argv[], SDLEnvironment *customSDLEnvironment) {
                     case SDL_JOYBUTTONUP:
                         if(e.jbutton.button == 0)  // KEY_A
                             g_engine->onMouseLeftChange(false);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 10
-                                                                             : e.jbutton.button == 7) ||
-                                e.jbutton.button == 1)  // KEY_PLUS/KEY_START || KEY_B
+                        else if(e.jbutton.button == 7 || e.jbutton.button == 1)  // KEY_PLUS/KEY_START || KEY_B
                             g_engine->onKeyboardKeyUp(SDL_SCANCODE_ESCAPE);
                         else if(e.jbutton.button == 2)  // KEY_X
                         {
@@ -471,40 +419,11 @@ int mainSDL(int argc, char *argv[], SDLEnvironment *customSDLEnvironment) {
                             xDown = false;
                         } else if(e.jbutton.button == 3)  // KEY_Y
                             g_engine->onKeyboardKeyUp(SDL_SCANCODE_Y);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 21 : false) ||
-                                (env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 13
-                                                                             : false))  // right stick up || dpad up
-                            g_engine->onKeyboardKeyUp(SDL_SCANCODE_UP);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 23 : false) ||
-                                (env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 15
-                                                                             : false))  // right stick down || dpad down
-                            g_engine->onKeyboardKeyUp(SDL_SCANCODE_DOWN);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 20 : false) ||
-                                (env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 12
-                                                                             : false))  // right stick left || dpad left
-                            g_engine->onKeyboardKeyUp(SDL_SCANCODE_LEFT);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 22 : false) ||
-                                (env->getOS() == Environment::OS::OS_HORIZON
-                                     ? e.jbutton.button == 14
-                                     : false))  // right stick right || dpad right
-                            g_engine->onKeyboardKeyUp(SDL_SCANCODE_RIGHT);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 6
-                                                                             : e.jbutton.button == 4))  // KEY_L
-                            g_engine->onKeyboardKeyUp((
-                                env->getOS() == Environment::OS::OS_HORIZON ? SDL_SCANCODE_L : SDL_SCANCODE_BACKSPACE));
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 7
-                                                                             : e.jbutton.button == 5))  // KEY_R
-                            g_engine->onKeyboardKeyUp(
-                                (env->getOS() == Environment::OS::OS_HORIZON ? SDL_SCANCODE_R : SDL_SCANCODE_LSHIFT));
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 8
-                                                                             : false))  // KEY_ZL
-                            g_engine->onKeyboardKeyUp(SDL_SCANCODE_Z);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON ? e.jbutton.button == 9
-                                                                             : false))  // KEY_ZR
-                            g_engine->onKeyboardKeyUp(SDL_SCANCODE_V);
-                        else if((env->getOS() == Environment::OS::OS_HORIZON
-                                     ? e.jbutton.button == 11
-                                     : e.jbutton.button == 6))  // KEY_MINUS/KEY_SELECT
+                        else if(e.jbutton.button == 4)  // KEY_L
+                            g_engine->onKeyboardKeyUp(SDL_SCANCODE_BACKSPACE);
+                        else if(e.jbutton.button == 5)  // KEY_R
+                            g_engine->onKeyboardKeyUp(SDL_SCANCODE_LSHIFT);
+                        else if(e.jbutton.button == 6)  // KEY_MINUS/KEY_SELECT
                             g_engine->onKeyboardKeyUp(SDL_SCANCODE_F1);
                         break;
 
@@ -518,7 +437,7 @@ int mainSDL(int argc, char *argv[], SDLEnvironment *customSDLEnvironment) {
                             else
                                 m_fJoystick0YPercent = clamp<float>((float)e.jaxis.value / 32767.0f, -1.0f, 1.0f);
                         }
-                        if(env->getOS() != Environment::OS::OS_HORIZON) {
+                        {
                             // ZL/ZR
                             if(e.jaxis.axis == 2 || e.jaxis.axis == 5) {
                                 if(e.jaxis.axis == 2) {
@@ -550,7 +469,7 @@ int mainSDL(int argc, char *argv[], SDLEnvironment *customSDLEnvironment) {
 
                     case SDL_JOYHATMOTION:
                         // debugLog("joyhatmotion: hat %i : value = %i\n", (int)e.jhat.hat, (int)e.jhat.value);
-                        if(env->getOS() != Environment::OS::OS_HORIZON) {
+                        {
                             const bool wasHatUpDown = hatUpDown;
                             const bool wasHatDownDown = hatDownDown;
                             const bool wasHatLeftDown = hatLeftDown;

+ 0 - 561
src/Engine/Platform/HorizonSDLEnvironment.cpp

@@ -1,561 +0,0 @@
-//================ Copyright (c) 2019, PG, All rights reserved. =================//
-//
-// Purpose:		nintendo switch SDL environment
-//
-// $NoKeywords: $nxsdlenv
-//===============================================================================//
-
-#ifdef __SWITCH__
-
-#include "HorizonSDLEnvironment.h"
-
-#ifdef MCENGINE_FEATURE_SDL
-
-#include <dirent.h>
-#include <switch.h>
-#include <sys/stat.h>
-
-#include "ConVar.h"
-#include "Engine.h"
-#include "Mouse.h"
-#include "SDL.h"
-#include "SoundEngine.h"
-
-ConVar horizon_snd_chunk_size_docked("horizon_snd_chunk_size_docked", 512, FCVAR_NONE);
-ConVar horizon_snd_chunk_size_undocked("horizon_snd_chunk_size_undocked", 256, FCVAR_NONE);
-
-// HACKHACK: manual keyboard/mouse handling from sdl internals, until audio gets fixed, see
-// https://github.com/devkitPro/SDL/commit/b91efb18a1a4752c03d56594b079aa804fe4e9ea audio was broken here:
-// https://github.com/devkitPro/SDL/commit/51d12c191cdc7eb2ea7acca3daaf5e714b436128
-static const HidKeyboardScancode switch_scancodes[MCENGINE_HORIZON_SDL_NUM_SCANCODES_SWITCH] = {KBD_A,
-                                                                                                KBD_B,
-                                                                                                KBD_C,
-                                                                                                KBD_D,
-                                                                                                KBD_E,
-                                                                                                KBD_F,
-                                                                                                KBD_G,
-                                                                                                KBD_H,
-                                                                                                KBD_I,
-                                                                                                KBD_J,
-                                                                                                KBD_K,
-                                                                                                KBD_L,
-                                                                                                KBD_M,
-                                                                                                KBD_N,
-                                                                                                KBD_O,
-                                                                                                KBD_P,
-                                                                                                KBD_Q,
-                                                                                                KBD_R,
-                                                                                                KBD_S,
-                                                                                                KBD_T,
-                                                                                                KBD_U,
-                                                                                                KBD_V,
-                                                                                                KBD_W,
-                                                                                                KBD_X,
-                                                                                                KBD_Y,
-                                                                                                KBD_Z,
-                                                                                                KBD_1,
-                                                                                                KBD_2,
-                                                                                                KBD_3,
-                                                                                                KBD_4,
-                                                                                                KBD_5,
-                                                                                                KBD_6,
-                                                                                                KBD_7,
-                                                                                                KBD_8,
-                                                                                                KBD_9,
-                                                                                                KBD_0,
-                                                                                                KBD_ENTER,
-                                                                                                KBD_ESC,
-                                                                                                KBD_BACKSPACE,
-                                                                                                KBD_TAB,
-                                                                                                KBD_SPACE,
-                                                                                                KBD_MINUS,
-                                                                                                KBD_EQUAL,
-                                                                                                KBD_LEFTBRACE,
-                                                                                                KBD_RIGHTBRACE,
-                                                                                                KBD_BACKSLASH,
-                                                                                                KBD_HASHTILDE,
-                                                                                                KBD_SEMICOLON,
-                                                                                                KBD_APOSTROPHE,
-                                                                                                KBD_GRAVE,
-                                                                                                KBD_COMMA,
-                                                                                                KBD_DOT,
-                                                                                                KBD_SLASH,
-                                                                                                KBD_CAPSLOCK,
-                                                                                                KBD_F1,
-                                                                                                KBD_F2,
-                                                                                                KBD_F3,
-                                                                                                KBD_F4,
-                                                                                                KBD_F5,
-                                                                                                KBD_F6,
-                                                                                                KBD_F7,
-                                                                                                KBD_F8,
-                                                                                                KBD_F9,
-                                                                                                KBD_F10,
-                                                                                                KBD_F11,
-                                                                                                KBD_F12,
-                                                                                                KBD_SYSRQ,
-                                                                                                KBD_SCROLLLOCK,
-                                                                                                KBD_PAUSE,
-                                                                                                KBD_INSERT,
-                                                                                                KBD_HOME,
-                                                                                                KBD_PAGEUP,
-                                                                                                KBD_DELETE,
-                                                                                                KBD_END,
-                                                                                                KBD_PAGEDOWN,
-                                                                                                KBD_RIGHT,
-                                                                                                KBD_LEFT,
-                                                                                                KBD_DOWN,
-                                                                                                KBD_UP,
-                                                                                                KBD_NUMLOCK,
-                                                                                                KBD_KPSLASH,
-                                                                                                KBD_KPASTERISK,
-                                                                                                KBD_KPMINUS,
-                                                                                                KBD_KPPLUS,
-                                                                                                KBD_KPENTER,
-                                                                                                KBD_KP1,
-                                                                                                KBD_KP2,
-                                                                                                KBD_KP3,
-                                                                                                KBD_KP4,
-                                                                                                KBD_KP5,
-                                                                                                KBD_KP6,
-                                                                                                KBD_KP7,
-                                                                                                KBD_KP8,
-                                                                                                KBD_KP9,
-                                                                                                KBD_KP0,
-                                                                                                KBD_KPDOT,
-                                                                                                KBD_102ND,
-                                                                                                KBD_COMPOSE,
-                                                                                                KBD_POWER,
-                                                                                                KBD_KPEQUAL,
-                                                                                                KBD_F13,
-                                                                                                KBD_F14,
-                                                                                                KBD_F15,
-                                                                                                KBD_F16,
-                                                                                                KBD_F17,
-                                                                                                KBD_F18,
-                                                                                                KBD_F19,
-                                                                                                KBD_F20,
-                                                                                                KBD_F21,
-                                                                                                KBD_F22,
-                                                                                                KBD_F23,
-                                                                                                KBD_F24,
-                                                                                                KBD_OPEN,
-                                                                                                KBD_HELP,
-                                                                                                KBD_PROPS,
-                                                                                                KBD_FRONT,
-                                                                                                KBD_STOP,
-                                                                                                KBD_AGAIN,
-                                                                                                KBD_UNDO,
-                                                                                                KBD_CUT,
-                                                                                                KBD_COPY,
-                                                                                                KBD_PASTE,
-                                                                                                KBD_FIND,
-                                                                                                KBD_MUTE,
-                                                                                                KBD_VOLUMEUP,
-                                                                                                KBD_VOLUMEDOWN,
-                                                                                                KBD_CAPSLOCK_ACTIVE,
-                                                                                                KBD_NUMLOCK_ACTIVE,
-                                                                                                KBD_SCROLLLOCK_ACTIVE,
-                                                                                                KBD_KPCOMMA,
-                                                                                                KBD_KPLEFTPAREN,
-                                                                                                KBD_KPRIGHTPAREN,
-                                                                                                KBD_LEFTCTRL,
-                                                                                                KBD_LEFTSHIFT,
-                                                                                                KBD_LEFTALT,
-                                                                                                KBD_LEFTMETA,
-                                                                                                KBD_RIGHTCTRL,
-                                                                                                KBD_RIGHTSHIFT,
-                                                                                                KBD_RIGHTALT,
-                                                                                                KBD_RIGHTMETA,
-                                                                                                KBD_MEDIA_PLAYPAUSE,
-                                                                                                KBD_MEDIA_STOPCD,
-                                                                                                KBD_MEDIA_PREVIOUSSONG,
-                                                                                                KBD_MEDIA_NEXTSONG,
-                                                                                                KBD_MEDIA_EJECTCD,
-                                                                                                KBD_MEDIA_VOLUMEUP,
-                                                                                                KBD_MEDIA_VOLUMEDOWN,
-                                                                                                KBD_MEDIA_MUTE,
-                                                                                                KBD_MEDIA_WWW,
-                                                                                                KBD_MEDIA_BACK,
-                                                                                                KBD_MEDIA_FORWARD,
-                                                                                                KBD_MEDIA_STOP,
-                                                                                                KBD_MEDIA_FIND,
-                                                                                                KBD_MEDIA_SCROLLUP,
-                                                                                                KBD_MEDIA_SCROLLDOWN,
-                                                                                                KBD_MEDIA_EDIT,
-                                                                                                KBD_MEDIA_SLEEP,
-                                                                                                KBD_MEDIA_COFFEE,
-                                                                                                KBD_MEDIA_REFRESH,
-                                                                                                KBD_MEDIA_CALC};
-
-ConVar *HorizonSDLEnvironment::m_mouse_sensitivity_ref = NULL;
-
-uint8_t HorizonSDLEnvironment::locks = 0;
-bool HorizonSDLEnvironment::keystate[] = {0};
-uint64_t HorizonSDLEnvironment::prev_buttons = 0;
-
-HorizonSDLEnvironment::HorizonSDLEnvironment() : SDLEnvironment(NULL) {
-    if(m_mouse_sensitivity_ref == NULL) m_mouse_sensitivity_ref = convar->getConVarByName("mouse_sensitivity");
-
-    // the switch has its own internal deadzone handling already applied
-    convar->getConVarByName("sdl_joystick0_deadzone")->setValue(0.0f);
-
-    m_bDocked = false;
-
-    m_fLastMouseDeltaTime = 0.0f;
-}
-
-HorizonSDLEnvironment::~HorizonSDLEnvironment() {}
-
-void HorizonSDLEnvironment::update() {
-    // when switching between docked/undocked and sleeping/awake, restart audio and switch resolution
-    // NOTE: for some reason, 256 byte audio buffer causes crackling only when docked, therefore we dynamically switch
-    // between 256 and 512 NOTE: if the console goes to sleep we get audio crackling later, therefore also restart audio
-    // engine when waking up
-    const bool dockedChange = (isDocked() != m_bDocked);
-    if(dockedChange) {
-        if(dockedChange) m_bDocked = !m_bDocked;
-
-        debugLog("HorizonSDLEnvironment: Switching to docked = %i (or waking up) ...\n", (int)m_bDocked);
-
-        // restart sound engine
-        convar->getConVarByName("snd_output_device")->setValue("Default");
-
-        // switch resolution
-        if(dockedChange) {
-            const Vector2 resolution = (m_bDocked ? Vector2(1920, 1080) : Vector2(1280, 720));
-            SDL_SetWindowSize(m_window, resolution.x, resolution.y);
-        }
-    }
-
-    SDLEnvironment::update();
-}
-
-void HorizonSDLEnvironment::update_before_winproc() {
-    hidScanInput();  // for manually handling keyboard/mouse
-
-    // HACKHACK: manually handle keyboard
-    {
-        for(int i = 0; i < MCENGINE_HORIZON_SDL_NUM_SCANCODES_SWITCH; i++) {
-            const HidKeyboardScancode keyCode = switch_scancodes[i];
-
-            if(hidKeyboardHeld(keyCode) && !keystate[i]) {
-                switch(keyCode) {
-                    case SDL_SCANCODE_NUMLOCKCLEAR:
-                        if(!(locks & 0x1)) {
-                            engine->onKeyboardKeyDown(keyCode);
-                            locks |= 0x1;
-                        } else {
-                            engine->onKeyboardKeyUp(keyCode);
-                            locks &= ~0x1;
-                        }
-                        break;
-
-                    case SDL_SCANCODE_CAPSLOCK:
-                        if(!(locks & 0x2)) {
-                            engine->onKeyboardKeyDown(keyCode);
-                            locks |= 0x2;
-                        } else {
-                            engine->onKeyboardKeyUp(keyCode);
-                            locks &= ~0x2;
-                        }
-                        break;
-
-                    case SDL_SCANCODE_SCROLLLOCK:
-                        if(!(locks & 0x4)) {
-                            engine->onKeyboardKeyDown(keyCode);
-                            locks |= 0x4;
-                        } else {
-                            engine->onKeyboardKeyUp(keyCode);
-                            locks &= ~0x4;
-                        }
-                        break;
-
-                    default:
-                        engine->onKeyboardKeyDown(keyCode);
-                }
-
-                keystate[i] = true;
-            } else if(!hidKeyboardHeld(keyCode) && keystate[i]) {
-                switch(keyCode) {
-                    case SDL_SCANCODE_CAPSLOCK:
-                    case SDL_SCANCODE_NUMLOCKCLEAR:
-                    case SDL_SCANCODE_SCROLLLOCK:
-                        break;
-
-                    default:
-                        engine->onKeyboardKeyUp(keyCode);
-                }
-
-                keystate[i] = false;
-            }
-        }
-    }
-
-    // HACKHACK: manually handle mouse
-    {
-        uint64_t buttons = hidMouseButtonsHeld();
-        uint64_t changed_buttons = buttons ^ prev_buttons;
-
-        // buttons
-        if(changed_buttons & MOUSE_LEFT) {
-            if(prev_buttons & MOUSE_LEFT)
-                engine->onMouseLeftChange(false);
-            else
-                engine->onMouseLeftChange(true);
-        }
-
-        if(changed_buttons & MOUSE_RIGHT) {
-            if(prev_buttons & MOUSE_RIGHT)
-                engine->onMouseRightChange(false);
-            else
-                engine->onMouseRightChange(true);
-        }
-
-        if(changed_buttons & MOUSE_MIDDLE) {
-            if(prev_buttons & MOUSE_MIDDLE)
-                engine->onMouseMiddleChange(false);
-            else
-                engine->onMouseMiddleChange(true);
-        }
-
-        prev_buttons = buttons;
-
-        MousePosition newMousePos;
-        hidMouseRead(&newMousePos);
-
-        // raw delta
-        // NOTE: the delta values are framerate dependent (poll-dependent), so basically unusable
-        const int32_t dx = (int32_t)newMousePos.velocityX * 2;
-        const int32_t dy = (int32_t)newMousePos.velocityY * 2;
-
-        if(dx != 0 || dy != 0) {
-            m_fLastMouseDeltaTime = engine->getTime();
-            engine->onMouseRawMove(dx, dy);
-        }
-
-        // position
-        // NOTE: the only use case for mouse control is docked mode. the raw coordinates from hidMouseRead() are
-        // restricted to 720p, so upscaling only gives 1.5x1.5 pixel accuracy at most
-        if(engine->getTime() < m_fLastMouseDeltaTime + 0.5f) {
-            const float rawRangeX = 1280.0f;
-            const float rawRangeY = 720.0f;
-
-            m_vMousePos.x =
-                ((((float)newMousePos.x - rawRangeX / 2.0f) * m_mouse_sensitivity_ref->getFloat()) + rawRangeX / 2.0f) /
-                rawRangeX;
-            m_vMousePos.y =
-                ((((float)newMousePos.y - rawRangeY / 2.0f) * m_mouse_sensitivity_ref->getFloat()) + rawRangeY / 2.0f) /
-                rawRangeY;
-
-            m_vMousePos *= engine->getScreenSize();  // scale to 1080p
-
-            m_vMousePos.x = clamp<float>(m_vMousePos.x, 0.0f, engine->getScreenSize().x);
-            m_vMousePos.y = clamp<float>(m_vMousePos.y, 0.0f, engine->getScreenSize().y);
-
-            engine->getMouse()->onPosChange(m_vMousePos);
-        }
-
-        // scrolling
-        const int scrollVelocityX = (int)newMousePos.scrollVelocityX;
-        const int scrollVelocityY = (int)newMousePos.scrollVelocityY;
-        if(scrollVelocityY != 0) engine->onMouseWheelHorizontal(scrollVelocityY);
-        if(scrollVelocityX != 0) engine->onMouseWheelVertical(scrollVelocityX);
-    }
-}
-
-Environment::OS HorizonSDLEnvironment::getOS() { return Environment::OS::OS_HORIZON; }
-
-void HorizonSDLEnvironment::sleep(unsigned int us) { svcSleepThread(us * 1000); }
-
-UString HorizonSDLEnvironment::getUsername() {
-    UString uUsername = convar->getConVarByName("name")->getString();
-
-    // this was directly taken from the libnx examples
-
-    Result rc = 0;
-
-    AccountUid userID;
-    /// bool account_selected = 0;
-    AccountProfile profile;
-    AccountUserData userdata;
-    AccountProfileBase profilebase;
-
-    char username[0x21];
-
-    memset(&userdata, 0, sizeof(userdata));
-    memset(&profilebase, 0, sizeof(profilebase));
-
-    rc = accountInitialize(AccountServiceType_Application);
-    if(R_FAILED(rc)) debugLog("accountInitialize() failed: 0x%x\n", rc);
-
-    if(R_SUCCEEDED(rc)) {
-        rc = accountGetLastOpenedUser(&userID);
-
-        if(R_FAILED(rc)) debugLog("accountGetActiveUser() failed: 0x%x\n", rc);
-        /// else if(!account_selected)
-        ///{
-        ///	debugLog("No user is currently selected.\n");
-        ///     rc = -1;
-        /// }
-
-        if(R_SUCCEEDED(rc)) {
-            /// debugLog("Current userID: 0x%lx 0x%lx\n", (u64)(userID>>64), (u64)userID);
-
-            rc = accountGetProfile(&profile, userID);
-
-            if(R_FAILED(rc)) debugLog("accountGetProfile() failed: 0x%x\n", rc);
-        }
-
-        if(R_SUCCEEDED(rc)) {
-            rc = accountProfileGet(&profile, &userdata, &profilebase);  // userdata is otional, see libnx acc.h.
-
-            if(R_FAILED(rc)) debugLog("accountProfileGet() failed: 0x%x\n", rc);
-
-            if(R_SUCCEEDED(rc)) {
-                memset(username, 0, sizeof(username));
-                strncpy(username, profilebase.nickname,
-                        sizeof(username) - 1);  // even though profilebase.username usually has a NUL-terminator, don't
-                                                // assume it does for safety.
-                debugLog("Username: %s\n", username);
-                uUsername = UString(username);
-            }
-            accountProfileClose(&profile);
-        }
-        accountExit();
-    }
-
-    return uUsername;
-}
-
-std::vector<UString> HorizonSDLEnvironment::getFilesInFolder(UString folder) {
-    std::vector<UString> files;
-
-    DIR *dir;
-    struct dirent *ent;
-
-    dir = opendir(folder.toUtf8());
-    if(dir == NULL) return files;
-
-    while((ent = readdir(dir))) {
-        const char *name = ent->d_name;
-        UString uName = UString(name);
-        UString fullName = folder;
-        fullName.append(uName);
-
-        struct stat stDirInfo;
-        int statret = stat(
-            fullName.toUtf8(),
-            &stDirInfo);  // NOTE: lstat() always returns 0 in st_mode, seems broken, therefore using stat() for now
-        if(statret < 0) {
-            /// perror (name);
-            /// debugLog("HorizonSDLEnvironment::getFilesInFolder() error, stat() returned %i!\n", lstatret);
-            continue;
-        }
-
-        if(!S_ISDIR(stDirInfo.st_mode)) files.push_back(uName);
-    }
-    closedir(dir);
-
-    return files;
-}
-
-std::vector<UString> HorizonSDLEnvironment::getFoldersInFolder(UString folder) {
-    std::vector<UString> folders;
-
-    DIR *dir;
-    struct dirent *ent;
-
-    dir = opendir(folder.toUtf8());
-    if(dir == NULL) return folders;
-
-    while((ent = readdir(dir))) {
-        const char *name = ent->d_name;
-        UString uName = UString(name);
-        UString fullName = folder;
-        fullName.append(uName);
-
-        struct stat stDirInfo;
-        int statret = stat(
-            fullName.toUtf8(),
-            &stDirInfo);  // NOTE: lstat() always returns 0 in st_mode, seems broken, therefore using stat() for now
-        if(statret < 0) {
-            /// perror (name);
-            /// debugLog("HorizonSDLEnvironment::getFoldersInFolder() error, stat() returned %i!\n", lstatret);
-            continue;
-        }
-
-        if(S_ISDIR(stDirInfo.st_mode)) folders.push_back(uName);
-    }
-    closedir(dir);
-
-    return folders;
-}
-
-std::vector<UString> HorizonSDLEnvironment::getLogicalDrives() {
-    std::vector<UString> drives;
-
-    drives.push_back("sdmc");
-    drives.push_back("romfs");
-
-    return drives;
-}
-
-UString HorizonSDLEnvironment::getFolderFromFilePath(std::string filepath) {
-    // NOTE: #include <libgen.h> and dirname() is undefined, seems like it does not exist anywhere in devkitpro except
-    // the header file
-    debugLog("WARNING: HorizonSDLEnvironment::getFolderFromFilePath() not available!\n");
-    return filepath;
-}
-
-Vector2 HorizonSDLEnvironment::getMousePos() { return m_vMousePos; }
-
-void HorizonSDLEnvironment::setMousePos(int x, int y) {
-    m_vMousePos.x = x;
-    m_vMousePos.y = y;
-}
-
-void HorizonSDLEnvironment::showKeyboard() {
-    // TODO: this is broken. it only works if svcSetHeapSize(&addr, 0xf000000); // 128 MB, but that's not enough for the
-    // rest of the game. switching sizes also crashes later.
-    // https://gbatemp.net/threads/software-keyboard-example-failing.528518/
-
-    SwkbdConfig kbd;
-    Result rc = swkbdCreate(&kbd, 0);
-    printf("swkbdCreate(): 0x%x\n", rc);
-
-    if(R_SUCCEEDED(rc)) {
-        swkbdConfigMakePresetDefault(&kbd);
-
-        char str[256] = {0};
-        rc = swkbdShow(&kbd, str, sizeof(str));
-        printf("swkbdShow(): 0x%x\n", rc);
-
-        swkbdClose(&kbd);
-
-        if(R_SUCCEEDED(rc)) {
-            UString uStr = UString(str);
-            for(int i = 0; i < uStr.length(); i++) {
-                engine->onKeyboardChar(uStr[i]);
-            }
-        }
-    }
-}
-
-bool HorizonSDLEnvironment::isDocked() { return (appletGetOperationMode() == AppletOperationMode_Docked); }
-
-int HorizonSDLEnvironment::getMemAvailableMB() {
-    uint64_t numBytes = 0;
-    svcGetInfo(&numBytes, 6, CUR_PROCESS_HANDLE, 0);
-    return (int)(numBytes / 1024 / 1024);
-}
-
-int HorizonSDLEnvironment::getMemUsedMB() {
-    uint64_t numBytes = 0;
-    svcGetInfo(&numBytes, 7, CUR_PROCESS_HANDLE, 0);
-    return (int)(numBytes / 1024 / 1024);
-}
-
-#endif
-
-#endif

+ 0 - 75
src/Engine/Platform/HorizonSDLEnvironment.h

@@ -1,75 +0,0 @@
-//================ Copyright (c) 2019, PG, All rights reserved. =================//
-//
-// Purpose:		nintendo switch SDL environment
-//
-// $NoKeywords: $nxsdlenv
-//===============================================================================//
-
-#ifdef __SWITCH__
-
-#ifndef HORIZONSDLENVIRONMENT_H
-#define HORIZONSDLENVIRONMENT_H
-
-#include "SDLEnvironment.h"
-
-#ifdef MCENGINE_FEATURE_SDL
-
-#define MCENGINE_HORIZON_SDL_NUM_SCANCODES_SWITCH 160
-
-class ConVar;
-
-class HorizonSDLEnvironment : public SDLEnvironment {
-   public:
-    HorizonSDLEnvironment();
-    virtual ~HorizonSDLEnvironment();
-
-    virtual void update();
-    void update_before_winproc();  // HACKHACK: mouse/keyboard
-
-    // system
-    virtual OS getOS();
-    virtual void sleep(unsigned int us);
-
-    // user
-    virtual UString getUsername();
-
-    // file IO
-    virtual std::vector<UString> getFilesInFolder(UString folder);
-    virtual std::vector<UString> getFoldersInFolder(UString folder);
-    virtual std::vector<UString> getLogicalDrives();
-    virtual std::string getFolderFromFilePath(std::string filepath);
-
-    // window
-    int getDPI() { return 96; }
-
-    // mouse
-    Vector2 getMousePos();
-    void setMousePos(int x, int y);
-
-    // ILLEGAL:
-    void showKeyboard();
-    bool isDocked();
-    int getMemAvailableMB();
-    int getMemUsedMB();
-
-   private:
-    static ConVar *m_mouse_sensitivity_ref;
-
-    bool m_bDocked;
-
-    Vector2 m_vMousePos;
-
-    uint32_t m_sensorHandles[4];
-
-    // HACKHACK: manual keyboard/mouse handling
-    static uint8_t locks;
-    static bool keystate[MCENGINE_HORIZON_SDL_NUM_SCANCODES_SWITCH];
-    static uint64_t prev_buttons;
-    float m_fLastMouseDeltaTime;
-};
-
-#endif
-
-#endif
-
-#endif

+ 0 - 34
src/Engine/Platform/HorizonThread.cpp

@@ -1,34 +0,0 @@
-//================ Copyright (c) 2020, PG, All rights reserved. =================//
-//
-// Purpose:		horizon implementation of thread
-//
-// $NoKeywords: $nxthread $os
-//===============================================================================//
-
-#ifdef __SWITCH__
-
-#include "HorizonThread.h"
-
-#include "Engine.h"
-
-HorizonThread::HorizonThread(McThread::START_ROUTINE start_routine, void *arg) : BaseThread() {
-    Result rc = threadCreate((Thread *)&m_thread, (ThreadFunc)start_routine, arg, NULL, 0x1000000, 0x2B, 2);
-
-    m_bReady = !R_FAILED(rc);
-
-    if(!m_bReady)
-        debugLog("HorizonThread Error: threadCreate() returned %i!\n", (int)rc);
-    else
-        threadStart((Thread *)&m_thread);
-}
-
-HorizonThread::~HorizonThread() {
-    if(!m_bReady) return;
-
-    m_bReady = false;
-
-    threadWaitForExit((Thread *)&m_thread);
-    threadClose((Thread *)&m_thread);
-}
-
-#endif

+ 0 - 32
src/Engine/Platform/HorizonThread.h

@@ -1,32 +0,0 @@
-//================ Copyright (c) 2020, PG, All rights reserved. =================//
-//
-// Purpose:		horizon implementation of thread
-//
-// $NoKeywords: $nxthread $os
-//===============================================================================//
-
-#ifdef __SWITCH__
-
-#ifndef HORIZONTHREAD_H
-#define HORIZONTHREAD_H
-
-#include <switch.h>
-
-#include "Thread.h"
-
-class HorizonThread : public BaseThread {
-   public:
-    HorizonThread(McThread::START_ROUTINE start_routine, void *arg);
-    virtual ~HorizonThread();
-
-    bool isReady() { return m_bReady; }
-
-   private:
-    bool m_bReady;
-
-    Thread m_thread;
-};
-
-#endif
-
-#endif

+ 0 - 41
src/Engine/Platform/HorizonTimer.cpp

@@ -1,41 +0,0 @@
-//================ Copyright (c) 2019, PG, All rights reserved. =================//
-//
-// Purpose:		fps timer
-//
-// $NoKeywords: $nxtime $os
-//===============================================================================//
-
-#ifdef __SWITCH__
-
-#include "HorizonTimer.h"
-
-#include <switch.h>
-
-HorizonTimer::HorizonTimer() : BaseTimer() {
-    m_startTime = 0;
-    m_currentTime = 0;
-
-    m_delta = 0.0;
-    m_elapsedTime = 0.0;
-    m_elapsedTimeMS = 0;
-}
-
-void HorizonTimer::start() {
-    m_startTime = armGetSystemTick();
-    m_currentTime = m_startTime;
-
-    m_delta = 0.0;
-    m_elapsedTime = 0.0;
-    m_elapsedTimeMS = 0;
-}
-
-void HorizonTimer::update() {
-    const uint64_t nowTime = armGetSystemTick();
-
-    m_delta = (double)armTicksToNs(nowTime - m_currentTime) / 1000000000.0;
-    m_elapsedTime = (double)armTicksToNs(nowTime - m_startTime) / 1000000000.0;
-    m_elapsedTimeMS = armTicksToNs(nowTime - m_startTime) / (uint64_t)1000000;
-    m_currentTime = nowTime;
-}
-
-#endif

+ 0 - 38
src/Engine/Platform/HorizonTimer.h

@@ -1,38 +0,0 @@
-//================ Copyright (c) 2019, PG, All rights reserved. =================//
-//
-// Purpose:		fps timer
-//
-// $NoKeywords: $nxtime $os
-//===============================================================================//
-
-#ifdef __SWITCH__
-
-#ifndef HORIZONTIMER_H
-#define HORIZONTIMER_H
-
-#include "Timer.h"
-
-class HorizonTimer : public BaseTimer {
-   public:
-    HorizonTimer();
-    virtual ~HorizonTimer() { ; }
-
-    virtual void start() override;
-    virtual void update() override;
-
-    virtual inline double getDelta() const override { return m_delta; }
-    virtual inline double getElapsedTime() const override { return m_elapsedTime; }
-    virtual inline uint64_t getElapsedTimeMS() const override { return m_elapsedTimeMS; }
-
-   private:
-    uint64_t m_startTime;
-    uint64_t m_currentTime;
-
-    double m_delta;
-    double m_elapsedTime;
-    uint64_t m_elapsedTimeMS;
-};
-
-#endif
-
-#endif

+ 0 - 7
src/Engine/Platform/OpenGLHeaders.h

@@ -53,12 +53,5 @@
 
 #endif
 
-#ifdef __SWITCH__
-
-#include <EGL/egl.h>
-#include <GLES2/gl2.h>
-
-#endif
-
 #endif
 // clang-format on

+ 0 - 6
src/Engine/Platform/Thread.cpp

@@ -11,7 +11,6 @@
 
 #include "ConVar.h"
 #include "Engine.h"
-#include "HorizonThread.h"
 
 // pthread implementation of Thread
 class PosixThread : public BaseThread {
@@ -49,12 +48,7 @@ ConVar *McThread::debug = &debug_thread;
 
 McThread::McThread(START_ROUTINE start_routine, void *arg) {
     m_baseThread = NULL;
-
-#if defined(__SWITCH__)
-    m_baseThread = new HorizonThread(start_routine, arg);
-#else
     m_baseThread = new PosixThread(start_routine, arg);
-#endif
 }
 
 McThread::~McThread() { SAFE_DELETE(m_baseThread); }

+ 0 - 8
src/Engine/Platform/Timer.cpp

@@ -19,10 +19,6 @@
 
 #include "MacOSTimer.h"
 
-#elif defined __SWITCH__
-
-#include "HorizonTimer.h"
-
 #endif
 
 Timer::Timer() {
@@ -40,10 +36,6 @@ Timer::Timer() {
 
     m_timer = new MacOSTimer();
 
-#elif defined __SWITCH__
-
-    m_timer = new HorizonTimer();
-
 #else
 
 #error Missing Timer implementation for OS!

+ 0 - 17
src/Engine/ResourceManager.cpp

@@ -45,33 +45,16 @@ ConVar debug_rm_("debug_rm", false, FCVAR_NONE);
 
 ConVar *ResourceManager::debug_rm = &debug_rm_;
 
-// HACKHACK: do this with env->getOS() or something
-#ifdef __SWITCH__
-
-const char *ResourceManager::PATH_DEFAULT_IMAGES = "romfs:/materials/";
-const char *ResourceManager::PATH_DEFAULT_FONTS = "romfs:/fonts/";
-const char *ResourceManager::PATH_DEFAULT_SHADERS = "romfs:/shaders/";
-
-#else
-
 const char *ResourceManager::PATH_DEFAULT_IMAGES = MCENGINE_DATA_DIR "materials/";
 const char *ResourceManager::PATH_DEFAULT_FONTS = MCENGINE_DATA_DIR "fonts/";
 const char *ResourceManager::PATH_DEFAULT_SHADERS = MCENGINE_DATA_DIR "shaders/";
 
-#endif
-
 ResourceManager::ResourceManager() {
     m_bNextLoadAsync = false;
     m_iNumResourceInitPerFrameLimit = 1;
 
     m_loadingWork.reserve(32);
 
-    // OS specific engine settings/overrides
-    if(env->getOS() == Environment::OS::OS_HORIZON) {
-        rm_numthreads.setValue(1.0f);
-        rm_numthreads.setDefaultFloat(1.0f);
-    }
-
     // create loader threads
     for(int i = 0; i < rm_numthreads.getInt(); i++) {
         ResourceManagerLoaderThread *loaderThread = new ResourceManagerLoaderThread();

+ 1 - 1
src/Engine/Sound.cpp

@@ -235,7 +235,7 @@ void Sound::setPositionMS(unsigned long ms) {
 void Sound::setVolume(float volume) {
     if(!m_bReady) return;
 
-    m_fVolume = clamp<float>(volume, 0.0f, 1.0f);
+    m_fVolume = clamp<float>(volume, 0.0f, 2.0f);
 
     for(auto channel : getActiveChannels()) {
         BASS_ChannelSetAttribute(channel, BASS_ATTRIB_VOL, m_fVolume);

+ 1 - 0
src/Engine/Sound.h

@@ -4,6 +4,7 @@
 #include <bass.h>
 #include <bass_fx.h>
 #include <bassasio.h>
+#include <bassloud.h>
 #include <bassmix.h>
 #include <basswasapi.h>
 

+ 37 - 20
src/Engine/SoundEngine.cpp

@@ -17,7 +17,6 @@
 #include "ConVar.h"
 #include "Engine.h"
 #include "Environment.h"
-#include "HorizonSDLEnvironment.h"
 #include "Osu.h"
 #include "OsuBeatmap.h"
 #include "OsuOptionsMenu.h"
@@ -127,6 +126,15 @@ SoundEngine::SoundEngine() {
         return;
     }
 
+    auto loud_version = BASS_Loudness_GetVersion();
+    debugLog("SoundEngine: BASSloud version = 0x%08x\n", loud_version);
+    if(HIWORD(loud_version) != BASSVERSION) {
+        engine->showMessageErrorFatal("Fatal Sound Error",
+                                      "An incorrect version of the BASSloud library file was loaded!");
+        engine->shutdown();
+        return;
+    }
+
 #ifdef _WIN32
     auto asio_version = BASS_ASIO_GetVersion();
     debugLog("SoundEngine: BASSASIO version = 0x%08x\n", asio_version);
@@ -148,7 +156,6 @@ SoundEngine::SoundEngine() {
 #endif
 
     BASS_SetConfig(BASS_CONFIG_BUFFER, 100);
-    BASS_SetConfig(BASS_CONFIG_NET_BUFFER, 500);
 
     // all beatmaps timed to non-iTunesSMPB + 529 sample deletion offsets on old dlls pre 2015
     BASS_SetConfig(BASS_CONFIG_MP3_OLDGAPS, 1);
@@ -325,6 +332,10 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
     debugLog("SoundEngine: initializeOutputDevice( %s ) ...\n", device.name.toUtf8());
 
     if(m_currentOutputDevice.driver == OutputDriver::BASS) {
+        BASS_SetDevice(0);
+        BASS_Free();
+        BASS_SetDevice(m_currentOutputDevice.id);
+
         BASS_Free();
     } else if(m_currentOutputDevice.driver == OutputDriver::BASS_ASIO) {
 #ifdef _WIN32
@@ -376,26 +387,32 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
             BASS_SetConfig(BASS_CONFIG_DEV_PERIOD, snd_dev_period.getInt());
     }
 
-    // ASIO and WASAPI: Initialize BASS on "No sound" device
+    const int freq = snd_freq.getInt();
+    HWND hwnd = NULL;
+#ifdef _WIN32
+    const WinEnvironment *winEnv = dynamic_cast<WinEnvironment *>(env);
+    hwnd = winEnv->getHwnd();
+#endif
+
     int bass_device_id = device.id;
     unsigned int runtimeFlags = BASS_DEVICE_STEREO | BASS_DEVICE_FREQ;
     if(device.driver == OutputDriver::BASS) {
-        runtimeFlags |= BASS_DEVICE_DSOUND;
+        // Regular BASS: we still want a "No sound" device to check for loudness
+        if(!BASS_Init(0, freq, runtimeFlags | BASS_DEVICE_NOSPEAKER, hwnd, NULL)) {
+            m_bReady = false;
+            engine->showMessageError("Sound Error", UString::format("BASS_Init(0) failed (%i)!", BASS_ErrorGetCode()));
+            return false;
+        }
     } else if(device.driver == OutputDriver::BASS_ASIO || device.driver == OutputDriver::BASS_WASAPI) {
+        // ASIO and WASAPI: Initialize BASS on "No sound" device
         runtimeFlags |= BASS_DEVICE_NOSPEAKER;
         bass_device_id = 0;
     }
 
-    const int freq = snd_freq.getInt();
-    HWND hwnd = NULL;
-#ifdef _WIN32
-    const WinEnvironment *winEnv = dynamic_cast<WinEnvironment *>(env);
-    hwnd = winEnv->getHwnd();
-#endif
-
     if(!BASS_Init(bass_device_id, freq, runtimeFlags, hwnd, NULL)) {
         m_bReady = false;
-        engine->showMessageError("Sound Error", UString::format("BASS_Init() failed (%i)!", BASS_ErrorGetCode()));
+        engine->showMessageError("Sound Error",
+                                 UString::format("BASS_Init(%d) failed (%i)!", bass_device_id, BASS_ErrorGetCode()));
         return false;
     }
 
@@ -509,10 +526,6 @@ bool SoundEngine::initializeOutputDevice(OUTPUT_DEVICE device) {
 #endif
     }
 
-    if(env->getOS() == Environment::OS::OS_HORIZON) {
-        osu_universal_offset_hardcoded.setValue(-45.0f);
-    }
-
     m_bReady = true;
     m_currentOutputDevice = device;
     snd_output_device.setValue(m_currentOutputDevice.name);
@@ -666,9 +679,14 @@ bool SoundEngine::setOutputDevice(OUTPUT_DEVICE device) {
 
     unsigned long prevMusicPositionMS = 0;
     if(bancho.osu != nullptr) {
-        if(!bancho.osu->isInPlayMode() && bancho.osu->getSelectedBeatmap() != NULL &&
-           bancho.osu->getSelectedBeatmap()->getMusic() != NULL)
+        if(bancho.osu->isInPlayMode()) {
+            // Kick the player out of play mode, since restarting SoundEngine during gameplay is not supported.
+            // XXX: Make OsuBeatmap work without a running SoundEngine
+            bancho.osu->getSelectedBeatmap()->fail();
+            bancho.osu->getSelectedBeatmap()->stop(true);
+        } else if(bancho.osu->getSelectedBeatmap()->getMusic() != NULL) {
             prevMusicPositionMS = bancho.osu->getSelectedBeatmap()->getMusic()->getPositionMS();
+        }
     }
 
     auto previous = m_currentOutputDevice;
@@ -683,8 +701,7 @@ bool SoundEngine::setOutputDevice(OUTPUT_DEVICE device) {
         bancho.osu->m_optionsMenu->onOutputDeviceResetUpdate();
 
         // start playing music again after audio device changed
-        if(!bancho.osu->isInPlayMode() && bancho.osu->getSelectedBeatmap() != NULL &&
-           bancho.osu->getSelectedBeatmap()->getMusic() != NULL) {
+        if(!bancho.osu->isInPlayMode() && bancho.osu->getSelectedBeatmap()->getMusic() != NULL) {
             bancho.osu->getSelectedBeatmap()->unloadMusic();
             bancho.osu->getSelectedBeatmap()->select();  // (triggers preview music play)
             bancho.osu->getSelectedBeatmap()->getMusic()->setPositionMS(prevMusicPositionMS);