Skip to content

Commit

Permalink
mods!!!!
Browse files Browse the repository at this point in the history
  • Loading branch information
rushiiMachine committed Mar 26, 2024
1 parent 7e1424b commit dfafe06
Show file tree
Hide file tree
Showing 10 changed files with 183 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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]
Expand All @@ -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,
};

Expand Down
2 changes: 1 addition & 1 deletion Osu.Performance.ROsu/Osu.Performance.ROsu.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<ROsuOutput Include="$(CargoOutput)\rosu.pdb" Condition="'$(Configuration)' == 'Debug'"/>
<Content Include="@(ROsuOutput)">
<TargetPath>rosu.ffi%(Extension)</TargetPath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Target>
Expand Down
68 changes: 36 additions & 32 deletions Osu.Performance.ROsu/OsuPerformance.cs
Original file line number Diff line number Diff line change
@@ -1,37 +1,36 @@
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;

namespace Osu.Performance.ROsu;

[UsedImplicitly]
public class OsuPerformance : IDisposable
{
private readonly Thread _calculatingThread;
private readonly ConcurrentQueue<PendingCalculation> _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<PendingCalculation>();
_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);
}

Expand All @@ -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
{
Expand All @@ -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);
}
}

/// <summary>
/// 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 <paramref name="score" />.
/// </summary>
/// <param name="difficulty">The precalculated/cached difficulty attributes of a map.</param>
/// <param name="score">A completed (or failed) score's info on the associated map.</param>
/// <param name="mods">The set of mods that were used on this score.</param>
[UsedImplicitly]
public static OsuPerformanceInfo CalculateScore(
OsuDifficultyAttributes difficulty,
OsuScoreState score,
uint mods
) => Native.CalculateOsuPerformance(ref difficulty, ref score, mods);
// /// <summary>
// /// 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 <paramref name="score" />.
// /// </summary>
// /// <param name="difficulty">The precalculated/cached difficulty attributes of a map.</param>
// /// <param name="score">A completed (or failed) score's info on the associated map.</param>
// /// <param name="mods">The set of mods that were used on this score.</param>
// [UsedImplicitly]
// public static OsuPerformanceInfo CalculateScore(
// OsuDifficultyAttributes difficulty,
// OsuScoreState score,
// uint mods
// ) => Native.CalculateOsuPerformance(ref difficulty, ref score, mods);

private struct PendingCalculation
{
Expand Down
5 changes: 3 additions & 2 deletions Osu.Performance.ROsu/rosu-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
1 change: 1 addition & 0 deletions Osu.Performance/Osu.Performance.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" PrivateAssets="all"/>
<ProjectReference Include="../Osu.Performance.ROsu/Osu.Performance.ROsu.csproj">
<Targets>Build;BuildNative</Targets>
<SetTargetFramework>TargetFramework=net452</SetTargetFramework>
</ProjectReference>
</ItemGroup>
Expand Down
62 changes: 40 additions & 22 deletions Osu.Stubs/Beatmap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -17,6 +17,30 @@ namespace Osu.Stubs;
[UsedImplicitly]
public static class Beatmap
{
/// <summary>
/// Original: Unknown, best guess: <c>SetContainingFolder(string absoluteDirPath)</c>
/// b20240123: <c>#=zQwzJucCIbIUZrSZR8Q==</c>
/// </summary>
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,
}
);

/// <summary>
/// Original: <c>Filename</c>
/// b20240123: <c>#=zdZI_NOQ=</c>
Expand All @@ -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;
}
);

/// <summary>
/// Original: <c>ContainingFolderAbsolute</c>
/// Original: Unknown, best guess: <c>ContainingFolder</c> (not absolute)
/// b20240123: <c>#=zDmW9P6igScNm</c>
/// </summary>
[UsedImplicitly]
public static readonly LazyField<string?> ContainingFolderAbsolute = new(
"Beatmap#ContainingFolderAbsolute",
public static readonly LazyField<string?> 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;
}
);

Expand All @@ -77,21 +97,19 @@ public static class Beatmap
.ParameterType;

/// <summary>
/// Utility wrapper to get the full beatmap path of a <c>Beatmap</c> object
/// Utility wrapper to get the full beatmap path of a <c>Beatmap</c>.
/// </summary>
/// <param name="beatmap">An instance of <c>Beatmap</c> that was initialized with the filepath.</param>
/// <returns>The absolute path, or null if this isn't a file-backed Beatmap.</returns>
[UsedImplicitly]
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);
}
}
25 changes: 22 additions & 3 deletions Osu.Stubs/IncreaseScoreType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
Loading

0 comments on commit dfafe06

Please sign in to comment.