Пример #1
0
        public RulesetStore(RealmAccess realm, Storage?storage = null)
        {
            realmAccess = realm;

            // On android in release configuration assemblies are loaded from the apk directly into memory.
            // We cannot read assemblies from cwd, so should check loaded assemblies instead.
            loadFromAppDomain();

            // This null check prevents Android from attempting to load the rulesets from disk,
            // as the underlying path "AppContext.BaseDirectory", despite being non-nullable, it returns null on android.
            // See https://github.com/xamarin/xamarin-android/issues/3489.
            if (RuntimeInfo.StartupDirectory != null)
            {
                loadFromDisk();
            }

            // the event handler contains code for resolving dependency on the game assembly for rulesets located outside the base game directory.
            // It needs to be attached to the assembly lookup event before the actual call to loadUserRulesets() else rulesets located out of the base game directory will fail
            // to load as unable to locate the game core assembly.
            AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly;

            var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets");

            if (rulesetStorage != null)
            {
                loadUserRulesets(rulesetStorage);
            }

            addMissingRulesets();
        }
Пример #2
0
        private void load(RealmAccess realm)
        {
            string rulesetName = Ruleset?.ShortName;

            var bindings = realm.Run(r => r.All <RealmKeyBinding>()
                                     .Where(b => b.RulesetName == rulesetName && b.Variant == variant)
                                     .Detach());

            foreach (var defaultGroup in Defaults.GroupBy(d => d.Action))
            {
                int intKey = (int)defaultGroup.Key;

                // one row per valid action.
                Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.ActionInt.Equals(intKey)).ToList())
                {
                    AllowMainMouseButtons = Ruleset != null,
                    Defaults = defaultGroup.Select(d => d.KeyCombination)
                });
            }

            Add(new ResetButton
            {
                Action = () => Children.OfType <KeyBindingRow>().ForEach(k => k.RestoreDefaults())
            });
        }
Пример #3
0
        public RealmFileStore(RealmAccess realm, Storage storage)
        {
            this.realm = realm;

            Storage = storage.GetStorageForDirectory(@"files");
            Store   = new StorageBackedResourceStore(Storage);
        }
Пример #4
0
        public SkinModelManager(Storage storage, RealmAccess realm, IStorageResourceProvider skinResources)
            : base(storage, realm)
        {
            this.skinResources = skinResources;

            // can be removed 20220420.
            populateMissingHashes();
        }
Пример #5
0
        public void SetUp()
        {
            var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()));

            storage = new NativeStorage(directory.FullName);

            realm           = new RealmAccess(storage, "test");
            keyBindingStore = new RealmKeyBindingStore(realm, new ReadableKeyCombinationProvider());
        }
Пример #6
0
        public ScoreManager(RulesetStore rulesets, Func <BeatmapManager> beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler,
                            Func <BeatmapDifficultyCache> difficulties = null, OsuConfigManager configManager = null)
        {
            this.realm         = realm;
            this.scheduler     = scheduler;
            this.difficulties  = difficulties;
            this.configManager = configManager;

            scoreModelManager = new ScoreModelManager(rulesets, beatmaps, storage, realm);
        }
Пример #7
0
        protected RulesetConfigManager(SettingsStore store, RulesetInfo ruleset, int?variant = null)
        {
            realm = store?.Realm;

            rulesetName = ruleset.ShortName;

            this.variant = variant ?? 0;

            Load();

            InitialiseDefaults();
        }
Пример #8
0
 private static long getFileSize(Storage testStorage, RealmAccess realm)
 {
     try
     {
         using (var stream = testStorage.GetStream(realm.Filename))
             return(stream?.Length ?? 0);
     }
     catch
     {
         // windows runs may error due to file still being open.
         return(0);
     }
 }
Пример #9
0
        public SkinManager(Storage storage, RealmAccess realm, GameHost host, IResourceStore <byte[]> resources, AudioManager audio, Scheduler scheduler)
            : base(storage, realm)
        {
            this.audio     = audio;
            this.scheduler = scheduler;
            this.host      = host;
            this.resources = resources;

            userFiles = new StorageBackedResourceStore(storage.GetStorageForDirectory("files"));

            skinImporter = new SkinImporter(storage, realm, this)
            {
                PostNotification = obj => PostNotification?.Invoke(obj),
            };

            var defaultSkins = new[]
            {
                DefaultLegacySkin = new DefaultLegacySkin(this),
                DefaultSkin       = new DefaultSkin(this),
            };

            // Ensure the default entries are present.
            realm.Write(r =>
            {
                foreach (var skin in defaultSkins)
                {
                    if (r.Find <SkinInfo>(skin.SkinInfo.ID) == null)
                    {
                        r.Add(skin.SkinInfo.Value);
                    }
                }
            });

            CurrentSkinInfo.ValueChanged += skin =>
            {
                CurrentSkin.Value = skin.NewValue.PerformRead(GetSkin);
            };

            CurrentSkin.Value         = DefaultSkin;
            CurrentSkin.ValueChanged += skin =>
            {
                if (!skin.NewValue.SkinInfo.Equals(CurrentSkinInfo.Value))
                {
                    throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead.");
                }

                SourceChanged?.Invoke();
            };
        }
