diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 0000000..86c4663 --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,27 @@ +name: PR Checks + +on: + pull_request: + +permissions: + contents: read + +env: + DOTNET_NOLOGO: true + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true + DOTNET_CLI_TELEMETRY_OPTOUT: true + +jobs: + formatting: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup .NET SDK + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 10.0.x + + - name: Verify whitespace formatting + run: dotnet format whitespace --verify-no-changes diff --git a/.github/workflows/ci.yml b/.github/workflows/release.yml similarity index 98% rename from .github/workflows/ci.yml rename to .github/workflows/release.yml index 3244e71..c7bc2e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,6 @@ -name: CI +name: Release CI on: - pull_request: push: branches: - main @@ -35,7 +34,6 @@ jobs: run: dotnet format whitespace --verify-no-changes build: - if: github.event_name != 'pull_request' runs-on: windows-latest needs: formatting steps: diff --git a/Directory.Build.props b/Directory.Build.props index 14359be..abbb1ea 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,7 +14,7 @@ - net9.0 + net10.0 latest enable true @@ -70,7 +70,8 @@ $(ResoniteManagedPath)FrooxEngine.dll - $(ResonitePath)FrooxEngine.dll false @@ -79,7 +80,8 @@ $(ResoniteManagedPath)Elements.Core.dll - $(ResonitePath)Elements.Core.dll false @@ -88,7 +90,8 @@ $(ResoniteManagedPath)Elements.Assets.dll - $(ResonitePath)Elements.Assets.dll false @@ -97,11 +100,22 @@ $(ResoniteManagedPath)POpusCodec.dll - $(ResonitePath)POpusCodec.dll false + + $(ResoniteManagedPath)Awwdio.dll + $(ResonitePath)Awwdio.dll + false + $(ResoniteLibsPath)ResoniteHotReloadLib.dll false diff --git a/TurboAudioStream.PrePatcher/TurboAudioStream.PrePatcher.csproj b/TurboAudioStream.PrePatcher/TurboAudioStream.PrePatcher.csproj index fc9fb83..4926081 100644 --- a/TurboAudioStream.PrePatcher/TurboAudioStream.PrePatcher.csproj +++ b/TurboAudioStream.PrePatcher/TurboAudioStream.PrePatcher.csproj @@ -1,35 +1,35 @@ - - netstandard2.0 - NU1603 - - - - + + netstandard2.0 + NU1603 + + + + - - - $(ResoniteManagedPath)FrooxEngine.dll - false - - - $(ResoniteManagedPath)Elements.Core.dll - false - - - $(ResoniteManagedPath)Elements.Assets.dll - false - - - $(ResoniteManagedPath)POpusCodec.dll - false - + + + $(ResoniteManagedPath)FrooxEngine.dll + false + + + $(ResoniteManagedPath)Elements.Core.dll + false + + + $(ResoniteManagedPath)Elements.Assets.dll + false + + + $(ResoniteManagedPath)POpusCodec.dll + false + - - - $(ResoniteManagedPath)%(Filename).dll - false - - + + + $(ResoniteManagedPath)%(Filename).dll + false + + diff --git a/TurboAudioStream/AudioBindingRepairHelper.cs b/TurboAudioStream/AudioBindingRepairHelper.cs index ac10e73..abbdd8f 100644 --- a/TurboAudioStream/AudioBindingRepairHelper.cs +++ b/TurboAudioStream/AudioBindingRepairHelper.cs @@ -25,7 +25,10 @@ out AudioStreamController? controller repaired = true; } - if (source is OpusStream opusStream && audioStreamInterface.Bitrate.Target is null) + if ( + source is OpusStream opusStream + && audioStreamInterface.Bitrate.Target is null + ) { audioStreamInterface.Bitrate.Target = opusStream.BitRate; repaired = true; diff --git a/TurboAudioStream/AudioPatchManager.cs b/TurboAudioStream/AudioPatchManager.cs index e01fcec..ed00bd5 100644 --- a/TurboAudioStream/AudioPatchManager.cs +++ b/TurboAudioStream/AudioPatchManager.cs @@ -62,14 +62,14 @@ public static void Synchronize() { foreach (Type patchType in registration.PatchTypes) { - registration.Harmony.CreateClassProcessor(patchType).Patch(); + _ = registration.Harmony.CreateClassProcessor(patchType).Patch(); } registration.IsPatched = true; } else if (!shouldBeEnabled && registration.IsPatched) { - registration.Harmony.UnpatchSelf(); + registration.Harmony.UnpatchAll(registration.Harmony.Id); registration.IsPatched = false; } } @@ -79,7 +79,7 @@ public static void UnpatchAll() { foreach ((_, PatchRegistration registration) in Registrations) { - registration.Harmony.UnpatchSelf(); + registration.Harmony.UnpatchAll(registration.Harmony.Id); registration.IsPatched = false; } } diff --git a/TurboAudioStream/AudioPatchPredicates.cs b/TurboAudioStream/AudioPatchPredicates.cs index 42c97e3..05161bb 100644 --- a/TurboAudioStream/AudioPatchPredicates.cs +++ b/TurboAudioStream/AudioPatchPredicates.cs @@ -5,7 +5,10 @@ namespace TurboAudioStream; internal static class AudioPatchPredicates { - public static bool IsLocalSenderOpusStream(IAudioStream source, out OpusStream? stream) + public static bool IsLocalSenderOpusStream( + IAudioStream source, + out OpusStream? stream + ) { stream = source as OpusStream; return stream?.User?.IsLocalUser == true; @@ -17,6 +20,6 @@ out OpusStream? opusStream ) { opusStream = stream as OpusStream; - return opusStream?.User?.IsLocalUser != true; + return opusStream is not null && opusStream.User?.IsLocalUser != true; } } diff --git a/TurboAudioStream/PatchTriggerLogger.cs b/TurboAudioStream/PatchTriggerLogger.cs index 91b4326..3adff7f 100644 --- a/TurboAudioStream/PatchTriggerLogger.cs +++ b/TurboAudioStream/PatchTriggerLogger.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Concurrent; -using Elements.Core; using FrooxEngine; using ResoniteModLoader; @@ -34,10 +33,7 @@ public static void Log( Math.Max(0f, TurboAudioStreamConfig.PatchLogCooldownSeconds) ); - if ( - LastLogTimes.TryGetValue(key, out DateTime lastLogTime) - && now - lastLogTime < cooldown - ) + if (LastLogTimes.TryGetValue(key, out DateTime lastLogTime) && now - lastLogTime < cooldown) { return; } diff --git a/TurboAudioStream/Patches/AudioStreamInterface_BindRepair_Patch.cs b/TurboAudioStream/Patches/AudioStreamInterface_BindRepair_Patch.cs index 19f8a13..5e4bd4d 100644 --- a/TurboAudioStream/Patches/AudioStreamInterface_BindRepair_Patch.cs +++ b/TurboAudioStream/Patches/AudioStreamInterface_BindRepair_Patch.cs @@ -1,4 +1,3 @@ -using Elements.Assets; using FrooxEngine; using HarmonyLib; @@ -20,17 +19,23 @@ bool spatialize return; } - if (!AudioBindingRepairHelper.TryRepair(__instance, source, out AudioStreamController? controller)) + if ( + !AudioBindingRepairHelper.TryRepair( + __instance, + source, + out AudioStreamController? controller + ) + ) { return; } - User? user = (source as IAudioStream)?.User; + User? user = source.User; PatchTriggerLogger.Log( AudioPatchFeature.BindRepair, $"repaired-interface-bind bitrate={bitrate} volume={volume:0.###} spatialize={spatialize}", user, - source as IWorldElement, + source, controller?.Slot ); } diff --git a/TurboAudioStream/Patches/AudioStreamInterface_OutgoingTuning_Patch.cs b/TurboAudioStream/Patches/AudioStreamInterface_OutgoingTuning_Patch.cs index 569dc44..a8b5c51 100644 --- a/TurboAudioStream/Patches/AudioStreamInterface_OutgoingTuning_Patch.cs +++ b/TurboAudioStream/Patches/AudioStreamInterface_OutgoingTuning_Patch.cs @@ -18,7 +18,12 @@ public static void Prefix(IAudioStream source) { try { - if (!AudioPatchPredicates.IsLocalSenderOpusStream(source, out OpusStream? stream)) + if ( + !AudioPatchPredicates.IsLocalSenderOpusStream( + source, + out OpusStream? stream + ) + ) { return; } diff --git a/TurboAudioStream/Patches/AudioStreamStereoSample_Read_ReceiverHeadroom_Patch.cs b/TurboAudioStream/Patches/AudioStreamStereoSample_Read_ReceiverHeadroom_Patch.cs index 55cd4d5..9c18c01 100644 --- a/TurboAudioStream/Patches/AudioStreamStereoSample_Read_ReceiverHeadroom_Patch.cs +++ b/TurboAudioStream/Patches/AudioStreamStereoSample_Read_ReceiverHeadroom_Patch.cs @@ -9,9 +9,20 @@ namespace TurboAudioStream.Patches; +[HarmonyPatch] internal static class AudioStreamStereoSample_Read_ReceiverHeadroom_Patch { - private static MethodBase TargetMethod() + private static readonly Action, int> UpdateReadSampleCount = + AccessTools.MethodDelegate, int>>( + AccessTools.DeclaredMethod(typeof(AudioStream), "UpdateReadSampleCount") + ?? throw new MissingMethodException( + typeof(AudioStream).FullName, + "UpdateReadSampleCount" + ) + ); + + [HarmonyTargetMethod] + internal static MethodInfo TargetMethod() { Type closedType = typeof(AudioStream<>).MakeGenericType(typeof(StereoSample)); MethodInfo method = closedType @@ -34,7 +45,14 @@ public static bool Prefix( ref int ____missedSamples ) { - if (!AudioPatchPredicates.IsRemoteReceiverOpusStream(__instance, out OpusStream? opusStream)) + _ = simulator; + + if ( + !AudioPatchPredicates.IsRemoteReceiverOpusStream( + __instance, + out OpusStream? opusStream + ) + ) { return true; } @@ -42,7 +60,7 @@ ref int ____missedSamples Engine? engine = __instance.Engine; if (__instance.IsDisposed || engine is null || __instance.User.IsAudioLocallyBlocked) { - buffer.Fill(default); + buffer.Clear(); return false; } @@ -63,10 +81,12 @@ ref int ____missedSamples { if (___audioBuffer == null || ___audioBuffer.Length != effectiveTargetBufferSize) { - ___audioBuffer = new CircularAudioBuffer( - effectiveTargetBufferSize, - ___audioBuffer - ); + ___audioBuffer = ___audioBuffer is null + ? new CircularAudioBuffer(effectiveTargetBufferSize) + : new CircularAudioBuffer( + effectiveTargetBufferSize, + ___audioBuffer + ); } double dspTime = engine.AudioSystem.DSPTime; @@ -74,7 +94,6 @@ ref int ____missedSamples sampleRate * effectiveMinimumBufferDelay ); bool isNewFrame = ____lastAudioTime != dspTime; - double elapsedSeconds = ____lastAudioTime >= 0.0 ? dspTime - ____lastAudioTime : -1.0; ____lastAudioTime = dspTime; if (isNewFrame) @@ -96,9 +115,9 @@ ref int ____missedSamples if (____activeReading || hasEnoughBufferedSamples) { - __instance.UpdateReadSampleCount(buffer.Length); + UpdateReadSampleCount(__instance, buffer.Length); int readSamples = ___audioBuffer.Read(buffer, ref globalPosition); - buffer.Slice(readSamples).Fill(default); + buffer[readSamples..].Clear(); ____missedSamples += buffer.Length - readSamples; ____lastReadSamples = readSamples; @@ -109,7 +128,7 @@ ref int ____missedSamples } else { - buffer.Fill(default); + buffer.Clear(); } if (!hasEnoughBufferedSamples) diff --git a/TurboAudioStream/Patches/SessionIncomingMessageManager_ProcessStreamMessage_DropLog_Patch.cs b/TurboAudioStream/Patches/SessionIncomingMessageManager_ProcessStreamMessage_DropLog_Patch.cs index 4e387a1..371b814 100644 --- a/TurboAudioStream/Patches/SessionIncomingMessageManager_ProcessStreamMessage_DropLog_Patch.cs +++ b/TurboAudioStream/Patches/SessionIncomingMessageManager_ProcessStreamMessage_DropLog_Patch.cs @@ -1,17 +1,27 @@ +using System; using System.Reflection; using FrooxEngine; using HarmonyLib; namespace TurboAudioStream.Patches; +[HarmonyPatch] internal static class SessionIncomingMessageManager_ProcessStreamMessage_DropLog_Patch { - private static MethodBase TargetMethod() => - AccessTools.Method(typeof(SessionIncomingMessageManager), "ProcessStreamMessage")!; + [HarmonyTargetMethod] + internal static MethodInfo TargetMethod() => + AccessTools.DeclaredMethod(typeof(SessionIncomingMessageManager), "ProcessStreamMessage") + ?? throw new MissingMethodException( + typeof(SessionIncomingMessageManager).FullName, + "ProcessStreamMessage" + ); public static void Prefix(SessionIncomingMessageManager __instance, StreamMessage stream) { - if (stream.IsOutdated || __instance.World.InitState != FrooxEngine.World.InitializationState.Finished) + if ( + stream.IsOutdated + || __instance.World.InitState != FrooxEngine.World.InitializationState.Finished + ) { StreamDropLogHelper.LogAsyncDrop("world-not-ready-or-outdated", stream); } diff --git a/TurboAudioStream/Patches/SyncController_ApplyStreams_DropLog_Patch.cs b/TurboAudioStream/Patches/SyncController_ApplyStreams_DropLog_Patch.cs index 03f9147..7ac1dcb 100644 --- a/TurboAudioStream/Patches/SyncController_ApplyStreams_DropLog_Patch.cs +++ b/TurboAudioStream/Patches/SyncController_ApplyStreams_DropLog_Patch.cs @@ -21,7 +21,10 @@ public static void Prefix(SyncController __instance, StreamMessage message) return; } - if (message.StreamGroup == ushort.MaxValue || user.StreamConfigurationVersion != message.StreamStateVersion) + if ( + message.StreamGroup == ushort.MaxValue + || user.StreamConfigurationVersion != message.StreamStateVersion + ) { string reason = user.StreamConfigurationVersion != message.StreamStateVersion diff --git a/TurboAudioStream/Patches/SyncController_AsyncStreamDecodeAndDispose_DropLog_Patch.cs b/TurboAudioStream/Patches/SyncController_AsyncStreamDecodeAndDispose_DropLog_Patch.cs index a03d737..9591062 100644 --- a/TurboAudioStream/Patches/SyncController_AsyncStreamDecodeAndDispose_DropLog_Patch.cs +++ b/TurboAudioStream/Patches/SyncController_AsyncStreamDecodeAndDispose_DropLog_Patch.cs @@ -1,4 +1,4 @@ -using System; +using System.Collections.Generic; using FrooxEngine; using HarmonyLib; @@ -29,13 +29,9 @@ public static void Prefix(SyncController __instance, StreamMessage message) try { - IStream stream = user.GetStream(streamId); - if (stream is null) - { - StreamDropLogHelper.LogAsyncDrop("missing-user-or-stream", message, user); - } + _ = user.GetStream(streamId); } - catch + catch (KeyNotFoundException) { StreamDropLogHelper.LogAsyncDrop("missing-user-or-stream", message, user); } diff --git a/TurboAudioStream/StreamDropLogHelper.cs b/TurboAudioStream/StreamDropLogHelper.cs index 58851f2..a8632b8 100644 --- a/TurboAudioStream/StreamDropLogHelper.cs +++ b/TurboAudioStream/StreamDropLogHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using Elements.Core; using FrooxEngine; @@ -23,18 +24,23 @@ public static void LogAsyncDrop( ); } + [SuppressMessage( + "Design", + "CA1031:Do not catch general exception types", + Justification = "Drop-log parsing must fail open so malformed async stream payloads do not break stream processing." + )] public static bool TryReadAsyncStreamId(StreamMessage message, out ulong streamId) { streamId = 0; try { - BitReaderStream stream = new(message.GetData()); - BitBinaryReaderX reader = new(stream); + using BitReaderStream stream = new(message.GetData()); + using BitBinaryReaderX reader = new(stream); streamId = reader.Read7BitEncoded(); return true; } - catch + catch (Exception) { return false; } diff --git a/TurboAudioStream/TurboAudioStreamMod.cs b/TurboAudioStream/TurboAudioStreamMod.cs index c8178f9..73b1762 100644 --- a/TurboAudioStream/TurboAudioStreamMod.cs +++ b/TurboAudioStream/TurboAudioStreamMod.cs @@ -24,21 +24,20 @@ public sealed class TurboAudioStreamMod : ResoniteMod /// public override string Author => - Assembly.GetCustomAttribute()?.Company - ?? string.Empty; + Assembly.GetCustomAttribute()?.Company ?? string.Empty; /// public override string Version => Assembly.GetCustomAttribute()?.InformationalVersion - ?? Assembly.GetName().Version?.ToString() - ?? "0.0.0"; + ?? Assembly.GetName().Version?.ToString() ?? "0.0.0"; /// public override string Link => Assembly .GetCustomAttributes() .FirstOrDefault(meta => meta.Key == "RepositoryUrl") - ?.Value ?? string.Empty; + ?.Value + ?? string.Empty; private static ModConfiguration? configuration; @@ -164,7 +163,9 @@ private static void ApplyConfiguration(ModConfiguration config) TurboAudioStreamConfig.BufferSize = config.GetValue(BufferSizeKey); TurboAudioStreamConfig.ApplicationType = config.GetValue(OpusApplicationTypeKey); TurboAudioStreamConfig.EncoderDelay = config.GetValue(EncoderDelayKey); - TurboAudioStreamConfig.EnableOutgoingTuningPatch = config.GetValue(EnableOutgoingTuningPatchKey); + TurboAudioStreamConfig.EnableOutgoingTuningPatch = config.GetValue( + EnableOutgoingTuningPatchKey + ); TurboAudioStreamConfig.EnableBindRepairPatch = config.GetValue(EnableBindRepairPatchKey); TurboAudioStreamConfig.EnableAsyncFreshnessGuardPatch = config.GetValue( EnableAsyncFreshnessGuardPatchKey @@ -178,11 +179,15 @@ private static void ApplyConfiguration(ModConfiguration config) TurboAudioStreamConfig.EnablePatchTriggerLogging = config.GetValue( EnablePatchTriggerLoggingKey ); - TurboAudioStreamConfig.PatchLogCooldownSeconds = config.GetValue(PatchLogCooldownSecondsKey); + TurboAudioStreamConfig.PatchLogCooldownSeconds = config.GetValue( + PatchLogCooldownSecondsKey + ); TurboAudioStreamConfig.ReceiverMinimumBufferDelayFloor = config.GetValue( ReceiverMinimumBufferDelayFloorKey ); - TurboAudioStreamConfig.ReceiverBufferSizeFloor = config.GetValue(ReceiverBufferSizeFloorKey); + TurboAudioStreamConfig.ReceiverBufferSizeFloor = config.GetValue( + ReceiverBufferSizeFloorKey + ); AudioPatchManager.Synchronize(); }