diff --git a/Osu.Patcher.Hook/Patches/Mods/AudioPreview/FixUpdatePlaybackRate.cs b/Osu.Patcher.Hook/Patches/Mods/AudioPreview/FixUpdatePlaybackRate.cs
index 0064117..eb9825d 100644
--- a/Osu.Patcher.Hook/Patches/Mods/AudioPreview/FixUpdatePlaybackRate.cs
+++ b/Osu.Patcher.Hook/Patches/Mods/AudioPreview/FixUpdatePlaybackRate.cs
@@ -1,5 +1,7 @@
using System.Collections.Generic;
+using System.Linq;
using System.Reflection;
+using System.Reflection.Emit;
using HarmonyLib;
using JetBrains.Annotations;
using Osu.Stubs.Audio;
@@ -9,58 +11,109 @@
namespace Osu.Patcher.Hook.Patches.Mods.AudioPreview;
///
-/// AudioTrackBass::updatePlaybackRate() forcibly resets the tempo to
-/// normal if a pitch isn't applied. This forces the tempo to be set if pitch isn't applied,
-/// to be used with the patch
+/// Apply various fixes to AudioTrackBass to make BASS_ATTRIB_TEMPO work again on
+/// "Preview" audio streams. We use this in combination with
+/// to reapply the constant pitch speed modifier (for DoubleTime).
///
[OsuPatch]
[HarmonyPatch]
[UsedImplicitly]
-internal static class FixUpdatePlaybackRate
+internal class FixUpdatePlaybackRate
{
- // static FixUpdatePlaybackRate()
- // {
- // Task.Run(async () =>
- // {
- // await Task.Delay(2000);
- // try
- // {
- // var mtd = AccessTools.Method(AudioTrackBass.Class.Reference.Name + ":" +
- // AudioTrackBass.UpdatePlaybackRate.Reference.Name);
- // foreach (var instruction in MethodReader.GetInstructions(mtd))
- // {
- // Console.WriteLine($"{instruction.Opcode} {instruction.Operand}");
- // }
- // }
- // catch (Exception e)
- // {
- // Console.WriteLine(e);
- // }
- // });
- // }
-
[UsedImplicitly]
[HarmonyTargetMethod]
- private static MethodBase Target() => AudioTrackBass.UpdatePlaybackRate.Reference;
+ private static MethodBase Target() => AudioTrackBass.Constructor.Reference;
- // TODO: WHY THE FUCK ISN'T THIS TRANSPILER APPLYING CHANGES????? PRE/POST PATCHES WORK FINE BUT THIS DOESN'T????????
[UsedImplicitly]
[HarmonyTranspiler]
- private static IEnumerable Transpiler(IEnumerable instructions)
+ private static IEnumerable Transpiler(
+ IEnumerable instructions,
+ ILGenerator generator)
{
- instructions = instructions.ManipulatorReplace(
- // Find inst that loads 0f as the parameter "value" to BASS_ChannelSetAttribute
- inst => inst.Is(Ldc_R4, 0f),
- inst => new CodeInstruction[]
+ // Remove the conditional & block that checks for this.Preview and always do the full initialization
+ // Otherwise, the "quick" initialization never uses BASS_FX_TempoCreate, which makes setting
+ // BASSAttribute.BASS_ATTRIB_TEMPO impossible (what's used for DoubleTime).
+ // instructions = instructions.NoopSignature(
+ instructions = instructions.NoopSignature(
+ // if (Preview) { audioStream = audioStreamForwards = audioStreamPrefilter; }
+ [
+ Ldarg_0,
+ Callvirt,
+ Brfalse_S,
+ Ldarg_0,
+ Ldarg_0,
+ Ldarg_0,
+ Ldfld,
+ Dup,
+ Stloc_1,
+ Stfld,
+ Ldloc_1,
+ Call,
+ Br_S,
+ ]
+ );
+
+ // Change this "BASSFlag flags = Preview ? 0 : (BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_STREAM_PRESCAN);"
+ // into "BASSFlag flags = Preview ? BASSFlag.BASS_STREAM_DECODE : (BASSFlag.BASS_STREAM_DECODE | BASSFlag.BASS_STREAM_PRESCAN);"
+ // This is so BASS_FX_TempoCreate still works when called later
+ var foundFlags = false;
+ const int BASS_STREAM_DECODE = 0x200000;
+ const int BASS_STREAM_PRESCAN = 0x20000;
+ instructions = instructions.Manipulator(
+ inst => foundFlags || inst.Is(Ldc_I4, BASS_STREAM_DECODE | BASS_STREAM_PRESCAN),
+ inst =>
{
- new(Ldarg_0) { labels = inst.labels }, // Load "this"
- new(Ldfld, AudioTrackBass.PlaybackRate.Reference), // Load the float64 "playbackRate"
- new(Ldc_R8, 100.0), // Load the float64 "100.0"
- new(Sub), // Subtract 100.0 from playbackRate
- new(Conv_R4), // Convert to float32
+ if (inst.OperandIs(BASS_STREAM_DECODE | BASS_STREAM_PRESCAN))
+ {
+ foundFlags = true;
+ return;
+ }
+
+ // This is the "? 0"
+ if (inst.opcode != Ldc_I4_0)
+ return;
+
+ inst.opcode = Ldc_I4;
+ inst.operand = BASS_STREAM_DECODE;
+ foundFlags = false;
}
);
+ // Load speed optimization: disable BASS_FX_ReverseCreate when the "quick" parameter is true (aka. Preview)
+ var found = false;
+ instructions = instructions.Reverse().ManipulatorReplace(
+ inst => found || inst.Is(Stfld, AudioTrackBass.AudioStreamBackwardsHandle.Reference),
+ inst =>
+ {
+ // Only targeting the Call instruction before Stfld
+ if (inst.opcode != Call)
+ {
+ found = true;
+ return [inst];
+ }
+
+ found = false;
+ var labelTrue = generator.DefineLabel();
+ var labelFalse = generator.DefineLabel();
+
+ return new[]
+ {
+ new(Ldarg_2), // "bool quick"
+ new(Brfalse_S, labelFalse),
+
+ // Clean up the 3 values to the Call on stack
+ new(Pop),
+ new(Pop),
+ new(Pop),
+ new(Ldc_I4_0), // Load a "0" to be used for Stfld AudioStreamBackwardsHandle
+ new(Br_S, labelTrue),
+
+ inst.WithLabels([labelFalse]),
+ new(Nop) { labels = [labelTrue] },
+ }.Reverse();
+ }
+ ).Reverse();
+
return instructions;
}
}
\ No newline at end of file
diff --git a/Osu.Patcher.Hook/Patches/Mods/AudioPreview/ModSelectAudioPreview.cs b/Osu.Patcher.Hook/Patches/Mods/AudioPreview/ModSelectAudioPreview.cs
index 02e3e6e..bf5d051 100644
--- a/Osu.Patcher.Hook/Patches/Mods/AudioPreview/ModSelectAudioPreview.cs
+++ b/Osu.Patcher.Hook/Patches/Mods/AudioPreview/ModSelectAudioPreview.cs
@@ -26,7 +26,8 @@ private static void After(
[HarmonyArgument(1)] int mod,
[HarmonyArgument(2)] bool playSound)
{
- // These calls happen for all mods on any mod update and don't actually do anything
+ // These calls happen for all mods on any mod update
+ // and don't actually indicate a ModButton being pressed
if (!playSound) return;
// var availableModStates = ModButton.AvailableStates.Get(__instance);
diff --git a/Osu.Stubs/Audio/AudioTrackBass.cs b/Osu.Stubs/Audio/AudioTrackBass.cs
index 2933136..798d7b2 100644
--- a/Osu.Stubs/Audio/AudioTrackBass.cs
+++ b/Osu.Stubs/Audio/AudioTrackBass.cs
@@ -16,38 +16,91 @@ public class AudioTrackBass
[Stub]
public static readonly LazyType Class = new(
"osu.Audio.AudioTrackBass",
- () => UpdatePlaybackRate!.Reference.DeclaringType!
+ () => Constructor!.Reference.DeclaringType!
);
///
- /// Original: updatePlaybackRate()
+ /// Original: AudioTrackBass(Stream data, bool quick = false, bool loop = false)
/// b20240123:
///
[Stub]
- public static readonly LazyMethod UpdatePlaybackRate = LazyMethod.ByPartialSignature(
- "osu.Audio.AudioTrackBass::updatePlaybackRate()",
+ public static readonly LazyConstructor Constructor = LazyConstructor.ByPartialSignature(
+ "osu.Audio.AudioTrackBass::AudioTrackBass(Stream, bool, bool)",
[
- Conv_R8,
+ Newobj,
+ Throw,
+ Ldarg_0,
+ Ldarg_1,
+ Isinst,
+ Stfld,
Ldarg_0,
Ldfld,
- Mul,
- Ldc_R8,
- Div,
- Conv_R4,
- Call,
- Pop,
]
);
///
- /// Original: playbackRate
+ /// Original: audioStreamBackwards
/// b20240123:
///
[Stub]
- public static readonly LazyField PlaybackRate = new(
- "osu.Audio.AudioTrackBass::playbackRate",
+ public static readonly LazyField AudioStreamBackwardsHandle = new(
+ "osu.Audio.AudioTrackBass::audioStreamBackwards",
() => Class.Reference
.GetDeclaredFields()
- .Single(field => field.FieldType == typeof(double))
+ .Where(field => field.FieldType == typeof(int))
+ .Skip(1)
+ .First()
);
+
+ // ///
+ // /// Original: updatePlaybackRate()
+ // /// b20240123:
+ // ///
+ // [Stub]
+ // public static readonly LazyMethod UpdatePlaybackRate = LazyMethod.ByPartialSignature(
+ // "osu.Audio.AudioTrackBass::updatePlaybackRate()",
+ // [
+ // Conv_R8,
+ // Ldarg_0,
+ // Ldfld,
+ // Mul,
+ // Ldc_R8,
+ // Div,
+ // Conv_R4,
+ // Call,
+ // Pop,
+ // ]
+ // );
+
+ // ///
+ // /// Original: Play(bool restart = true)
+ // /// b20240123:
+ // ///
+ // [Stub]
+ // public static readonly LazyMethod Play = LazyMethod.BySignature(
+ // "osu.Audio.AudioTrackBass::Play(bool)",
+ // [
+ // Ldarg_0,
+ // Ldc_I4_1,
+ // Stfld,
+ // Ldarg_0,
+ // Call,
+ // Ldarg_1,
+ // Call,
+ // Pop,
+ // Ret,
+ // ]
+ // );
+
+ // ///
+ // /// Original: playbackRate
+ // /// b20240123:
+ // ///
+ // [Stub]
+ // public static readonly LazyField PlaybackRate = new(
+ // "osu.Audio.AudioTrackBass::playbackRate",
+ // () => Class.Reference
+ // .GetDeclaredFields()
+ // .Single(field => field.FieldType == typeof(double))
+ // );
}
\ No newline at end of file