Пример #10
0
        public override void SetUp()
        {
            storage = new TemporaryNativeStorage("realm-benchmark");
            storage.DeleteDirectory(string.Empty);

            realm = new RealmAccess(storage, "client");

            realm.Run(r =>
            {
                realm.Write(c => c.Add(TestResources.CreateTestBeatmapSetInfo(rulesets: new[] { new OsuRuleset().RulesetInfo })));
            });

            updateThread = new UpdateThread(() => { }, null);
            updateThread.Start();
        }
Пример #11
0
        protected void RunTestWithRealmAsync(Func <RealmAccess, Storage, Task> testAction, [CallerMemberName] string caller = "")
        {
            using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
            {
                host.Run(new RealmTestGame(async() =>
                {
                    var testStorage = storage.GetStorageForDirectory(caller);

                    using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
                    {
                        Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
                        await testAction(realm, testStorage);

                        realm.Dispose();

                        Logger.Log($"Final database size: {getFileSize(testStorage, realm)}");
                        realm.Compact();
                    }
                }));
            }
        }
Пример #12
0
        protected void RunTestWithRealm(Action <RealmAccess, OsuStorage> testAction, [CallerMemberName] string caller = "")
        {
            using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller))
            {
                host.Run(new RealmTestGame(() =>
                {
                    // ReSharper disable once AccessToDisposedClosure
                    var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller));

                    using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME))
                    {
                        Logger.Log($"Running test using realm file {testStorage.GetFullPath(realm.Filename)}");
                        testAction(realm, testStorage);

                        realm.Dispose();

                        Logger.Log($"Final database size: {getFileSize(testStorage, realm)}");
                        realm.Compact();
                        Logger.Log($"Final database size after compact: {getFileSize(testStorage, realm)}");
                    }
                }));
            }
        }
Пример #13
0
        public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider?api, AudioManager audioManager, IResourceStore <byte[]> gameResources, GameHost?host = null,
                              WorkingBeatmap?defaultBeatmap = null, bool performOnlineLookups = false)
            : base(storage, realm)
        {
            if (performOnlineLookups)
            {
                if (api == null)
                {
                    throw new ArgumentNullException(nameof(api), "API must be provided if online lookups are required.");
                }

                onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
            }

            var userResources = new RealmFileStore(realm, storage).Store;

            BeatmapTrackStore = audioManager.GetTrackStore(userResources);

            beatmapImporter = CreateBeatmapImporter(storage, realm, rulesets, onlineBeatmapLookupQueue);
            beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj);

            workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host);
        }
Пример #14
0
 public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore <byte[]> resources, GameHost host, WorkingBeatmap defaultBeatmap)
     : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap)
 {
 }
Пример #15
0
 protected RealmArchiveModelManager(Storage storage, RealmAccess realm)
     : base(storage, realm)
 {
     realmFileStore = new RealmFileStore(realm, storage);
 }
