From 1a407df9e5ecaab5cea63d55cb675475b31398a9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 20:50:21 +0000 Subject: [PATCH 1/7] fix: Argon skin corruption + ppy cherry-pick + surface format + safe-mode shader wipe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changes: - Cherry-pick ppy editor toolbox icons commit: new OsuIcon entries (EditorHit, EditorDrumRoll, EditorSwell, EditorFruit, EditorJuiceStream, EditorBananaShower, EditorNote, EditorHoldNote) and all 8 composition tools updated to use SpriteIcon instead of BeatmapStatisticIcon. Bump osu-resources to 2026.519.0. - Fix Argon skin visual corruption (white circles, off-hue TrianglesV2 on buttons): Force-wipe the shader pipeline cache in ForceOpenGLRendererIfSafeMode(). When a Vulkan session dies mid-compile (ANR), the shader cache sentinel is already marked 'done for v241' but partial/misaligned SPIR-V blobs remain on disk. The rescue OpenGL session then picks up these stale shaders which were compiled against the old GlobalUniformData layout (pre-UniformPadding12 alignment fix). Result: the masking uniform (CornerRadius clip) and gradient colour uniform are read from wrong struct offsets → Argon hit circles appear as white squares, button TrianglesV2 shows wrong hues. Wiping unconditionally on every safe-mode entry fixes this. - Make pre-SDL Window.SetFormat(RGBA8888) unconditional (remove IsVulkanConfigured guard). Ensures 32-bit colour in both Vulkan and OpenGL modes; prevents an RGB565 initial surface allocation that causes colour-channel artefacts in early frames of the OpenGL rescue session. Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/aff86d54-a6b0-494c-b672-682c2bfbd9ff Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com> --- osu.Android/LogManagement.cs | 38 ++++++++++++++++++ osu.Android/OsuGameActivity.cs | 40 ++++++++++--------- .../Edit/BananaShowerCompositionTool.cs | 5 ++- .../Edit/FruitCompositionTool.cs | 5 ++- .../Edit/JuiceStreamCompositionTool.cs | 5 ++- .../Edit/HoldNoteCompositionTool.cs | 5 ++- .../Edit/NoteCompositionTool.cs | 5 ++- .../Edit/DrumRollCompositionTool.cs | 5 ++- .../Edit/HitCompositionTool.cs | 5 ++- .../Edit/SwellCompositionTool.cs | 5 ++- osu.Game/Graphics/OsuIcon.cs | 32 +++++++++++++++ osu.Game/osu.Game.csproj | 2 +- 12 files changed, 117 insertions(+), 35 deletions(-) diff --git a/osu.Android/LogManagement.cs b/osu.Android/LogManagement.cs index 5ce4a7089a74..c93986f0eec4 100644 --- a/osu.Android/LogManagement.cs +++ b/osu.Android/LogManagement.cs @@ -481,6 +481,44 @@ public static void ForceOpenGLRendererIfSafeMode() string? root = resolveStorageRoot(); if (root == null) return; + // The previous launch died (Vulkan ANR or native crash) before the + // shader compilation burst finished. The on-disk pipeline cache can + // contain: + // • SPIR-V blobs compiled against the old GlobalUniformData layout + // (before the UniformPadding12 alignment fix in 2026.519.1) if + // the WipeShaderCacheOnceForVersion sentinel was already written + // but the Vulkan session was killed mid-compile. + // • Partially-written or incomplete pipeline objects from the + // interrupted Vulkan compile pass. + // + // Either case causes visual corruption on the rescue OpenGL session: + // – Argon hit circles render as white rectangles (masking uniform + // at wrong struct offset → CornerRadius clipping broken). + // – TrianglesV2 buttons show the wrong hue (gradient colour data + // at wrong offset → DrawColourInfo.Colour.Interpolate returns + // garbage channel values). + // + // Wipe the shader cache unconditionally here — bypassing the + // version-code sentinel — so the OpenGL rescue session always starts + // from a clean slate. The sentinel is NOT reset: the next normal + // (non-safe-mode) launch will still skip the version wipe and reuse + // the freshly-compiled OpenGL cache from this rescue session. + string shaderCacheDir = Path.Combine(root, "cache", "shaders"); + + if (Directory.Exists(shaderCacheDir)) + { + try + { + Directory.Delete(shaderCacheDir, recursive: true); + Logger.Log("[osu!] Android safe-mode: shader cache wiped to ensure clean OpenGL recompilation.", LoggingTarget.Runtime); + } + catch (Exception e) + { + Debug.WriteLine($"[osu!] LogManagement: safe-mode shader cache wipe failed ({e.Message}); falling back to per-entry sweep"); + sweepDirectoryBestEffort(shaderCacheDir); + } + } + string iniPath = Path.Combine(root, "framework.ini"); if (!File.Exists(iniPath)) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 96b61fda9042..fe871beae404 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -246,25 +246,29 @@ protected override void OnCreate(Bundle? savedInstanceState) // Stamp RGBA8888 at the Window level BEFORE SDL creates its SurfaceView inside // base.OnCreate(). Android's default SurfaceView pixel format on many high-density - // Samsung / Qualcomm panels is RGB565. SDL3 only calls SurfaceHolder.setFormat( - // RGBA8888) for the OpenGL path — the Vulkan path inherits the window default. - // Setting the format here, before SDL attaches its SurfaceView, ensures the - // SurfaceView is born with RGBA8888 and eliminates the format-change teardown - // (SurfaceHolder.SetFormat in DecorView.Post) that otherwise fires mid-Vulkan-init - // and can produce the "Draw thread did not acknowledge teardown within 250ms" warning. - // The DecorView.Post call and the SurfaceChanged reactive guard are retained as - // belt-and-braces fallbacks for timing windows or OEM variants where this hint is - // not honoured by the SurfaceView allocation path. - if (LogManagement.IsVulkanConfigured()) + // Samsung / Qualcomm panels is RGB565. Setting RGBA8888 here (before SDL attaches + // its SurfaceView) ensures the SurfaceView is born with full 32-bit colour in both + // Vulkan and OpenGL modes: + // - Vulkan: the Veldrid swapchain can request VK_FORMAT_R8G8B8A8_SRGB / BGRA8888 + // directly, but the underlying ANativeWindow must also support RGBA8888 — a + // Window born at RGB565 forces a surface teardown (and the + // "Draw thread did not acknowledge teardown within 250ms" warning) when Veldrid + // later calls ANativeWindow_setBuffersGeometry with RGBA8888. + // - OpenGL safe-mode (after a Vulkan crash): SDL3 does call + // SurfaceHolder.setFormat(RGBA8888) for EGL surfaces, but it only does so AFTER + // the SurfaceView is created. Pre-stamping the Window format here guarantees + // the initial SurfaceView allocation happens at RGBA8888, avoiding a brief + // RGB565 render pass that can leave colour-channel artefacts visible in the + // first few frames. + // Belt-and-braces fallbacks (DecorView.Post watcher, SurfaceChanged reactive guard) + // are retained for OEM variants where this Window-level hint is not honoured. + try { - try - { - Window?.SetFormat(global::Android.Graphics.Format.Rgba8888); - } - catch (Exception e) - { - Debug.WriteLine($"[osu!] Pre-SDL Window.SetFormat(RGBA8888) failed (non-fatal): {e.Message}"); - } + Window?.SetFormat(global::Android.Graphics.Format.Rgba8888); + } + catch (Exception e) + { + Debug.WriteLine($"[osu!] Pre-SDL Window.SetFormat(RGBA8888) failed (non-fatal): {e.Message}"); } // BASS AAudio: if the user opted in, tell BASS to open an AAudio device instead diff --git a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs index be93ba0242ed..3ef47c92c42b 100644 --- a/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/BananaShowerCompositionTool.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Beatmaps; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -17,7 +18,7 @@ public BananaShowerCompositionTool() { } - public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorBananaShower }; public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new BananaShowerPlacementBlueprint(); } diff --git a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs index 71c1e6690320..0ec79ef6ad7a 100644 --- a/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/FruitCompositionTool.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Beatmaps; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -17,7 +18,7 @@ public FruitCompositionTool() { } - public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorFruit }; public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new FruitPlacementBlueprint(); } diff --git a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs index 7a567820f370..7a4bfeee8200 100644 --- a/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs +++ b/osu.Game.Rulesets.Catch/Edit/JuiceStreamCompositionTool.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Beatmaps; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Edit; @@ -17,7 +18,7 @@ public JuiceStreamCompositionTool() { } - public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorJuiceStream }; public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new JuiceStreamPlacementBlueprint(); } diff --git a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs index 592f8d9af7c9..6c433f0b16d5 100644 --- a/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/HoldNoteCompositionTool.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Beatmaps; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Edit.Blueprints; @@ -16,7 +17,7 @@ public HoldNoteCompositionTool() { } - public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorHoldNote }; public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HoldNotePlacementBlueprint(); } diff --git a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs index 2e54d635251c..5c2f8c8fcfda 100644 --- a/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs +++ b/osu.Game.Rulesets.Mania/Edit/NoteCompositionTool.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Beatmaps; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Edit.Blueprints; @@ -17,7 +18,7 @@ public NoteCompositionTool() { } - public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorNote }; public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new NotePlacementBlueprint(); } diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs index ba0fda67712b..5c7ee13e1a1a 100644 --- a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Beatmaps; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; @@ -17,7 +18,7 @@ public DrumRollCompositionTool() { } - public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Sliders); + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorDrumRoll }; public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); } diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs index f58defba83e8..4eb00988091d 100644 --- a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Beatmaps; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; @@ -17,7 +18,7 @@ public HitCompositionTool() { } - public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Circles); + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorHit }; public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); } diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs index 4ec623e29ef1..67e0987abe1f 100644 --- a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs @@ -2,7 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Game.Beatmaps; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; @@ -17,7 +18,7 @@ public SwellCompositionTool() { } - public override Drawable CreateIcon() => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Spinners); + public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.EditorSwell }; public override HitObjectPlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); } diff --git a/osu.Game/Graphics/OsuIcon.cs b/osu.Game/Graphics/OsuIcon.cs index 1796372bb3ca..acebccc94610 100644 --- a/osu.Game/Graphics/OsuIcon.cs +++ b/osu.Game/Graphics/OsuIcon.cs @@ -93,6 +93,14 @@ public static class OsuIcon public static IconUsage EditorHitCircle => get(OsuIconMapping.EditorHitCircle); public static IconUsage EditorSlider => get(OsuIconMapping.EditorSlider); public static IconUsage EditorSpinner => get(OsuIconMapping.EditorSpinner); + public static IconUsage EditorHit => get(OsuIconMapping.EditorHit); + public static IconUsage EditorDrumRoll => get(OsuIconMapping.EditorDrumRoll); + public static IconUsage EditorSwell => get(OsuIconMapping.EditorSwell); + public static IconUsage EditorFruit => get(OsuIconMapping.EditorFruit); + public static IconUsage EditorJuiceStream => get(OsuIconMapping.EditorJuiceStream); + public static IconUsage EditorNote => get(OsuIconMapping.EditorNote); + public static IconUsage EditorHoldNote => get(OsuIconMapping.EditorHoldNote); + public static IconUsage EditorBananaShower => get(OsuIconMapping.EditorBananaShower); public static IconUsage EditorGrid => get(OsuIconMapping.EditorGrid); public static IconUsage EditorAddControlPoint => get(OsuIconMapping.EditorAddControlPoint); public static IconUsage EditorConvertToStream => get(OsuIconMapping.EditorConvertToStream); @@ -409,6 +417,30 @@ private enum OsuIconMapping [Description(@"Editor/spinner")] EditorSpinner, + [Description(@"Editor/hit")] + EditorHit, + + [Description(@"Editor/drum-roll")] + EditorDrumRoll, + + [Description(@"Editor/swell")] + EditorSwell, + + [Description(@"Editor/fruit")] + EditorFruit, + + [Description(@"Editor/juice-stream")] + EditorJuiceStream, + + [Description(@"Editor/banana-shower")] + EditorBananaShower, + + [Description(@"Editor/note")] + EditorNote, + + [Description(@"Editor/hold-note")] + EditorHoldNote, + [Description(@"Editor/grid")] EditorGrid, diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 30a42f4732c4..f6c5d087d4f6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -49,7 +49,7 @@ would silently fall back to the 4 KB-aligned nuget.org build and emit NU1903. --> - + From 22d4059563a0910287f71e02a178c8596c31391a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 19 May 2026 21:40:35 +0000 Subject: [PATCH 2/7] fix(Android): OnPause watchdog to prevent Vulkan IMMEDIATE-mode ANR Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/e20f5f4d-6cae-4655-808b-9f6bda1c985e Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com> --- osu.Android/OsuGameActivity.cs | 69 ++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index fe871beae404..6965f34bcd7a 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -967,6 +967,75 @@ public void SurfaceDestroyed(ISurfaceHolder holder) } } + protected override void OnPause() + { + // Root cause of the recurring Vulkan IMMEDIATE-mode ANR (process-runtime ~50s): + // + // 1. Samsung Game Booster (or any surface-lifecycle event) fires onPause() on the + // Java main thread at ~50 seconds of active Vulkan gameplay. + // 2. SDL3's native onPause() sends SDL_EVENT_DID_ENTER_BACKGROUND through its event + // filter synchronously on the calling (Java main) thread. + // 3. The event filter calls Window.Suspended → GameHost.Suspend() → + // ThreadRunner.Suspend() → DrawThread.Pause() → WaitForState(Paused). + // 4. WaitForState spins: `while (state != Paused) Thread.Sleep(1)` — NO TIMEOUT. + // 5. The draw thread is stuck inside vkQueuePresentKHR (Vulkan IMMEDIATE mode; + // FrameSync=ActualUnlimited) due to an Adreno 7xx driver stall. It can only + // check pauseRequested at the START of the next frame — which never comes. + // 6. Java main thread spins forever → input dispatching times out after 10s → ANR. + // + // Fix: watchdog the OnPause() call. If base.OnPause() hasn't returned within 7 seconds + // (leaving a 3-second margin before the 10-second ANR), the draw thread is conclusively + // stuck in the driver. Kill the process immediately for a clean restart rather than a + // frozen 10-second ANR. + // + // We intentionally do NOT set FLAG_STARTUP_IN_PROGRESS (safe-mode) before killing. + // The startup completed successfully; this is a mid-session driver hang triggered by + // a transient system event (Game Booster first-session overlay). The next launch will + // retry Vulkan normally. Safe-mode is reserved for launch-time hangs where the renderer + // itself cannot initialize. + // + // Only active for Vulkan: OpenGL's eglSwapBuffers cannot stall indefinitely in the way + // vkQueuePresentKHR can, so OpenGL sessions are not at risk of this ANR pattern. + if (LogManagement.IsVulkanConfigured()) + { + var pauseCompleted = new ManualResetEventSlim(false); + + ThreadPool.QueueUserWorkItem(_ => + { + const int watchdog_ms = 7000; + + if (pauseCompleted.Wait(watchdog_ms)) + return; + + // base.OnPause() has not returned — draw thread is conclusively stuck in + // vkQueuePresentKHR. Write a diagnostic marker and kill cleanly. + try + { + CrashDiagnostics.WriteAliveMarker( + $"OnPause watchdog fired after {watchdog_ms}ms: draw thread stuck in vkQueuePresentKHR (Vulkan IMMEDIATE ANR). Killing for clean restart."); + } + catch { } + + try + { + Debug.WriteLine( + "[osu!] OnPause watchdog: draw thread stuck in vkQueuePresentKHR >7s — killing for clean Vulkan restart."); + } + catch { } + + try { global::Android.OS.Process.KillProcess(global::Android.OS.Process.MyPid()); } + catch { } + }); + + base.OnPause(); + pauseCompleted.Set(); + } + else + { + base.OnPause(); + } + } + public override void OnConfigurationChanged(global::Android.Content.Res.Configuration newConfig) { base.OnConfigurationChanged(newConfig); From 5b3f5c62eeb023f9398b7ef0f8ef9eac3da83af6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 20 May 2026 04:44:20 +0000 Subject: [PATCH 3/7] chore: bump osu-framework to 2026.520.1 Agent-Logs-Url: https://github.com/winnerspiros/osu/sessions/ad643f8c-cd6e-4da3-b81a-5821e16c4552 Co-authored-by: winnerspiros <1675249+winnerspiros@users.noreply.github.com> --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 4 ++-- osu.iOS.props | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index bad6fd6c2ba8..6b408a828bed 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - +