From dfafe068b4f6fcfc6b071bcd2b6689a6cc30a5a7 Mon Sep 17 00:00:00 2001
From: rushiiMachine <33725716+rushiiMachine@users.noreply.github.com>
Date: Tue, 26 Mar 2024 12:29:21 -0700
Subject: [PATCH] mods!!!!
---
.../PatchUpdatePerformanceCalculator.cs | 34 +++++-----
.../Osu.Performance.ROsu.csproj | 2 +-
Osu.Performance.ROsu/OsuPerformance.cs | 68 ++++++++++---------
Osu.Performance.ROsu/rosu-ffi/src/lib.rs | 5 +-
Osu.Performance/Osu.Performance.csproj | 1 +
Osu.Stubs/Beatmap.cs | 62 +++++++++++------
Osu.Stubs/IncreaseScoreType.cs | 25 ++++++-
Osu.Stubs/Obfuscated.cs | 46 +++++++++++++
Osu.Stubs/Opcode/OsuAssembly.cs | 2 +-
Osu.Stubs/Score.cs | 16 +++++
10 files changed, 183 insertions(+), 78 deletions(-)
create mode 100644 Osu.Stubs/Obfuscated.cs
diff --git a/Osu.Patcher.Hook/Patches/LivePerformance/PatchUpdatePerformanceCalculator.cs b/Osu.Patcher.Hook/Patches/LivePerformance/PatchUpdatePerformanceCalculator.cs
index 1b81a45..a718c96 100644
--- a/Osu.Patcher.Hook/Patches/LivePerformance/PatchUpdatePerformanceCalculator.cs
+++ b/Osu.Patcher.Hook/Patches/LivePerformance/PatchUpdatePerformanceCalculator.cs
@@ -1,12 +1,12 @@
using System;
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-using System.IO;
using System.Reflection;
+using System.Threading;
using HarmonyLib;
using JetBrains.Annotations;
using Osu.Performance.ROsu;
using Osu.Stubs;
-using Osu.Stubs.Opcode;
namespace Osu.Patcher.Hook.Patches.LivePerformance;
@@ -16,26 +16,31 @@ public static class PatchUpdatePerformanceCalculator
{
private static OsuPerformance? _performance;
- internal static void ResetCalculator()
+ internal static void ResetCalculator() => new Thread(() =>
{
+ Debug.WriteLine("Resetting performance calculator");
+
_performance?.Dispose();
_performance = null;
var currentScore = Player.CurrentScore.Get();
if (currentScore == null) return;
+ var modsObfuscated = Score.EnabledMods.Get(currentScore);
+ var mods = Score.EnabledModsGetValue.Invoke(modsObfuscated);
+
+ // Clear relax mod for now (live pp calculations for relax are fucking garbage)
+ mods &= ~(1 << 7);
+
var beatmap = Score.Beatmap.Get(currentScore);
if (beatmap == null) return;
- var beatmapSubPath = Beatmap.GetBeatmapPath(beatmap);
- if (beatmapSubPath == null) return;
+ var beatmapPath = Beatmap.GetBeatmapPath(beatmap);
+ if (beatmapPath == null) return;
- var osuDir = Path.GetDirectoryName(OsuAssembly.Assembly.Location)!;
- var beatmapPath = Path.Combine(osuDir, "Songs", beatmapSubPath);
-
- _performance = new OsuPerformance(beatmapPath, 0);
+ _performance = new OsuPerformance(beatmapPath, (uint)mods);
_performance.OnNewCalculation += Console.WriteLine;
- }
+ }).Start();
[UsedImplicitly]
[HarmonyTargetMethod]
@@ -51,17 +56,12 @@ private static void After(
{
if (_performance == null) return;
- const int HitScoreMask = IncreaseScoreType.Osu300 |
- IncreaseScoreType.Osu100 |
- IncreaseScoreType.Osu50 |
- IncreaseScoreType.MissBit;
-
- var judgement = (increaseScoreType & HitScoreMask) switch
+ var judgement = (increaseScoreType & ~IncreaseScoreType.OsuComboModifiers) switch
{
IncreaseScoreType.Osu300 => OsuJudgement.Result300,
IncreaseScoreType.Osu100 => OsuJudgement.Result100,
IncreaseScoreType.Osu50 => OsuJudgement.Result50,
- IncreaseScoreType.MissBit => OsuJudgement.ResultMiss,
+ IncreaseScoreType.OsuMiss => OsuJudgement.ResultMiss,
_ => OsuJudgement.None,
};
diff --git a/Osu.Performance.ROsu/Osu.Performance.ROsu.csproj b/Osu.Performance.ROsu/Osu.Performance.ROsu.csproj
index 7d776ed..bf7d4e8 100644
--- a/Osu.Performance.ROsu/Osu.Performance.ROsu.csproj
+++ b/Osu.Performance.ROsu/Osu.Performance.ROsu.csproj
@@ -28,7 +28,7 @@
rosu.ffi%(Extension)
- PreserveNewest
+ Always
diff --git a/Osu.Performance.ROsu/OsuPerformance.cs b/Osu.Performance.ROsu/OsuPerformance.cs
index 6712909..d9d16fc 100644
--- a/Osu.Performance.ROsu/OsuPerformance.cs
+++ b/Osu.Performance.ROsu/OsuPerformance.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
+using System.Threading.Tasks;
using JetBrains.Annotations;
namespace Osu.Performance.ROsu;
@@ -8,30 +9,28 @@ namespace Osu.Performance.ROsu;
[UsedImplicitly]
public class OsuPerformance : IDisposable
{
- private readonly Thread _calculatingThread;
private readonly ConcurrentQueue _queue;
+ private readonly CancellationTokenSource _queueTaskCancellation;
private readonly IntPtr _state;
- private volatile int _closed;
public OsuPerformance(string mapPath, uint mods)
{
_state = Native.InitializeOsuGradualPerformance(mapPath, mods);
_queue = new ConcurrentQueue();
- _closed = 0;
- _calculatingThread = new Thread(ProcessQueue)
- {
- IsBackground = true,
- };
-
- _calculatingThread.Start();
+ _queueTaskCancellation = new CancellationTokenSource();
+ Task.Factory.StartNew(
+ ProcessQueue,
+ _queueTaskCancellation.Token,
+ TaskCreationOptions.LongRunning,
+ TaskScheduler.Default
+ );
}
public void Dispose()
{
- Interlocked.Exchange(ref _closed, 1);
+ _queueTaskCancellation.Cancel();
OnNewCalculation = null;
- _calculatingThread.Abort();
Native.DisposeGradualOsuPerformance(_state);
}
@@ -49,7 +48,7 @@ public void Dispose()
[UsedImplicitly]
public void AddJudgement(OsuJudgement judgement, uint maxCombo)
{
- if (_closed > 0) return;
+ if (_queueTaskCancellation.IsCancellationRequested) return;
_queue.Enqueue(new PendingCalculation
{
@@ -58,40 +57,45 @@ public void AddJudgement(OsuJudgement judgement, uint maxCombo)
});
}
- private void ProcessQueue()
+ private async void ProcessQueue()
{
while (true)
{
- if (_closed > 0) return;
+ if (_queueTaskCancellation.IsCancellationRequested) return;
while (_queue.TryDequeue(out var item))
{
var performance = Native.CalculateGradualOsuPerformance(_state, item.Judgement, item.MaxCombo);
- var clamped = Math.Max(0, performance);
- OnNewCalculation?.Invoke(clamped);
+ if (performance < 0f)
+ {
+ Console.WriteLine(new Exception("Cannot calculate performance after the end of a beatmap!"));
+ break;
+ }
+
+ OnNewCalculation?.Invoke(performance);
- if (_closed > 0) return;
+ if (_queueTaskCancellation.IsCancellationRequested) return;
}
- Thread.Sleep(100);
+ await Task.Delay(200);
}
}
- ///
- /// Calculates the performance metrics of a score while and returns the complete info.
- /// If this is a failed score, or is in progress for whatever reason, then the end of the score will be
- /// calculated based on the sum of the amount of hits recorded in .
- ///
- /// The precalculated/cached difficulty attributes of a map.
- /// A completed (or failed) score's info on the associated map.
- /// The set of mods that were used on this score.
- [UsedImplicitly]
- public static OsuPerformanceInfo CalculateScore(
- OsuDifficultyAttributes difficulty,
- OsuScoreState score,
- uint mods
- ) => Native.CalculateOsuPerformance(ref difficulty, ref score, mods);
+ // ///
+ // /// Calculates the performance metrics of a score while and returns the complete info.
+ // /// If this is a failed score, or is in progress for whatever reason, then the end of the score will be
+ // /// calculated based on the sum of the amount of hits recorded in .
+ // ///
+ // /// The precalculated/cached difficulty attributes of a map.
+ // /// A completed (or failed) score's info on the associated map.
+ // /// The set of mods that were used on this score.
+ // [UsedImplicitly]
+ // public static OsuPerformanceInfo CalculateScore(
+ // OsuDifficultyAttributes difficulty,
+ // OsuScoreState score,
+ // uint mods
+ // ) => Native.CalculateOsuPerformance(ref difficulty, ref score, mods);
private struct PendingCalculation
{
diff --git a/Osu.Performance.ROsu/rosu-ffi/src/lib.rs b/Osu.Performance.ROsu/rosu-ffi/src/lib.rs
index b9e0b46..c709960 100644
--- a/Osu.Performance.ROsu/rosu-ffi/src/lib.rs
+++ b/Osu.Performance.ROsu/rosu-ffi/src/lib.rs
@@ -68,8 +68,9 @@ extern "C" fn calculate_osu_performance_gradual(
state.performance.next(state.score.clone())
};
- // TODO: handle errors
- return performance.unwrap().pp;
+ return performance
+ .map(|attrs| attrs.pp)
+ .unwrap_or(-1.0);
}
#[no_mangle]
diff --git a/Osu.Performance/Osu.Performance.csproj b/Osu.Performance/Osu.Performance.csproj
index 712a463..be330c9 100644
--- a/Osu.Performance/Osu.Performance.csproj
+++ b/Osu.Performance/Osu.Performance.csproj
@@ -27,6 +27,7 @@
+ Build;BuildNativeTargetFramework=net452
diff --git a/Osu.Stubs/Beatmap.cs b/Osu.Stubs/Beatmap.cs
index 6ca4e62..c7d7141 100644
--- a/Osu.Stubs/Beatmap.cs
+++ b/Osu.Stubs/Beatmap.cs
@@ -3,10 +3,10 @@
using System.IO;
using System.Linq;
using System.Reflection;
-using System.Reflection.Emit;
using HarmonyLib;
using JetBrains.Annotations;
using Osu.Stubs.Opcode;
+using static System.Reflection.Emit.OpCodes;
namespace Osu.Stubs;
@@ -17,6 +17,30 @@ namespace Osu.Stubs;
[UsedImplicitly]
public static class Beatmap
{
+ ///
+ /// Original: Unknown, best guess: SetContainingFolder(string absoluteDirPath)
+ /// b20240123: #=zQwzJucCIbIUZrSZR8Q==
+ ///
+ private static readonly LazyMethod SetContainingFolder = new(
+ "Beatmap#SetContainingFolder(...)",
+ new[]
+ {
+ Callvirt,
+ Starg_S,
+ Ldarg_0,
+ Ldarg_1,
+ Ldc_I4_1,
+ Newarr,
+ Dup,
+ Ldc_I4_0,
+ Ldsfld,
+ Stelem_I2,
+ Callvirt,
+ Stfld, // Reference to ContainingFolder
+ Ret,
+ }
+ );
+
///
/// Original: Filename
/// b20240123: #=zdZI_NOQ=
@@ -33,32 +57,28 @@ public static class Beatmap
.Skip(1)
.First();
- Debug.Assert(storeInstruction.Opcode == OpCodes.Stfld);
+ Debug.Assert(storeInstruction.Opcode == Stfld);
return (FieldInfo)storeInstruction.Operand;
}
);
///
- /// Original: ContainingFolderAbsolute
+ /// Original: Unknown, best guess: ContainingFolder (not absolute)
/// b20240123: #=zDmW9P6igScNm
///
[UsedImplicitly]
- public static readonly LazyField ContainingFolderAbsolute = new(
- "Beatmap#ContainingFolderAbsolute",
+ public static readonly LazyField ContainingFolder = new(
+ "Beatmap#ContainingFolder",
() =>
{
- // TODO: find this field properly
- return RuntimeType.Field("#=zDmW9P6igScNm");
+ // Last Stfld is a reference to ContainingFolder
+ var storeInstruction = MethodReader
+ .GetInstructions(SetContainingFolder.Reference)
+ .Reverse()
+ .First(inst => inst.Opcode == Stfld);
- // // Second Stfld is a reference to ContainingFolderAbsolute
- // var storeInstruction = MethodReader
- // .GetInstructions(PrimaryConstructor)
- // .Where(inst => inst.Opcode == OpCodes.Stfld)
- // .Skip(1)
- // .First();
- //
- // return (FieldInfo)storeInstruction.Operand;
+ return (FieldInfo)storeInstruction.Operand;
}
);
@@ -77,7 +97,7 @@ public static class Beatmap
.ParameterType;
///
- /// Utility wrapper to get the full beatmap path of a Beatmap object
+ /// Utility wrapper to get the full beatmap path of a Beatmap.
///
/// An instance of Beatmap that was initialized with the filepath.
/// The absolute path, or null if this isn't a file-backed Beatmap.
@@ -85,13 +105,11 @@ public static class Beatmap
public static string? GetBeatmapPath(object beatmap)
{
var filename = Filename.Get(beatmap);
- var directory = ContainingFolderAbsolute.Get(beatmap);
+ var folder = ContainingFolder.Get(beatmap);
+ if (filename == null || folder == null) return null;
- if (filename != null && directory != null)
- {
- return Path.Combine(directory, filename);
- }
+ var osuDir = Path.GetDirectoryName(OsuAssembly.Assembly.Location)!;
- return null;
+ return Path.Combine(osuDir, "Songs", folder, filename);
}
}
\ No newline at end of file
diff --git a/Osu.Stubs/IncreaseScoreType.cs b/Osu.Stubs/IncreaseScoreType.cs
index 7390139..3c2581e 100644
--- a/Osu.Stubs/IncreaseScoreType.cs
+++ b/Osu.Stubs/IncreaseScoreType.cs
@@ -10,13 +10,32 @@ namespace Osu.Stubs;
[UsedImplicitly]
public class IncreaseScoreType
{
- // TODO: reverse engineer all enum values
- public const int MissBit = 1 << 31; // No clue if this is right
+ // For HitCircles, the only IST emitted is the final hit result (Osu50, Osu100, Osu300, OsuMiss)
+
+ // For OsuSliders, an IST is emitted for the slider head, slider ticks, slider end, and the overall hit result.
+ // Slider head values: If hit within any timing window, then OsuSliderHead, otherwise OsuSliderHeadMiss
+ // Slider ticks: If properly tracked, then OsuSliderTick, otherwise OsuSliderTickMiss
+ // Slider end: If properly tracked, then OsuSliderEnd, otherwise OsuSliderEndMiss
+ // Final result: Any of the values that are emitted by HitCircle. This is the only IST from sliders that is tracked in scores.
+
+ // This can be combined with any Osu300,Osu100,Osu50 if a significant amount of the combo
+ // was missed but the combo end object was hit
+ public const int OsuComboEndSmall = 1 << 0;
+ public const int OsuComboEndMedium = 1 << 1; // Same as above but more of the combo was hit
+ public const int OsuComboEndFull = 1 << 2; // Same as above but the entire combo was 300s
+ public const int OsuComboModifiers = OsuComboEndSmall | OsuComboEndMedium | OsuComboEndFull;
+
+ public const int OsuSliderTick = 1 << 3;
+ public const int OsuSliderHead = 1 << 6;
+ public const int OsuSliderEnd = 1 << 7;
+ public const int OsuSliderHeadMiss = -0x40000;
+ public const int OsuSliderTickMiss = -0x40000;
+ public const int OsuSliderEndMiss = -0x80000;
- public const int Miss = -131072;
public const int Osu50 = 1 << 8;
public const int Osu100 = 1 << 9;
public const int Osu300 = 1 << 10;
+ public const int OsuMiss = -0x20000;
// Used as the first parameter in the constructor for ScoreChange
[UsedImplicitly]
diff --git a/Osu.Stubs/Obfuscated.cs b/Osu.Stubs/Obfuscated.cs
new file mode 100644
index 0000000..1bad808
--- /dev/null
+++ b/Osu.Stubs/Obfuscated.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Linq;
+using System.Reflection;
+using JetBrains.Annotations;
+using Osu.Stubs.Opcode;
+using static System.Reflection.Emit.OpCodes;
+
+namespace Osu.Stubs;
+
+[UsedImplicitly]
+public class Obfuscated
+{
+ private static readonly LazyMethod Finalize = new(
+ "Obfuscated#Finalize()",
+ new[]
+ {
+ Newobj,
+ Dup,
+ Stsfld,
+ Ldc_I4,
+ Ldc_I4_0,
+ Callvirt, // This is a reference to Scheduler::AddDelayed, TODO: use this?
+ Pop,
+ Leave_S,
+ Ldarg_0,
+ Call,
+ Endfinally,
+ Ret,
+ }
+ );
+
+ [UsedImplicitly]
+ public static readonly LazyMethod