Пример #16
0
        private void load(OsuConfigManager config, Framework.Game game, RealmAccess realm, IAPIProvider api)
        {
            // prevent user from changing beatmap while the intro is still running.
            beatmap = Beatmap.BeginLease(false);

            MenuVoice = config.GetBindable <bool>(OsuSetting.MenuVoice);
            MenuMusic = config.GetBindable <bool>(OsuSetting.MenuMusic);

            if (api.LocalUser.Value.IsSupporter)
            {
                AddInternal(skinnableSeeya = new SkinnableSound(new SampleInfo(SeeyaSampleName)));
            }
            else
            {
                seeya = audio.Samples.Get(SeeyaSampleName);
            }

            // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection.
            if (!MenuMusic.Value)
            {
                realm.Run(r =>
                {
                    var usableBeatmapSets = r.All <BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected).AsRealmCollection();

                    int setCount = usableBeatmapSets.Count;

                    if (setCount > 0)
                    {
                        var found = usableBeatmapSets[RNG.Next(0, setCount - 1)].Beatmaps.FirstOrDefault();

                        if (found != null)
                        {
                            initialBeatmap = beatmaps.GetWorkingBeatmap(found);
                        }
                    }
                });
            }

            // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available.
            if (initialBeatmap == null)
            {
                // Intro beatmaps are generally made using the osu! ruleset.
                // It might not be present in test projects for other rulesets.
                bool osuRulesetPresent = rulesets.GetRuleset(0) != null;

                if (!loadThemedIntro() && osuRulesetPresent)
                {
                    // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state.
                    // this could happen if a user has nuked their files store. for now, reimport to repair this.
                    var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).GetResultSafely();

                    import?.PerformWrite(b => b.Protected = true);

                    loadThemedIntro();
                }
            }

            bool loadThemedIntro()
            {
                var setInfo = beatmaps.QueryBeatmapSet(b => b.Protected && b.Hash == BeatmapHash);

                if (setInfo == null)
                {
                    return(false);
                }

                setInfo.PerformRead(s =>
                {
                    if (s.Beatmaps.Count == 0)
                    {
                        return;
                    }

                    initialBeatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps.First());
                });

                return(UsingThemedIntro = initialBeatmap != null);
            }
        }
Пример #17
0
 public BeatmapImporter(Storage storage, RealmAccess realm, BeatmapOnlineLookupQueue?onlineLookupQueue = null)
     : base(storage, realm)
 {
     this.onlineLookupQueue = onlineLookupQueue;
 }
Пример #18
0
 public ScoreImporter(RulesetStore rulesets, Func <BeatmapManager> beatmaps, Storage storage, RealmAccess realm)
     : base(storage, realm)
 {
     this.rulesets = rulesets;
     this.beatmaps = beatmaps;
 }
Пример #19
0
 public TestBeatmapModelManager(TestBeatmapManager testBeatmapManager, Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
     : base(databaseAccess, storage, beatmapOnlineLookupQueue)
 {
     this.testBeatmapManager = testBeatmapManager;
 }
Пример #20
0
 public SettingsStore(RealmAccess realm)
 {
     Realm = realm;
 }
Пример #21
0
 protected virtual BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue?onlineLookupQueue) =>
 new BeatmapModelManager(realm, storage, onlineLookupQueue);
Пример #22
0
 protected override BeatmapModelManager CreateBeatmapModelManager(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue onlineLookupQueue)
 {
     return(new TestBeatmapModelManager(storage, realm, onlineLookupQueue));
 }
Пример #23
0
 public TestBeatmapModelManager(Storage storage, RealmAccess databaseAccess, BeatmapOnlineLookupQueue beatmapOnlineLookupQueue)
     : base(databaseAccess, storage, beatmapOnlineLookupQueue)
 {
 }
Пример #24
0
        private void prepareDetachedRulesets(RealmAccess realmAccess)
        {
            realmAccess.Write(realm =>
            {
                var rulesets = realm.All <RulesetInfo>();

                List <Ruleset> instances = LoadedAssemblies.Values
                                           .Select(r => Activator.CreateInstance(r) as Ruleset)
                                           .Where(r => r != null)
                                           .Select(r => r.AsNonNull())
                                           .ToList();

                // add all legacy rulesets first to ensure they have exclusive choice of primary key.
                foreach (var r in instances.Where(r => r is ILegacyRuleset))
                {
                    if (realm.All <RulesetInfo>().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.OnlineID) == null)
                    {
                        realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
                    }
                }

                // add any other rulesets which have assemblies present but are not yet in the database.
                foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
                {
                    if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
                    {
                        var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName);

                        if (existingSameShortName != null)
                        {
                            // even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName.
                            // this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one.
                            // in such cases, update the instantiation info of the existing entry to point to the new one.
                            existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo;
                        }
                        else
                        {
                            realm.Add(new RulesetInfo(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.OnlineID));
                        }
                    }
                }

                List <RulesetInfo> detachedRulesets = new List <RulesetInfo>();

                // perform a consistency check and detach final rulesets from realm for cross-thread runtime usage.
                foreach (var r in rulesets.OrderBy(r => r.OnlineID))
                {
                    try
                    {
                        var resolvedType = Type.GetType(r.InstantiationInfo)
                                           ?? throw new RulesetLoadException(@"Type could not be resolved");

                        var instanceInfo = (Activator.CreateInstance(resolvedType) as Ruleset)?.RulesetInfo
                                           ?? throw new RulesetLoadException(@"Instantiation failure");

                        // If a ruleset isn't up-to-date with the API, it could cause a crash at an arbitrary point of execution.
                        // To eagerly handle cases of missing implementations, enumerate all types here and mark as non-available on throw.
                        resolvedType.Assembly.GetTypes();

                        r.Name              = instanceInfo.Name;
                        r.ShortName         = instanceInfo.ShortName;
                        r.InstantiationInfo = instanceInfo.InstantiationInfo;
                        r.Available         = true;

                        detachedRulesets.Add(r.Clone());
                    }
                    catch (Exception ex)
                    {
                        r.Available = false;
                        Logger.Log($"Could not load ruleset {r}: {ex.Message}");
                    }
                }

                availableRulesets.AddRange(detachedRulesets.OrderBy(r => r));
            });
        }
