Skip to content

Commit 847d045

Browse files
authored
Merge pull request #173 from winnerspiros/fix/revert-to-android-native-build-working-state-4120152865434539135
Revert to last working build for Android native (PR #10772562851119172684)
2 parents 67f903f + de52641 commit 847d045

41 files changed

Lines changed: 303 additions & 195 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release.yml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,14 @@ jobs:
8686
8787
- name: Build Android APK
8888
run: |
89-
# Use optimized publish command with aggressive trimming (defined in project props)
89+
# Use optimized publish command with aggressive trimming and RID-specific build
9090
PUBLISH_CMD="dotnet publish -c Release osu.Android/osu.Android.csproj -f net10.0-android -r android-arm64 --self-contained true \
9191
-p:Version=${{ steps.version.outputs.version }} \
9292
-p:ApplicationDisplayVersion=${{ steps.version.outputs.version }} \
9393
-p:ApplicationVersion=${{ github.run_number }} \
94+
-p:PublishTrimmed=true -p:TrimMode=link -p:AndroidLinkMode=SdkOnly \
95+
-p:AndroidEnableResourceShrinking=true \
96+
-p:MtouchLink=Full \
9497
-p:AndroidCreatePackagePerAbi=false"
9598
9699
if [ "${{ steps.keystore.outputs.has_keystore }}" == "true" ]; then
@@ -114,11 +117,13 @@ jobs:
114117
APK=$(find osu.Android/bin/Release -name "*.apk" | head -1)
115118
fi
116119
117-
118-
echo "Verifying APK signature..."
119-
APKSIGNER=$(find $ANDROID_HOME/build-tools -name apksigner | sort -r | head -1)
120-
if [ -n "$APKSIGNER" ]; then
121-
$APKSIGNER verify --verbose "$APK"
120+
echo "Optimizing APK using zipalign if available..."
121+
# zipalign is usually in the build-tools directory of Android SDK
122+
ZIPALIGN=$(find $ANDROID_HOME/build-tools -name zipalign | sort -r | head -1)
123+
if [ -n "$ZIPALIGN" ]; then
124+
$ZIPALIGN -v 4 "$APK" "${APK}.aligned"
125+
mv "${APK}.aligned" "$APK"
126+
echo "zipalign complete."
122127
fi
123128
124129
echo "Final APK size: $(du -h $APK | cut -f1)"

HitErrorMeter_master.cs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
#nullable disable
5+
6+
using osu.Framework.Allocation;
7+
using osu.Framework.Graphics.Containers;
8+
using osu.Game.Graphics;
9+
using osu.Game.Rulesets.Judgements;
10+
using osu.Game.Rulesets.Scoring;
11+
using osu.Game.Rulesets.UI;
12+
using osu.Game.Skinning;
13+
using osuTK.Graphics;
14+
15+
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
16+
{
17+
public abstract partial class HitErrorMeter : CompositeDrawable, ISerialisableDrawable
18+
{
19+
protected HitWindows HitWindows { get; private set; }
20+
21+
[Resolved(canBeNull: true)]
22+
private ScoreProcessor processor { get; set; }
23+
24+
[Resolved]
25+
private OsuColour colours { get; set; }
26+
27+
[Resolved(canBeNull: true)]
28+
private GameplayClockContainer gameplayClockContainer { get; set; }
29+
30+
public bool UsesFixedAnchor { get; set; }
31+
32+
[BackgroundDependencyLoader(true)]
33+
private void load(DrawableRuleset drawableRuleset)
34+
{
35+
HitWindows = drawableRuleset?.FirstAvailableHitWindows ?? HitWindows.Empty;
36+
37+
// This is to allow the visual state to be correct after HUD comes visible after being hidden.
38+
AlwaysPresent = true;
39+
}
40+
41+
protected override void LoadComplete()
42+
{
43+
base.LoadComplete();
44+
45+
gameplayClockContainer?.OnSeek += Clear;
46+
47+
processor?.NewJudgement += processorNewJudgement;
48+
}
49+
50+
// Scheduled as meter implementations are likely going to change/add drawables when reacting to this.
51+
private void processorNewJudgement(JudgementResult j) => Schedule(() => OnNewJudgement(j));
52+
53+
/// <summary>
54+
/// Fired when a new judgement arrives.
55+
/// </summary>
56+
/// <param name="judgement">The new judgement.</param>
57+
protected abstract void OnNewJudgement(JudgementResult judgement);
58+
59+
protected Color4 GetColourForHitResult(HitResult result)
60+
{
61+
return colours.ForHitResult(result);
62+
}
63+
64+
/// <summary>
65+
/// Invoked by <see cref="GameplayClockContainer.OnSeek"/>.
66+
/// Any inheritors of <see cref="HitErrorMeter"/> should have this method clear their container that displays the hit error results.
67+
/// </summary>
68+
public abstract void Clear();
69+
70+
protected override void Dispose(bool isDisposing)
71+
{
72+
base.Dispose(isDisposing);
73+
74+
processor?.NewJudgement -= processorNewJudgement;
75+
76+
gameplayClockContainer?.OnSeek -= Clear;
77+
}
78+
}
79+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2+
// See the LICENCE file in the repository root for full licence text.
3+
4+
using System.Linq;
5+
using osu.Framework.Allocation;
6+
using osu.Framework.Screens;
7+
using osu.Game.Beatmaps.Drawables.Cards;
8+
using osu.Game.Configuration;
9+
using osu.Game.Localisation;
10+
using osu.Game.Online.API.Requests.Responses;
11+
using osu.Game.Online.Rooms;
12+
using osu.Game.Overlays.Notifications;
13+
using osu.Game.Screens.Menu;
14+
15+
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
16+
{
17+
public partial class NewDailyChallengeNotification : SimpleNotification
18+
{
19+
private readonly Room room;
20+
21+
private BeatmapCardNano card = null!;
22+
23+
public NewDailyChallengeNotification(Room room)
24+
{
25+
this.room = room;
26+
}
27+
28+
[BackgroundDependencyLoader]
29+
private void load(OsuGame? game, SessionStatics statics)
30+
{
31+
Text = DailyChallengeStrings.ChallengeLiveNotification;
32+
var playlistItem = room.Playlist.FirstOrDefault();
33+
if (playlistItem?.Beatmap.BeatmapSet is APIBeatmapSet beatmapSet)
34+
Content.Add(card = new BeatmapCardNano(beatmapSet));
35+
Activated = () =>
36+
{
37+
if (statics.Get<bool>(Static.DailyChallengeIntroPlayed))
38+
game?.PerformFromScreen(s => s.Push(new DailyChallenge(room)), [typeof(MainMenu)]);
39+
else
40+
game?.PerformFromScreen(s => s.Push(new DailyChallengeIntro(room)), [typeof(MainMenu)]);
41+
42+
return true;
43+
};
44+
}
45+
46+
protected override void Update()
47+
{
48+
base.Update();
49+
card.Width = Content.DrawWidth;
50+
}
51+
}
52+
}

final_fix.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import os
2+
3+
def fix_loc():
4+
path = 'osu.Game/Localisation/GraphicsSettingsStrings.cs'
5+
with open(path, 'r') as f:
6+
content = f.read()
7+
8+
# Correct insertion before Resolution
9+
insertion = '\n /// <summary>\n /// "Refresh rate"\n /// </summary>\n public static LocalisableString RefreshRate => new TranslatableString(getKey(@"refresh_rate"), @"Refresh rate");\n'
10+
11+
# We use replace with exact match to ensure indentation is correct (8 spaces)
12+
old_text = ' public static LocalisableString ScreenMode => new TranslatableString(getKey(@"screen_mode"), @"Screen mode");'
13+
new_text = old_text + insertion
14+
15+
if old_text in content and 'RefreshRate' not in content:
16+
with open(path, 'w') as f:
17+
f.write(content.replace(old_text, new_text))
18+
print("Fixed Localisation")
19+
20+
def fix_results():
21+
path = 'osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/ResultsScreen.cs'
22+
with open(path, 'r') as f:
23+
content = f.read()
24+
25+
# Fix the if-null patterns using regex to preserve indentation exactly
26+
import re
27+
28+
# if (x != null) x.Looping = false; -> x?.Looping = false;
29+
content = re.sub(r'if \((playerScoreTickChannel|opponentScoreTickChannel) != null\) \1\.Looping = false;', r'\1?.Looping = false;', content)
30+
31+
# if (x != null && condition) -> if (condition) \n x?.Looping = false;
32+
# Wait, the original was:
33+
# if (playerScoreTickChannel != null && playerScoreBar.Height >= playerScorePercent)
34+
# playerScoreTickChannel.Looping = false;
35+
36+
content = re.sub(r'if \((playerScoreTickChannel|opponentScoreTickChannel) != null && (.*?)\)\s+(.*?)\.Looping = false;',
37+
r'if (\2)\n \1?.Looping = false;', content)
38+
39+
with open(path, 'w') as f:
40+
f.write(content)
41+
print("Fixed ResultsScreen")
42+
43+
fix_loc()
44+
fix_results()

fix_final_v4.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import os
2+
3+
def fix_localisation():
4+
path = 'osu.Game/Localisation/GraphicsSettingsStrings.cs'
5+
with open(path, 'r') as f:
6+
lines = f.readlines()
7+
8+
new_lines = []
9+
skip = False
10+
for i, line in enumerate(lines):
11+
if 'public static LocalisableString ScreenMode' in line:
12+
new_lines.append(line)
13+
new_lines.append('\n')
14+
new_lines.append(' /// <summary>\n')
15+
new_lines.append(' /// "Refresh rate"\n')
16+
new_lines.append(' /// </summary>\n')
17+
new_lines.append(' public static LocalisableString RefreshRate => new TranslatableString(getKey(@"refresh_rate"), @"Refresh rate");\n')
18+
new_lines.append('\n')
19+
skip = True
20+
continue
21+
22+
if skip:
23+
if 'public static LocalisableString Resolution' in line:
24+
new_lines.append(' /// <summary>\n')
25+
new_lines.append(' /// "Resolution"\n')
26+
new_lines.append(' /// </summary>\n')
27+
new_lines.append(line)
28+
skip = False
29+
continue
30+
31+
new_lines.append(line)
32+
33+
with open(path, 'w') as f:
34+
f.writelines(new_lines)
35+
36+
def fix_results():
37+
path = 'osu.Game/Screens/OnlinePlay/Matchmaking/RankedPlay/ResultsScreen.cs'
38+
with open(path, 'r') as f:
39+
content = f.read()
40+
41+
# Identify the block to replace
42+
import re
43+
# We want to replace from "// safety timeout" to the end of the scoreBarProgress block
44+
pattern = re.compile(r'// safety timeout to ensure scoreTicks don\'t play forever\s+Scheduler\.AddDelayed\(\(\) =>\s+\{.*?\}\s+scoreBarProgress\.BindValueChanged\(e =>\s+\{.*?\}\);\s+\}\);', re.DOTALL)
45+
46+
# That's too complex. Let's just target the specific lines.
47+
48+
fixed_content = re.sub(r'Scheduler\.AddDelayed\(\(\) =>\s+\{\s+playerScoreTickChannel\?\.Looping = false;\s+opponentScoreTickChannel\?\.Looping = false;\s+opponentScoreTickChannel\?\.Looping = false;\s+scoreBarProgress\.BindValueChanged',
49+
r'Scheduler.AddDelayed(() =>\n {\n playerScoreTickChannel?.Looping = false;\n opponentScoreTickChannel?.Looping = false;\n }, score_text_duration + 500);\n\n scoreBarProgress.BindValueChanged', content)
50+
51+
with open(path, 'w') as f:
52+
f.write(fixed_content)
53+
54+
fix_localisation()
55+
fix_results()

osu.Android.props

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<SupportedOSPlatformVersion>33.0</SupportedOSPlatformVersion>
44
<RuntimeIdentifiers>android-arm64</RuntimeIdentifiers>
55
<AndroidPackageFormat>apk</AndroidPackageFormat>
6-
<MandroidI18n>CJK;West;</MandroidI18n>
6+
<MandroidI18n>CJK;Mideast;Rare;West;Other;</MandroidI18n>
77
<AndroidHttpClientHandlerType>Xamarin.Android.Net.AndroidMessageHandler</AndroidHttpClientHandlerType>
88
<!-- NullabilityInfoContextSupport is disabled by default for Android -->
99
<NullabilityInfoContextSupport>true</NullabilityInfoContextSupport>
@@ -15,18 +15,19 @@
1515
</PropertyGroup>
1616
<!-- Patch NuGet-provided .so files that ship with 4 KB ELF alignment to 16 KB.
1717
See build/PatchElfPageSize.targets for details.
18+
TODO: Remove once ppy.Veldrid.SPIRV ships 16 KB-aligned native libraries. -->
1819
<Import Project="$(MSBuildThisFileDirectory)build\PatchElfPageSize.targets" />
1920
<!-- Release-only optimisations: AOT for low-latency gameplay, trimming for smaller APK.
2021
Suppress trim analysis warnings because the project uses reflection extensively
2122
(Newtonsoft.Json, Realm, AutoMapper, RuntimeBinder). -->
2223
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
2324
<RunAOTCompilation>true</RunAOTCompilation>
2425
<AndroidEnableProfiledAot>true</AndroidEnableProfiledAot>
26+
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
2527
<PublishTrimmed>true</PublishTrimmed>
26-
<TrimMode>partial</TrimMode>
28+
<TrimMode>link</TrimMode>
2729
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
2830
<AndroidEnableResourceShrinking>true</AndroidEnableResourceShrinking>
29-
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
3031
</PropertyGroup>
3132
<ItemGroup>
3233
<!-- IMPORTANT: ppy.osu.Framework.Android v2026.318.0 only ships net8.0-android34.0 assets.
@@ -41,17 +42,4 @@
4142
Since Realm objects are not declared directly in Android projects, simply disable Fody. -->
4243
<DisableFody>true</DisableFody>
4344
</PropertyGroup>
44-
45-
<!-- WORKAROUND: Fix NativeAOT ILC failure on .NET 10 Android (early 2026 regression).
46-
The SDK incorrectly stamps AssetType="runtime" on non-DLL files (pdb, so, dbg)
47-
in the runtime pack, causing the AOT compiler to fail with BadImageFormatException.
48-
This target fixes the metadata before the SDK uses it.
49-
See: https://github.com/dotnet/runtime/pull/126214 -->
50-
<Target Name="FixRuntimePackAssetTypes" AfterTargets="_AddRuntimeLibsToPublishAssets" BeforeTargets="ComputeManagedAssembliesToCompileToNative">
51-
<ItemGroup>
52-
<RuntimePackAsset Condition="'%(Extension)' != '.dll'">
53-
<AssetType>native</AssetType>
54-
</RuntimePackAsset>
55-
</ItemGroup>
56-
</Target>
5745
</Project>

osu.Android/OboeAudioRedirector.cs

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// See the LICENCE file in the repository root for full licence text.
33

44
using System;
5-
using System.Diagnostics.CodeAnalysis;
65
using System.Collections;
76
using System.Collections.Generic;
87
using System.Linq;
@@ -102,8 +101,6 @@ public void RefreshMixers(int hardwareSampleRate)
102101
Console.WriteLine($"[osu!] Oboe redirector initialized successfully: master={masterMixer}, sources={string.Join(',', mixerHandles)}");
103102
}
104103

105-
// Trimming warnings suppressed because AudioManager.ActiveMixers and related types are manually preserved in Linker.xml.
106-
[UnconditionalSuppressMessage("Trimming", "IL2026, IL2067, IL2070, IL2072, IL2075, IL2080, IL2106", Justification = "Preserved in Linker.xml")]
107104
private IEnumerable<AudioMixer> getActiveMixers()
108105
{
109106
Type type = typeof(AudioManager);
@@ -126,24 +123,6 @@ private IEnumerable<AudioMixer> getActiveMixers()
126123
}
127124
}
128125
}
129-
130-
foreach (var prop in type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
131-
{
132-
if (prop.CanRead && prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericArguments().Contains(typeof(AudioMixer)))
133-
{
134-
object? val = prop.GetValue(audioManager);
135-
if (val is IEnumerable enumerable)
136-
{
137-
foreach (var item in enumerable)
138-
{
139-
if (item is AudioMixer mixer)
140-
yield return mixer;
141-
}
142-
yield break;
143-
}
144-
}
145-
}
146-
147126
type = type.BaseType!;
148127
}
149128
}
@@ -298,8 +277,6 @@ private void addMixer(AudioMixer? mixer)
298277
mixerHandles.Add(handle);
299278
}
300279