Пример #25
0
 public RealmRulesetStore(RealmAccess realm, Storage?storage = null)
     : base(storage)
 {
     prepareDetachedRulesets(realm);
 }
Пример #26
0
        private void load(GameHost host, RealmAccess realm)
        {
            SettingsButton blockAction;
            SettingsButton unblockAction;

            Children = new Drawable[]
            {
                new SettingsButton
                {
                    Text   = DebugSettingsStrings.ClearAllCaches,
                    Action = host.Collect
                },
                new SettingsButton
                {
                    Text   = DebugSettingsStrings.CompactRealm,
                    Action = () =>
                    {
                        // Blocking operations implicitly causes a Compact().
                        using (realm.BlockAllOperations())
                        {
                        }
                    }
                },
                blockAction = new SettingsButton
                {
                    Text = "Block realm",
                },
                unblockAction = new SettingsButton
                {
                    Text = "Unblock realm",
                },
            };

            blockAction.Action = () =>
            {
                try
                {
                    var token = realm.BlockAllOperations();

                    blockAction.Enabled.Value = false;

                    // As a safety measure, unblock after 10 seconds.
                    // This is to handle the case where a dev may block, but then something on the update thread
                    // accesses realm and blocks for eternity.
                    Task.Factory.StartNew(() =>
                    {
                        Thread.Sleep(10000);
                        unblock();
                    });

                    unblockAction.Action = unblock;

                    void unblock()
                    {
                        if (token == null)
                        {
                            return;
                        }

                        token?.Dispose();
                        token = null;

                        Scheduler.Add(() =>
                        {
                            blockAction.Enabled.Value = true;
                            unblockAction.Action      = null;
                        });
                    }
                }
                catch (Exception e)
                {
                    Logger.Error(e, "Blocking realm failed");
                }
            };
        }
Пример #27
0
 protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm, RulesetStore rulesets, BeatmapOnlineLookupQueue?onlineLookupQueue) =>
 new BeatmapImporter(storage, realm, onlineLookupQueue);
Пример #28
0
 public RealmKeyBindingStore(RealmAccess realm, ReadableKeyCombinationProvider keyCombinationProvider)
 {
     this.realm = realm;
     this.keyCombinationProvider = keyCombinationProvider;
 }
Пример #29
0
 public NonOptimisedBeatmapImporter(RealmAccess realm, Storage storage)
     : base(realm, storage)
 {
 }