301-
// Trimming warnings suppressed because source handles (BASS mixer/stream/channel) are identified via reflection over types preserved in Linker.xml.
302-
[UnconditionalSuppressMessage("Trimming", "IL2026, IL2067, IL2070, IL2072, IL2075, IL2080, IL2106", Justification = "Preserved in Linker.xml")]
303280
private int findHandle(object obj)
304281
{
305282
Type? type = obj.GetType();

osu.Android/OsuGameActivity.cs

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
22
// See the LICENCE file in the repository root for full licence text.
33

4-
using System.Diagnostics.CodeAnalysis;
54
using Android.App;
65
using Android.Content.PM;
76
using Android.Content;
@@ -74,20 +73,7 @@ protected override osu.Framework.Game CreateGame()
7473
return game;
7574
}
7675

77-
7876
public OsuGameActivity()
79-
{
80-
initialise();
81-
}
82-
83-
protected OsuGameActivity(IntPtr handle, JniHandleOwnership transfer)
84-
: base()
85-
{
86-
initialise();
87-
}
88-
89-
[UnconditionalSuppressMessage("Trimming", "IL2026, IL2067, IL2070, IL2072, IL2075, IL2080, IL2106", Justification = "Preserved in Linker.xml")]
90-
private void initialise()
9177
{
9278
game = new OsuGameAndroid(this);
9379

0 commit comments

Comments
 (0)