Пример #30
0
        private void load(ReadableKeyCombinationProvider keyCombinationProvider)
        {
            try
            {
                using (var str = File.OpenRead(typeof(OsuGameBase).Assembly.Location))
                    VersionHash = str.ComputeMD5Hash();
            }
            catch
            {
                // special case for android builds, which can't read DLLs from a packed apk.
                // should eventually be handled in a better way.
                VersionHash = $"{Version}-{RuntimeInfo.OS}".ComputeMD5Hash();
            }

            Resources.AddStore(new DllResourceStore(OsuResources.ResourceAssembly));

            if (Storage.Exists(DatabaseContextFactory.DATABASE_NAME))
            {
                dependencies.Cache(EFContextFactory = new DatabaseContextFactory(Storage));
            }

            dependencies.Cache(realm = new RealmAccess(Storage, "client", EFContextFactory));

            dependencies.CacheAs <RulesetStore>(RulesetStore = new RealmRulesetStore(realm, Storage));
            dependencies.CacheAs <IRulesetStore>(RulesetStore);

            Decoder.RegisterDependencies(RulesetStore);

            // Backup is taken here rather than in EFToRealmMigrator to avoid recycling realm contexts
            // after initial usages below. It can be moved once a direction is established for handling re-subscription.
            // See https://github.com/ppy/osu/pull/16547 for more discussion.
            if (EFContextFactory != null)
            {
                const string backup_folder = "backups";

                string migration = $"before_final_migration_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}";

                EFContextFactory.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.db"));
                realm.CreateBackup(Path.Combine(backup_folder, $"client.{migration}.realm"));

                using (var source = Storage.GetStream("collection.db"))
                {
                    if (source != null)
                    {
                        using (var destination = Storage.GetStream(Path.Combine(backup_folder, $"collection.{migration}.db"), FileAccess.Write, FileMode.CreateNew))
                            source.CopyTo(destination);
                    }
                }
            }

            dependencies.CacheAs(Storage);

            var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore <byte[]>(Resources, @"Textures")));

            largeStore.AddStore(Host.CreateTextureLoaderStore(new OnlineStore()));
            dependencies.Cache(largeStore);

            dependencies.CacheAs(this);
            dependencies.CacheAs(LocalConfig);

            InitialiseFonts();

            Audio.Samples.PlaybackConcurrency = SAMPLE_CONCURRENCY;

            dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
            dependencies.CacheAs <ISkinSource>(SkinManager);

            EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration) new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();

            MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;

            dependencies.CacheAs(API ??= new APIAccess(LocalConfig, endpoints, VersionHash));

            dependencies.CacheAs(spectatorClient   = new OnlineSpectatorClient(endpoints));
            dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints));

            var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);

            // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
            dependencies.Cache(ScoreManager   = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, () => difficultyCache, LocalConfig));
            dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, performOnlineLookups: true));

            dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API));
            dependencies.Cache(ScoreDownloader   = new ScoreModelDownloader(ScoreManager, API));

            dependencies.Cache(difficultyCache = new BeatmapDifficultyCache());
            AddInternal(difficultyCache);

            dependencies.Cache(userCache = new UserLookupCache());
            AddInternal(userCache);

            dependencies.Cache(beatmapCache = new BeatmapLookupCache());
            AddInternal(beatmapCache);

            var scorePerformanceManager = new ScorePerformanceCache();

            dependencies.Cache(scorePerformanceManager);
            AddInternal(scorePerformanceManager);

            dependencies.CacheAs <IRulesetConfigCache>(rulesetConfigCache = new RulesetConfigCache(realm, RulesetStore));

            var powerStatus = CreateBatteryInfo();

            if (powerStatus != null)
            {
                dependencies.CacheAs(powerStatus);
            }

            dependencies.Cache(SessionStatics = new SessionStatics());
            dependencies.Cache(new OsuColour());

            RegisterImportHandler(BeatmapManager);
            RegisterImportHandler(ScoreManager);
            RegisterImportHandler(SkinManager);

            // drop track volume game-wide to leave some head-room for UI effects / samples.
            // this means that for the time being, gameplay sample playback is louder relative to the audio track, compared to stable.
            // we may want to revisit this if users notice or complain about the difference (consider this a bit of a trial).
            Audio.Tracks.AddAdjustment(AdjustableProperty.Volume, globalTrackVolumeAdjust);

            Beatmap = new NonNullableBindable <WorkingBeatmap>(defaultBeatmap);

            dependencies.CacheAs <IBindable <WorkingBeatmap> >(Beatmap);
            dependencies.CacheAs(Beatmap);

            // add api components to hierarchy.
            if (API is APIAccess apiAccess)
            {
                AddInternal(apiAccess);
            }
            AddInternal(spectatorClient);
            AddInternal(multiplayerClient);

            AddInternal(rulesetConfigCache);

            GlobalActionContainer globalBindings;

            base.Content.Add(new SafeAreaContainer
            {
                SafeAreaOverrideEdges = SafeAreaOverrideEdges,
                RelativeSizeAxes      = Axes.Both,
                Child = CreateScalingContainer().WithChildren(new Drawable[]
                {
                    (MenuCursorContainer = new MenuCursorContainer
                    {
                        RelativeSizeAxes = Axes.Both
                    }).WithChild(content = new OsuTooltipContainer(MenuCursorContainer.Cursor)
                    {
                        RelativeSizeAxes = Axes.Both
                    }),
                    // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything.
                    globalBindings = new GlobalActionContainer(this)
                })
            });

            KeyBindingStore = new RealmKeyBindingStore(realm, keyCombinationProvider);
            KeyBindingStore.Register(globalBindings, RulesetStore.AvailableRulesets);

            dependencies.Cache(globalBindings);

            PreviewTrackManager previewTrackManager;

            dependencies.Cache(previewTrackManager = new PreviewTrackManager(BeatmapManager.BeatmapTrackStore));
            Add(previewTrackManager);

            AddInternal(MusicController = new MusicController());
            dependencies.CacheAs(MusicController);

            Ruleset.BindValueChanged(onRulesetChanged);
            Beatmap.BindValueChanged(onBeatmapChanged);
        }