Decompiled source of GTFOCustomMusic v1.0.0
GTFOCustomMusic.dll
Decompiled 9 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; using AK; using Agents; using BepInEx; using BepInEx.Configuration; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using ChainedPuzzles; using Enemies; using GTFO.API; using GTFOCustomMusic.Audio; using GTFOCustomMusic.Director; using GTFOCustomMusic.Net; using GTFOCustomMusic.Playlist; using GameData; using GameEvent; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppSystem.Collections.Generic; using LevelGeneration; using Microsoft.CodeAnalysis; using NVorbis; using Player; using SNetwork; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace GTFOCustomMusic { internal class MusicRuntime : MonoBehaviour { private Task _loading; private bool _ready; private eGameStateName _gameState; private MUS_State _musState; private ulong _master; private bool _overlayFlipped; private static int _jumpsSeen; private static bool _wasPaused; private GUIStyle _style; public static bool InLevel { get; private set; } private bool OverlayVisible { get { if (PluginConfig.ShowOverlay.Value) { return (MusicNet.IsHost || PluginConfig.OverlayForClients.Value) != _overlayFlipped; } return false; } } public MusicRuntime(IntPtr ptr) : base(ptr) { } private void Awake() { MusicPlayer.Init(); StartLoad(); } private void StartLoad() { _ready = false; string folder = PluginConfig.ResolvedMusicFolder; _loading = Task.Run(delegate { PlaylistStore.Load(folder); }); } private void Update() { //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Expected O, but got Unknown TrackMusicState(); if (!_ready) { if (!_loading.IsCompleted) { return; } if (_loading.IsFaulted) { ManualLogSource l = Plugin.L; bool flag = default(bool); BepInExErrorLogInterpolatedStringHandler val = new BepInExErrorLogInterpolatedStringHandler(22, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Playlist load failed: "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<Exception>(_loading.Exception?.GetBaseException()); } l.LogError(val); } _ready = true; BootSound.OnPlaylistLoaded(); } TrackLobby(); TrackGameState(); TrackPause(); SyncClock.Tick(); ClipCache.Tick(); if (!SyncClock.Paused) { if (InLevel && MusicNet.IsHost) { Engagement.Tick(); MusicDirector.Tick(); } MusicPlayer.Tick(); } BootSound.Tick(); GameMusicMute.Tick(); if (Input.GetKeyDown(PluginConfig.ReloadKey.Value)) { Reload(); } if (Input.GetKeyDown(PluginConfig.OverlayKey.Value)) { _overlayFlipped = !_overlayFlipped; } } private void TrackLobby() { long num; if (!SNet.HasMaster) { num = 0L; } else { SNet_Player master = SNet.Master; num = (long)((master != null) ? master.Lookup : 0); } ulong num2 = (ulong)num; if (num2 != _master) { _master = num2; SyncClock.IsHost = MusicNet.IsHost; SyncClock.Reset(); Plugin.Verbose($"Master changed: {num2} (we are {(MusicNet.IsHost ? "host" : "client")})"); if (!MusicNet.IsHost) { MusicNet.SendHello(); } } } private void TrackMusicState() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)MusicManager.Current == (Object)null)) { MUS_State val = MusicManager.CurrentState(); if (val != _musState) { Plugin.Verbose($"GTFO music state {_musState} -> {val}"); _musState = val; } } } private void TrackGameState() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Invalid comparison between Unknown and I4 //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Invalid comparison between Unknown and I4 //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Invalid comparison between Unknown and I4 eGameStateName currentStateName = GameStateManager.CurrentStateName; if (currentStateName == _gameState) { return; } eGameStateName gameState = _gameState; _gameState = currentStateName; Plugin.Verbose($"Game state {gameState} -> {currentStateName}"); bool flag = currentStateName - 6 <= 1; if (flag && !MusicNet.IsHost) { MusicNet.SendHello(); } bool flag2 = (int)currentStateName == 10; if (flag2 == InLevel) { return; } InLevel = flag2; if (flag2) { if (MusicNet.IsHost) { MusicDirector.OnLevelStart(); } else { MusicNet.SendHello(); } } else { MusicDirector.OnLevelEnd(); MusicPlayer.StopAll(((int)currentStateName == 14) ? 8.0 : 1.5); } } private static void TrackPause() { SyncClock.SetLocalPaused(InLevel && Time.timeScale == 0f); if (SyncClock.Jumps != _jumpsSeen) { _jumpsSeen = SyncClock.Jumps; MusicPlayer.Halt(); } if (SyncClock.Paused != _wasPaused) { _wasPaused = SyncClock.Paused; Plugin.Verbose(_wasPaused ? "Paused: music stopped" : $"Resumed: music picks up again ({SyncClock.PausedTotal:F2}s paused in total)"); } } private void Reload() { if (MusicPlayer.Active) { Plugin.L.LogWarning((object)"Not reloading while music is playing"); return; } Plugin.L.LogMessage((object)"Reloading playlist"); ClipCache.Clear(); StartLoad(); } private void OnApplicationFocus(bool focused) { MusicPlayer.FocusMul = ((focused || !PluginConfig.MuteOnFocusLoss.Value) ? 1f : 0f); } private void OnDestroy() { GameMusicMute.Restore(); } private void OnGUI() { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Expected O, but got Unknown //IL_0249: Unknown result type (might be due to invalid IL or missing references) if (_ready && OverlayVisible) { if (_style == null) { GUIStyle val = new GUIStyle { fontSize = 13 }; val.normal.textColor = Color.white; _style = val; } double now = SyncClock.Now; string text = $"GTFOCustomMusic [{(MusicNet.IsHost ? "host" : "client")}] {MusicPlayer.Phase} track={MusicPlayer.TrackId}{(SyncClock.Paused ? " (paused)" : "")}\n" + (MusicNet.IsHost ? $"awake={Engagement.AwakeCount} alarm={Engagement.AlarmActive} wave={Engagement.WaveAlive} hot={Engagement.Hot} activity={now - Engagement.LastActivity:F0}s contact={now - Engagement.LastContact:F0}s pool={MusicDirector.Pool}\n" : $"offset={SyncClock.Offset * 1000.0:F1}ms rtt={SyncClock.Rtt * 1000.0:F0}ms\n") + "clips: " + ClipCache.Describe(); GUI.Label(new Rect(12f, 140f, 900f, 120f), text, _style); } } } [BepInPlugin("dev.ntainy.GTFOCustomMusic", "GTFOCustomMusic", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class Plugin : BasePlugin { public const string GUID = "dev.ntainy.GTFOCustomMusic"; public const string NAME = "GTFOCustomMusic"; public const string VERSION = "1.0.0"; internal static ManualLogSource L; public override void Load() { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Expected O, but got Unknown L = ((BasePlugin)this).Log; PluginConfig.Bind(((BasePlugin)this).Config); MusicNet.Register(); Harmony val = new Harmony("dev.ntainy.GTFOCustomMusic"); Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(typeof(Plugin).Assembly); bool flag = default(bool); foreach (Type type in typesFromAssembly) { if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: false).Length == 0) { continue; } try { val.CreateClassProcessor(type).Patch(); } catch (Exception ex) { ManualLogSource l = L; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(20, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Patches in "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(type.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.GetBaseException().Message); } l.LogError(val2); } } ((BasePlugin)this).AddComponent<MusicRuntime>(); ManualLogSource l2 = L; BepInExMessageLogInterpolatedStringHandler val3 = new BepInExMessageLogInterpolatedStringHandler(8, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>("GTFOCustomMusic"); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>("1.0.0"); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(" loaded"); } l2.LogMessage(val3); } internal static void Verbose(string msg) { if (PluginConfig.VerboseSignals.Value) { L.LogInfo((object)msg); } } } public enum GameMusicMode { MuteWhilePlaying, Never } public enum ExtractionTrigger { ExitScan, ObjectiveComplete, Never } public enum StingerUse { ScoutScream, Always, Never } internal static class PluginConfig { public static ConfigEntry<float> MasterVolume; public static ConfigEntry<bool> FollowGameSliders; public static ConfigEntry<float> GameSliderRangeDb; public static ConfigEntry<string> MusicFolder; public static ConfigEntry<GameMusicMode> GameMusic; public static ConfigEntry<bool> MuteOnFocusLoss; public static ConfigEntry<KeyCode> ReloadKey; public static ConfigEntry<string> BootSoundEvent; public static ConfigEntry<bool> BootSoundReplaces; public static ConfigEntry<int> RoomWakeThreshold; public static ConfigEntry<int> ContactMinAwake; public static ConfigEntry<float> ContactWindow; public static ConfigEntry<float> SuspenseHold; public static ConfigEntry<float> ScoutHold; public static ConfigEntry<float> CombatQuietTime; public static ConfigEntry<float> CombatMaxQuietWithAwake; public static ConfigEntry<float> SameTrackWindow; public static ConfigEntry<ExtractionTrigger> Extraction; public static ConfigEntry<float> SuspenseFadeIn; public static ConfigEntry<float> SuspenseFadeOut; public static ConfigEntry<float> CooldownFade; public static ConfigEntry<float> TrackCrossfade; public static ConfigEntry<float> SuspenseCutoffHz; public static ConfigEntry<float> SuspenseGain; public static ConfigEntry<float> CommitSweep; public static ConfigEntry<float> LeadInCloseBars; public static ConfigEntry<float> LeadInCloseSeconds; public static ConfigEntry<float> RotateAfter; public static ConfigEntry<float> ScheduleLead; public static ConfigEntry<StingerUse> Stingers; public static ConfigEntry<float> MemeChanceCombat; public static ConfigEntry<float> MemeChanceAlarm; public static ConfigEntry<float> MemeChanceExtraction; public static ConfigEntry<bool> VerboseSignals; public static ConfigEntry<bool> ShowOverlay; public static ConfigEntry<bool> OverlayForClients; public static ConfigEntry<KeyCode> OverlayKey; public static string ResolvedMusicFolder { get { if (!string.IsNullOrWhiteSpace(MusicFolder.Value)) { return MusicFolder.Value; } return Path.Combine(Paths.ConfigPath, "GTFOCustomMusic"); } } public static void Bind(ConfigFile c) { MasterVolume = c.Bind<float>("General", "MasterVolume", 1f, "Multiplier applied to every track's own Volume: the fixed balance between our music and GTFO's own. Day-to-day level comes from the game's sliders (FollowGameSliders)."); FollowGameSliders = c.Bind<bool>("General", "FollowGameSliders", true, "GTFO's Master and Music volume sliders scale this mod's music as well. Until the game first applies them (level load, or touching a slider) they count as full."); GameSliderRangeDb = c.Bind<float>("General", "GameSliderRangeDb", 20f, "How the sliders map to gain: slider 1 = 0 dB, each step down toward 0 lowers it evenly in dB over this range, and 0 is silent (e.g. 20: half slider = -10 dB). Master and Music stack, so keep it gentle. 0 = linear multiplier."); MusicFolder = c.Bind<string>("General", "MusicFolder", "", "Folder with playlist.json and the audio files. Empty = BepInEx/config/GTFOCustomMusic."); GameMusic = c.Bind<GameMusicMode>("General", "GameMusic", GameMusicMode.MuteWhilePlaying, "What to do with GTFO's own music while this mod plays."); MuteOnFocusLoss = c.Bind<bool>("General", "MuteOnFocusLoss", true, "Mute (not pause, so sync is kept) when the game window loses focus."); ReloadKey = c.Bind<KeyCode>("General", "ReloadKey", (KeyCode)291, "Re-read playlist.json and audio files (only while idle). Everyone in the lobby has to reload."); BootSoundEvent = c.Bind<string>("General", "BootSoundEvent", "PLAY_06_MAIN_MENU_LAUNCH", "GTFO sound event (an AK.EVENTS name) that plays the playlist's BootSound. With VerboseSignals, every event posted outside a level is logged as 'Sound NAME', to find the one you want."); BootSoundReplaces = c.Bind<bool>("General", "BootSoundReplaces", true, "Skip GTFO's own sound for BootSoundEvent when the BootSound plays instead."); RoomWakeThreshold = c.Bind<int>("Detection", "RoomWakeThreshold", 4, "Combat starts when this many enemies are awake at the same time."); ContactMinAwake = c.Bind<int>("Detection", "ContactMinAwake", 3, "Outside alarms, contact (you get hit / you hit an awake enemy) starts combat only with at least this many enemies awake. Keeps single woken sleepers in suspense."); ContactWindow = c.Bind<float>("Detection", "ContactWindow", 1.5f, "Seconds a contact event counts as recent."); SuspenseHold = c.Bind<float>("Detection", "SuspenseHold", 10f, "Seconds without any engagement signal before suspense fades out."); ScoutHold = c.Bind<float>("Detection", "ScoutHold", 45f, "Suspense holds this long after a scout's last detect pulse (until it's killed). 0 = scouts are ordinary signals."); CombatQuietTime = c.Bind<float>("Detection", "CombatQuietTime", 2f, "Seconds after the last awake enemy is dead (and no alarm) before combat fades out."); CombatMaxQuietWithAwake = c.Bind<float>("Detection", "CombatMaxQuietWithAwake", 20f, "Fade out even with enemies still flagged awake after this long without any hits (lost/stuck enemies)."); SameTrackWindow = c.Bind<float>("Detection", "SameTrackWindow", 10f, "A new episode starting within this many seconds of the last one ending keeps its track, carried on from where it would be by now (muffled again while sneaking), at most twice in a row. 0 = always pick fresh."); Extraction = c.Bind<ExtractionTrigger>("Detection", "Extraction", ExtractionTrigger.ExitScan, "When the Extraction pool takes over: once the extraction scan starts (ExitScan), from the moment the main objective is done (ObjectiveComplete, also for alarm doors on the way out), or never."); SuspenseFadeIn = c.Bind<float>("Playback", "SuspenseFadeIn", 2f, ""); SuspenseFadeOut = c.Bind<float>("Playback", "SuspenseFadeOut", 4f, ""); CooldownFade = c.Bind<float>("Playback", "CooldownFade", 3f, "Fade length when combat ends."); TrackCrossfade = c.Bind<float>("Playback", "TrackCrossfade", 3f, "Crossfade when one track takes over from another (rotation on a loop seam, pool change)."); SuspenseCutoffHz = c.Bind<float>("Playback", "SuspenseCutoffHz", 700f, "Low-pass cutoff of the muffled copy of Loop that serves as the suspense layer for tracks without a Suspense file (rendered at decode time; reload to apply)."); SuspenseGain = c.Bind<float>("Playback", "SuspenseGain", 0.55f, "Relative gain of the muffled suspense layer."); CommitSweep = c.Bind<float>("Playback", "CommitSweep", 0.4f, "Seconds the muffled layer takes to open up into the full loop when suspense turns into combat."); LeadInCloseBars = c.Bind<float>("Playback", "LeadInCloseBars", 2f, "Over this many bars at the end of a LeadIn, it crossfades into a muffled copy of itself so the loop's drop lands muffled during suspense. Opens back up if combat starts right before the drop. 0 = off (reload to apply)."); LeadInCloseSeconds = c.Bind<float>("Playback", "LeadInCloseSeconds", 4f, "The same close in seconds, for tracks without a Bpm. 0 = off for those (reload to apply)."); RotateAfter = c.Bind<float>("Playback", "RotateAfter", 600f, "A long fight crossfades to another track of its pool at the first loop seam after this many seconds. Extraction tracks never rotate. 0 = never."); ScheduleLead = c.Bind<float>("Playback", "ScheduleLead", 0.2f, "Seconds between the host deciding and everyone hearing it. Must cover network latency."); Stingers = c.Bind<StingerUse>("Playback", "Stingers", StingerUse.ScoutScream, "When a track's Stinger plays as suspense turns into combat: only when a scout screams (otherwise the muffled song opens up into itself), on every commit, or never. Loud starts from idle always skip it."); MemeChanceCombat = c.Bind<float>("Playback", "MemeChanceCombat", 0.3f, "Chance a Combat pick is a Meme track (host). Never two Meme tracks in a row (so at most every other pick), never when a scout started the episode. 0 = no Meme tracks."); MemeChanceAlarm = c.Bind<float>("Playback", "MemeChanceAlarm", 0.5f, "Chance an Alarm pick is a Meme track (host). Never two Meme tracks in a row (so at most every other pick), never when a scout started the episode. 0 = no Meme tracks."); MemeChanceExtraction = c.Bind<float>("Playback", "MemeChanceExtraction", 0.2f, "Chance an Extraction pick is a Meme track (host). Never two Meme tracks in a row (so at most every other pick), never when a scout started the episode. 0 = no Meme tracks."); VerboseSignals = c.Bind<bool>("Debug", "VerboseSignals", false, "Log every engagement signal and game event. Use this to tune detection."); ShowOverlay = c.Bind<bool>("Debug", "ShowOverlay", false, "On-screen debug overlay (phase, awake count, clock offset...)."); OverlayForClients = c.Bind<bool>("Debug", "OverlayForClients", false, "Keep the overlay while in someone else's lobby. Off: it shows only until we join as a client (and for the host)."); OverlayKey = c.Bind<KeyCode>("Debug", "OverlayKey", (KeyCode)290, "Toggles the overlay against what ShowOverlay/OverlayForClients would show right now."); } } } namespace GTFOCustomMusic.Playlist { public enum Pool : byte { Combat, Alarm, Extraction } public sealed class TrackDef { public string Id { get; set; } public bool Enabled { get; set; } = true; public List<Pool> Pools { get; set; } = new List<Pool> { Pool.Combat }; public string Loop { get; set; } public string LeadIn { get; set; } public string Stinger { get; set; } public string SuspenseLayer { get; set; } public bool MuffleSuspenseLayer { get; set; } public float? SuspenseGain { get; set; } public bool LeadInAlarmOnly { get; set; } public string Suspense { get; set; } public bool Meme { get; set; } public float Volume { get; set; } = 1f; public float LoopTail { get; set; } public float Bpm { get; set; } public float SuspenseBpm { get; set; } public int BeatsPerBar { get; set; } = 4; public float FirstBeat { get; set; } [JsonExtensionData] public Dictionary<string, JsonElement> Unknown { get; set; } [JsonIgnore] public uint Key { get; set; } [JsonIgnore] public AudioFile LoopFile { get; set; } [JsonIgnore] public AudioFile LeadInFile { get; set; } [JsonIgnore] public AudioFile StingerFile { get; set; } [JsonIgnore] public AudioFile SuspenseFile { get; set; } [JsonIgnore] public AudioFile LayerFile { get; set; } [JsonIgnore] public AudioFile LeadInLayerFile { get; set; } [JsonIgnore] public double LoopPeriod => LoopFile.Seconds - (double)LoopTail; [JsonIgnore] public double LeadInSeconds => LeadInFile?.Seconds ?? 0.0; [JsonIgnore] public double StingerSeconds => StingerFile?.Seconds ?? 0.0; [JsonIgnore] public double SuspensePeriod => SuspenseFile?.Seconds ?? 0.0; [JsonIgnore] public double BarSeconds { get { if (!(Bpm > 0f)) { return 0.0; } return (double)BeatsPerBar * 60.0 / (double)Bpm; } } [JsonIgnore] public double SuspenseBarSeconds { get { if (!(SuspenseBpm > 0f)) { return 0.0; } return (double)BeatsPerBar * 60.0 / (double)SuspenseBpm; } } [JsonIgnore] public double LeadInCloseSeconds { get { if (LeadInLayerFile != null) { return Math.Min(LeadInSeconds / 2.0, (BarSeconds > 0.0) ? ((double)PluginConfig.LeadInCloseBars.Value * BarSeconds) : ((double)PluginConfig.LeadInCloseSeconds.Value)); } return 0.0; } } public IEnumerable<AudioFile> Files() { if (LoopFile != null) { yield return LoopFile; } if (LeadInFile != null) { yield return LeadInFile; } if (StingerFile != null) { yield return StingerFile; } if (SuspenseFile != null) { yield return SuspenseFile; } if (LayerFile != null) { yield return LayerFile; } if (LeadInLayerFile != null) { yield return LeadInLayerFile; } } } public sealed class PlaylistFile { public string BootSound { get; set; } public float BootVolume { get; set; } = 1f; public List<TrackDef> Tracks { get; set; } = new List<TrackDef>(); } internal static class PlaylistStore { private static readonly JsonSerializerOptions Json = new JsonSerializerOptions { PropertyNameCaseInsensitive = true, ReadCommentHandling = JsonCommentHandling.Skip, AllowTrailingCommas = true, WriteIndented = true, Converters = { (JsonConverter)new JsonStringEnumConverter() } }; public static List<TrackDef> Tracks { get; private set; } = new List<TrackDef>(); public static uint Hash { get; private set; } public static AudioFile Boot { get; private set; } public static float BootVolume { get; private set; } = 1f; public static TrackDef Get(uint key) { return Tracks.FirstOrDefault((TrackDef t) => t.Key == key); } public static List<TrackDef> PoolTracks(Pool pool) { List<TrackDef> list; while (true) { list = Tracks.Where((TrackDef t) => t.Pools.Contains(pool)).ToList(); if (list.Count > 0 || pool == Pool.Combat) { break; } pool--; } return list; } public static void Load(string folder) { //IL_03b2: Unknown result type (might be due to invalid IL or missing references) //IL_03b9: Expected O, but got Unknown //IL_04f0: Unknown result type (might be due to invalid IL or missing references) //IL_04f7: Expected O, but got Unknown //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Expected O, but got Unknown //IL_05be: Unknown result type (might be due to invalid IL or missing references) //IL_05c5: Expected O, but got Unknown //IL_049a: Unknown result type (might be due to invalid IL or missing references) //IL_04a1: Expected O, but got Unknown //IL_065a: Unknown result type (might be due to invalid IL or missing references) //IL_0661: Expected O, but got Unknown //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_036b: Expected O, but got Unknown string text = Path.Combine(folder, "playlist.json"); bool flag = default(bool); if (!File.Exists(text)) { Directory.CreateDirectory(folder); File.WriteAllText(text, JsonSerializer.Serialize(new PlaylistFile(), Json)); ManualLogSource l = Plugin.L; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(41, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("No playlist found, wrote an empty one to "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(text); } l.LogWarning(val); } PlaylistFile playlistFile = JsonSerializer.Deserialize<PlaylistFile>(File.ReadAllText(text), Json) ?? new PlaylistFile(); List<TrackDef> list = new List<TrackDef>(); foreach (TrackDef item in playlistFile.Tracks.Where((TrackDef t) => t.Enabled)) { try { if (string.IsNullOrEmpty(item.Id) || string.IsNullOrEmpty(item.Loop)) { throw new InvalidDataException("Id and Loop are required"); } item.Key = Fnv1a(item.Id); item.LoopFile = AudioFile.Read(folder, item.Loop); if ((double)item.LoopTail >= item.LoopFile.Seconds) { throw new InvalidDataException("LoopTail longer than Loop"); } Dictionary<string, JsonElement> unknown = item.Unknown; if (unknown != null && unknown.Count > 0) { Plugin.L.LogWarning((object)("Track '" + item.Id + "': unknown keys ignored: " + string.Join(", ", item.Unknown.Keys) + (item.Unknown.ContainsKey("Intro") ? " (\"Intro\" is now \"Stinger\")" : ""))); } item.LeadInFile = Optional(folder, item.LeadIn); if (item.LeadInFile != null && ((item.Bpm > 0f) ? PluginConfig.LeadInCloseBars.Value : PluginConfig.LeadInCloseSeconds.Value) > 0f) { item.LeadInLayerFile = AudioFile.Muffled(item.LeadInFile, PluginConfig.SuspenseCutoffHz.Value); } item.StingerFile = Optional(folder, item.Stinger); item.SuspenseFile = Optional(folder, item.Suspense); if (item.SuspenseFile == null || item.LeadInFile != null) { AudioFile audioFile = Optional(folder, item.SuspenseLayer); if (audioFile != null && audioFile.Frames != item.LoopFile.Frames) { Plugin.L.LogError((object)($"Track '{item.Id}': SuspenseLayer is {audioFile.Frames} frames, Loop is {item.LoopFile.Frames}; " + "they must match exactly to play in lockstep. Using a muffled Loop instead")); audioFile = null; } float value = PluginConfig.SuspenseCutoffHz.Value; item.LayerFile = ((audioFile == null) ? AudioFile.Muffled(item.LoopFile, value) : (item.MuffleSuspenseLayer ? AudioFile.Muffled(audioFile, value) : audioFile)); } if (item.SuspenseFile != null && !string.IsNullOrEmpty(item.SuspenseLayer)) { ManualLogSource l2 = Plugin.L; BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(54, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Track '"); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(item.Id); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("': SuspenseLayer is unused with a Suspense clip"); } l2.LogWarning(val); } list.Add(item); } catch (Exception ex) { ManualLogSource l3 = Plugin.L; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(18, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Track '"); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(item.Id); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("' skipped: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message); } l3.LogError(val2); } } List<string> list2 = (from t in list group t by t.Key into g where g.Count() > 1 select g.First().Id).ToList(); if (list2.Count > 0) { ManualLogSource l4 = Plugin.L; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(21, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Duplicate track ids: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(string.Join(", ", list2)); } l4.LogError(val2); } try { Boot = Optional(folder, playlistFile.BootSound); } catch (Exception ex2) { Boot = null; ManualLogSource l5 = Plugin.L; BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(19, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("BootSound skipped: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex2.Message); } l5.LogError(val2); } BootVolume = playlistFile.BootVolume; Tracks = list; Hash = Fnv1a(string.Join("|", from t in list orderby t.Key select $"{t.Id}:{t.LoopFile.Frames}:{t.LeadInFile?.Frames}:{t.StingerFile?.Frames}:{t.SuspenseFile?.Frames}:{t.LayerFile?.Frames}:{t.LoopTail}")); foreach (Pool value2 in Enum.GetValues(typeof(Pool))) { ManualLogSource l6 = Plugin.L; BepInExInfoLogInterpolatedStringHandler val3 = new BepInExInfoLogInterpolatedStringHandler(7, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral("Pool "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<Pool>(value2); ((BepInExLogInterpolatedStringHandler)val3).AppendLiteral(": "); ((BepInExLogInterpolatedStringHandler)val3).AppendFormatted<string>(string.Join(", ", from t in PoolTracks(value2) select t.Id)); } l6.LogInfo(val3); } ManualLogSource l7 = Plugin.L; BepInExMessageLogInterpolatedStringHandler val4 = new BepInExMessageLogInterpolatedStringHandler(47, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("Playlist loaded: "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<int>(list.Count); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" tracks, "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<int>((from f in list.SelectMany((TrackDef t) => t.Files()) select f.Bytes).Distinct().Sum((byte[] b) => b.Length) / 1048576); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" MB compressed, hash "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<uint>(Hash, "X8"); } l7.LogMessage(val4); } private static AudioFile Optional(string folder, string relPath) { if (!string.IsNullOrEmpty(relPath)) { return AudioFile.Read(folder, relPath); } return null; } public static uint Fnv1a(string s) { uint num = 2166136261u; byte[] bytes = Encoding.UTF8.GetBytes(s); foreach (byte b in bytes) { num ^= b; num *= 16777619; } return num; } } } namespace GTFOCustomMusic.Patches { [HarmonyPatch] internal static class EnemyPatches { [HarmonyPatch(typeof(EnemyAgent), "Setup")] [HarmonyPostfix] [HarmonyWrapSafe] private static void Setup(EnemyAgent __instance) { if (MusicNet.IsHost) { Engagement.EnemySpawned(__instance); } } [HarmonyPatch(typeof(EnemyAI), "ModeChange")] [HarmonyPostfix] [HarmonyWrapSafe] private static void ModeChange(EnemyAI __instance) { if (MusicNet.IsHost && MusicRuntime.InLevel) { Engagement.EnemyMode(__instance.m_enemyAgent); } } [HarmonyPatch(typeof(EnemyAgent), "OnDead")] [HarmonyPostfix] [HarmonyWrapSafe] private static void OnDead(EnemyAgent __instance) { if (MusicNet.IsHost) { Engagement.EnemyGone(__instance, died: true); } } [HarmonyPatch(typeof(EnemyAgent), "OnDeSpawn")] [HarmonyPostfix] [HarmonyWrapSafe] private static void OnDeSpawn(EnemyAgent __instance) { if (MusicNet.IsHost) { Engagement.EnemyGone(__instance, died: false); } } } [HarmonyPatch] internal static class DamagePatches { private static int _sentryDepth; [HarmonyPatch(typeof(SentryGunInstance_Firing_Bullets), "FireBullet")] [HarmonyPrefix] private static void SentryFirePre() { _sentryDepth++; } [HarmonyPatch(typeof(SentryGunInstance_Firing_Bullets), "FireBullet")] [HarmonyPostfix] private static void SentryFirePost() { _sentryDepth--; } [HarmonyPatch(typeof(SentryGunInstance_Firing_Bullets), "UpdateFireMaster")] [HarmonyPrefix] private static void SentryUpdatePre() { _sentryDepth++; } [HarmonyPatch(typeof(SentryGunInstance_Firing_Bullets), "UpdateFireMaster")] [HarmonyPostfix] private static void SentryUpdatePost() { _sentryDepth--; } [HarmonyPatch(typeof(Dam_EnemyDamageBase), "BulletDamage")] [HarmonyPrefix] [HarmonyWrapSafe] private static void BulletDamage(Dam_EnemyDamageBase __instance, Agent __1) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Invalid comparison between Unknown and I4 if (MusicRuntime.InLevel) { EnemyAgent owner = __instance.Owner; if (!((Object)(object)owner == (Object)null)) { bool flag = (Object)(object)owner.AI != (Object)null && (int)((AgentAI)owner.AI).m_mode == 4; SignalKind kind = ((_sentryDepth > 0 || ((Object)(object)__1 != (Object)null && (int)__1.Type == 3)) ? SignalKind.SentryHit : ((!IsBot(__1)) ? (flag ? SignalKind.GunHitSleeper : SignalKind.GunHit) : SignalKind.BotHit)); MusicNet.Signal(kind, ((Agent)owner).GlobalID); } } } [HarmonyPatch(typeof(Dam_EnemyDamageBase), "MeleeDamage")] [HarmonyPrefix] [HarmonyWrapSafe] private static void MeleeDamage(Dam_EnemyDamageBase __instance, Agent __1) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Invalid comparison between Unknown and I4 if (MusicRuntime.InLevel) { EnemyAgent owner = __instance.Owner; if (!((Object)(object)owner == (Object)null)) { MusicNet.Signal((!((Object)(object)owner.AI != (Object)null) || (int)((AgentAI)owner.AI).m_mode != 4) ? ((!IsBot(__1)) ? SignalKind.MeleeAwake : SignalKind.BotHit) : SignalKind.StealthHit, ((Agent)owner).GlobalID); } } } [HarmonyPatch(typeof(Dam_EnemyDamageBase), "ExplosionDamage")] [HarmonyPrefix] [HarmonyWrapSafe] private static void ExplosionDamage(Dam_EnemyDamageBase __instance) { if (MusicRuntime.InLevel && !((Object)(object)__instance.Owner == (Object)null)) { MusicNet.Signal(SignalKind.Explosion, ((Agent)__instance.Owner).GlobalID); } } [HarmonyPatch(typeof(Dam_PlayerDamageBase), "OnIncomingDamage")] [HarmonyPrefix] [HarmonyWrapSafe] private static void PlayerIncomingDamage(Dam_PlayerDamageBase __instance, Agent __2) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 if (MusicRuntime.InLevel && !((Object)(object)__2 == (Object)null) && (int)__2.Type == 1) { PlayerAgent owner = __instance.Owner; if (!((Object)(object)owner == (Object)null) && ((Agent)owner).IsLocallyOwned) { MusicNet.Signal(SignalKind.PlayerHurt, __2.GlobalID); } } } private static bool IsBot(Agent a) { PlayerAgent val = ((a != null) ? ((Il2CppObjectBase)a).TryCast<PlayerAgent>() : null); if ((Object)(object)val != (Object)null && (Object)(object)val.Owner != (Object)null) { return val.Owner.IsBot; } return false; } } internal static class SoundEvents { private static Dictionary<uint, string> _names; private static readonly Dictionary<string, float> LastLogged = new Dictionary<string, float>(); public static string Name(uint id) { if (_names == null) { _names = new Dictionary<uint, string>(); foreach (PropertyInfo item in from p in typeof(EVENTS).GetProperties(BindingFlags.Static | BindingFlags.Public) where p.PropertyType == typeof(uint) select p) { try { _names.TryAdd((uint)item.GetValue(null), item.Name); } catch { } } Plugin.Verbose($"Read {_names.Count} sound event names"); } if (!_names.TryGetValue(id, out var value)) { return null; } return value; } public static bool Posted(string name, string idText) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Expected O, but got Unknown //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown if (PluginConfig.VerboseSignals.Value && !MusicRuntime.InLevel) { string key = name ?? idText; float realtimeSinceStartup = Time.realtimeSinceStartup; if (!LastLogged.TryGetValue(key, out var value) || realtimeSinceStartup - value > 5f) { LastLogged[key] = realtimeSinceStartup; ManualLogSource l = Plugin.L; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(9, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Sound "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(name ?? "?"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(idText); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(")"); } l.LogInfo(val); } } try { return BootSound.OnEvent(name); } catch (Exception ex) { ManualLogSource l2 = Plugin.L; bool flag2 = default(bool); BepInExErrorLogInterpolatedStringHandler val2 = new BepInExErrorLogInterpolatedStringHandler(19, 1, ref flag2); if (flag2) { ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral("Boot sound failed: "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(ex.Message); } l2.LogError(val2); return false; } } public static IEnumerable<MethodBase> PostMethods(Type firstParam) { return ((IEnumerable<Type>)new Type[2] { typeof(CellSound), typeof(CellSoundPlayer) }).SelectMany((Func<Type, IEnumerable<MethodInfo>>)AccessTools.GetDeclaredMethods).Where(delegate(MethodInfo m) { if (m.Name == "Post") { ParameterInfo[] parameters = m.GetParameters(); if (parameters != null && parameters.Length > 0) { return parameters[0].ParameterType == firstParam; } } return false; }); } } [HarmonyPatch] internal static class SoundIdPatches { private static IEnumerable<MethodBase> TargetMethods() { return SoundEvents.PostMethods(typeof(uint)); } [HarmonyPrefix] [HarmonyWrapSafe] private static bool Post(uint __0) { return !SoundEvents.Posted(SoundEvents.Name(__0), __0.ToString()); } } [HarmonyPatch] internal static class SoundNamePatches { private static IEnumerable<MethodBase> TargetMethods() { return SoundEvents.PostMethods(typeof(string)); } [HarmonyPrefix] [HarmonyWrapSafe] private static bool Post(string __0) { return !SoundEvents.Posted(__0, "by name"); } } [HarmonyPatch] internal static class AlarmPatches { [HarmonyPatch(typeof(ChainedPuzzleInstance), "OnStateChange")] [HarmonyPostfix] [HarmonyWrapSafe] private static void PuzzleState(ChainedPuzzleInstance __instance, pChainedPuzzleState __1) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Invalid comparison between Unknown and I4 //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Invalid comparison between Unknown and I4 if (MusicNet.IsHost && MusicRuntime.InLevel) { if (__instance.m_isExitPuzzle && (int)__1.status == 1) { Engagement.ExitScan(); } ChainedPuzzleDataBlock data = __instance.Data; if (data != null && data.TriggerAlarmOnActivate) { Engagement.AlarmPuzzle(((Il2CppObjectBase)__instance).Pointer, (int)__1.status == 1); } } } [HarmonyPatch(typeof(WardenObjectiveManager), "StopAlarms")] [HarmonyPostfix] [HarmonyWrapSafe] private static void StopAlarms() { if (MusicNet.IsHost && MusicRuntime.InLevel) { Engagement.StopAllAlarms(); } } } [HarmonyPatch] internal static class WavePatches { private static IEnumerable<MethodBase> TargetMethods() { return from m in AccessTools.GetDeclaredMethods(typeof(SurvivalWave)) where m.Name == "Spawn" select m; } [HarmonyPostfix] [HarmonyWrapSafe] private static void Spawned() { if (MusicNet.IsHost && MusicRuntime.InLevel) { MusicNet.Signal(SignalKind.WaveSpawned, 0); } } } [HarmonyPatch] internal static class GameEventPatches { private static readonly HashSet<eGameEvent> Spammy = new HashSet<eGameEvent> { (eGameEvent)21, (eGameEvent)19, (eGameEvent)20, (eGameEvent)28, (eGameEvent)53, (eGameEvent)129 }; private static readonly Dictionary<eGameEvent, float> LastLogged = new Dictionary<eGameEvent, float>(); [HarmonyPatch(typeof(GameEventManager), "PostEvent", new Type[] { typeof(eGameEvent), typeof(PlayerAgent), typeof(float), typeof(string), typeof(Dictionary<string, string>) })] [HarmonyPrefix] [HarmonyWrapSafe] private static void PostEvent(eGameEvent __0, PlayerAgent __1) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: Expected I4, but got Unknown //IL_0066: Unknown result type (might be due to invalid IL or missing references) if (PluginConfig.VerboseSignals.Value) { float realtimeSinceStartup = Time.realtimeSinceStartup; if (!Spammy.Contains(__0) || !LastLogged.TryGetValue(__0, out var value) || realtimeSinceStartup - value > 5f) { LastLogged[__0] = realtimeSinceStartup; ManualLogSource l = Plugin.L; bool flag = default(bool); BepInExInfoLogInterpolatedStringHandler val = new BepInExInfoLogInterpolatedStringHandler(18, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("GameEvent "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<eGameEvent>(__0); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" player="); object obj; if (__1 == null) { obj = null; } else { SNet_Player owner = __1.Owner; obj = ((owner != null) ? owner.NickName : null); } ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>((string)obj); } l.LogInfo(val); } } if (!MusicRuntime.InLevel) { return; } if ((int)__0 != 13) { switch (__0 - 43) { case 0: MusicNet.Signal(SignalKind.ScoutDetect, 0); break; case 1: MusicNet.Signal(SignalKind.ScoutScream, 0); break; case 2: MusicNet.Signal(SignalKind.ScoutDead, 0); break; } } else if ((Object)(object)__1 != (Object)null && (((Agent)__1).IsLocallyOwned || (MusicNet.IsHost && (Object)(object)__1.Owner != (Object)null && __1.Owner.IsBot))) { if (FallPatches.JustFell(__1)) { SNet_Player owner2 = __1.Owner; Plugin.Verbose("Fall damage (" + ((owner2 != null) ? owner2.NickName : null) + "), not contact"); } else { MusicNet.Signal(SignalKind.PlayerHurt, 0); } } } } [HarmonyPatch] internal static class ReactorPatches { [HarmonyPatch(typeof(LG_WardenObjective_Reactor), "OnBuildDone")] [HarmonyPostfix] [HarmonyWrapSafe] private static void BuildDone(LG_WardenObjective_Reactor __instance) { if (MusicNet.IsHost) { Engagement.ReactorBuilt(__instance); } } } [HarmonyPatch] internal static class FallPatches { private const float Window = 1f; private static readonly Dictionary<IntPtr, float> FellAt = new Dictionary<IntPtr, float>(); public static bool JustFell(PlayerAgent p) { if (FellAt.TryGetValue(((Il2CppObjectBase)p).Pointer, out var value)) { return Time.realtimeSinceStartup - value < 1f; } return false; } private static void Stamp(Dam_PlayerDamageBase d) { if ((Object)(object)d != (Object)null && (Object)(object)d.Owner != (Object)null) { FellAt[((Il2CppObjectBase)d.Owner).Pointer] = Time.realtimeSinceStartup; } } [HarmonyPatch(typeof(Dam_SyncedDamageBase), "FallDamage")] [HarmonyPrefix] [HarmonyWrapSafe] private static void Fall(Dam_SyncedDamageBase __instance) { Stamp(((Il2CppObjectBase)__instance).TryCast<Dam_PlayerDamageBase>()); } [HarmonyPatch(typeof(Dam_PlayerDamageBase), "ReceiveFallDamage")] [HarmonyPrefix] [HarmonyWrapSafe] private static void ReceiveFall(Dam_PlayerDamageBase __instance) { Stamp(__instance); } } [HarmonyPatch] internal static class SettingsPatches { [HarmonyPatch(typeof(CellSettingsApply), "ApplyMusicVolume")] [HarmonyPostfix] [HarmonyWrapSafe] private static void ApplyMusicVolume(float __0) { GameMusicMute.OnGameApplied(__0); } [HarmonyPatch(typeof(CellSettingsApply), "ApplyMasterVolume")] [HarmonyPostfix] [HarmonyWrapSafe] private static void ApplyMasterVolume(float __0) { GameVolume.OnMaster(__0); } } } namespace GTFOCustomMusic.Net { internal static class MusicNet { private const string EvState = "GTFOCustomMusic.State"; private const string EvSignal = "GTFOCustomMusic.Signal"; private const string EvPreload = "GTFOCustomMusic.Preload"; private const string EvHello = "GTFOCustomMusic.Hello"; private const string EvPing = "GTFOCustomMusic.Ping"; private const string EvPong = "GTFOCustomMusic.Pong"; private const string EvPause = "GTFOCustomMusic.Pause"; private static readonly Dictionary<(SignalKind, ushort), double> Throttle = new Dictionary<(SignalKind, ushort), double>(); public static bool IsHost { get { if (SNet.HasMaster) { return SNet.IsMaster; } return true; } } public static void Register() { NetworkAPI.RegisterEvent<StatePacket>("GTFOCustomMusic.State", (Action<ulong, StatePacket>)delegate(ulong sender, StatePacket p) { if (!IsHost) { SNet_Player master = SNet.Master; if (sender == ((master != null) ? new ulong?(master.Lookup) : ((ulong?)null))) { MusicPlayer.Apply(p); } } }); NetworkAPI.RegisterEvent<SignalPacket>("GTFOCustomMusic.Signal", (Action<ulong, SignalPacket>)delegate(ulong sender, SignalPacket p) { if (IsHost) { Engagement.Signal(p.Kind, p.Enemy, NameOf(sender)); } }); NetworkAPI.RegisterEvent<PreloadPacket>("GTFOCustomMusic.Preload", (Action<ulong, PreloadPacket>)delegate(ulong sender, PreloadPacket p) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown if (!IsHost) { TrackDef trackDef = PlaylistStore.Get(p.Track); if (trackDef != null) { ClipCache.Request(trackDef); } else { ManualLogSource l = Plugin.L; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(28, 1, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendLiteral("Host preloads unknown track "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<uint>(p.Track, "X8"); } l.LogWarning(val); } } }); NetworkAPI.RegisterEvent<HelloPacket>("GTFOCustomMusic.Hello", (Action<ulong, HelloPacket>)delegate(ulong sender, HelloPacket p) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Expected O, but got Unknown if (IsHost) { if (p.PlaylistHash != PlaylistStore.Hash) { ManualLogSource l = Plugin.L; bool flag = default(bool); BepInExWarningLogInterpolatedStringHandler val = new BepInExWarningLogInterpolatedStringHandler(32, 3, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<string>(NameOf(sender)); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" has a different playlist ("); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<uint>(p.PlaylistHash, "X8"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(" vs "); ((BepInExLogInterpolatedStringHandler)val).AppendFormatted<uint>(PlaylistStore.Hash, "X8"); ((BepInExLogInterpolatedStringHandler)val).AppendLiteral(")"); } l.LogWarning(val); } SNet_Player val2 = Find(sender); if (!((Object)(object)val2 == (Object)null)) { foreach (TrackDef item in MusicDirector.PreloadSet()) { NetworkAPI.InvokeEvent<PreloadPacket>("GTFOCustomMusic.Preload", new PreloadPacket { Track = item.Key }, val2, (SNet_ChannelType)2); } if (SyncClock.Paused) { NetworkAPI.InvokeEvent<PausePacket>("GTFOCustomMusic.Pause", new PausePacket { Paused = 1 }, val2, (SNet_ChannelType)2); } if (MusicDirector.Current.Seq != 0) { StatePacket statePacket = SyncClock.ToWire(MusicDirector.Current); statePacket.Flags |= StateFlags.Reset; NetworkAPI.InvokeEvent<StatePacket>("GTFOCustomMusic.State", statePacket, val2, (SNet_ChannelType)2); } } } }); NetworkAPI.RegisterEvent<PingPacket>("GTFOCustomMusic.Ping", (Action<ulong, PingPacket>)delegate(ulong sender, PingPacket p) { if (IsHost) { SNet_Player val = Find(sender); if ((Object)(object)val != (Object)null) { NetworkAPI.InvokeEvent<PongPacket>("GTFOCustomMusic.Pong", new PongPacket { ClientT = p.ClientT, HostT = SyncClock.Raw }, val, (SNet_ChannelType)4); } } }); NetworkAPI.RegisterEvent<PongPacket>("GTFOCustomMusic.Pong", (Action<ulong, PongPacket>)delegate(ulong sender, PongPacket p) { if (!IsHost) { SNet_Player master = SNet.Master; if (sender == ((master != null) ? new ulong?(master.Lookup) : ((ulong?)null))) { SyncClock.OnPong(p.ClientT, p.HostT); } } }); NetworkAPI.RegisterEvent<PausePacket>("GTFOCustomMusic.Pause", (Action<ulong, PausePacket>)delegate(ulong sender, PausePacket p) { if (!IsHost) { SNet_Player master = SNet.Master; if (sender == ((master != null) ? new ulong?(master.Lookup) : ((ulong?)null))) { Plugin.Verbose((p.Paused != 0) ? "Host paused" : $"Host resumed after {p.Length:F2}s"); SyncClock.OnHostPause(p.Paused != 0, p.Length); } } }); } public static void SendState(StatePacket p) { if (SNet.HasMaster && SNet.IsMaster) { NetworkAPI.InvokeEvent<StatePacket>("GTFOCustomMusic.State", p, (SNet_ChannelType)2); } } public static void SendPreload(uint track) { if (SNet.HasMaster && SNet.IsMaster) { NetworkAPI.InvokeEvent<PreloadPacket>("GTFOCustomMusic.Preload", new PreloadPacket { Track = track }, (SNet_ChannelType)2); } } public static void SendPause(bool paused, double length) { if (SNet.HasMaster && SNet.IsMaster) { NetworkAPI.InvokeEvent<PausePacket>("GTFOCustomMusic.Pause", new PausePacket { Paused = (paused ? ((byte)1) : ((byte)0)), Length = length }, (SNet_ChannelType)2); } } public static void SendHello() { if (!IsHost) { NetworkAPI.InvokeEvent<HelloPacket>("GTFOCustomMusic.Hello", new HelloPacket { PlaylistHash = PlaylistStore.Hash }, SNet.Master, (SNet_ChannelType)2); } } public static void SendPing(double now) { if (!IsHost && (Object)(object)SNet.Master != (Object)null) { NetworkAPI.InvokeEvent<PingPacket>("GTFOCustomMusic.Ping", new PingPacket { ClientT = now }, SNet.Master, (SNet_ChannelType)4); } } public static void Signal(SignalKind kind, ushort enemy = 0) { double now = SyncClock.Now; SignalKind item = kind; bool flag = ((kind == SignalKind.StealthHit || kind == SignalKind.GunHitSleeper) ? true : false); (SignalKind, ushort) key = (item, (ushort)(flag ? enemy : 0)); if (!Throttle.TryGetValue(key, out var value) || !(now < value)) { Throttle[key] = now + 0.25; if (IsHost) { Engagement.Signal(kind, enemy, "host"); } else if ((Object)(object)SNet.Master != (Object)null) { NetworkAPI.InvokeEvent<SignalPacket>("GTFOCustomMusic.Signal", new SignalPacket { Kind = kind, Enemy = enemy }, SNet.Master, (SNet_ChannelType)2); } } } private static SNet_Player Find(ulong lookup) { List<SNet_Player> lobbyPlayers = SNet.LobbyPlayers; if (lobbyPlayers == null) { return null; } for (int i = 0; i < lobbyPlayers.Count; i++) { if ((Object)(object)lobbyPlayers[i] != (Object)null && lobbyPlayers[i].Lookup == lookup) { return lobbyPlayers[i]; } } return null; } private static string NameOf(ulong lookup) { SNet_Player obj = Find(lookup); return ((obj != null) ? obj.NickName : null) ?? lookup.ToString(); } } public enum Phase : byte { Idle, Suspense, Combat, Cooldown } public enum SignalKind : byte { StealthHit, MeleeAwake, GunHit, GunHitSleeper, SentryHit, BotHit, Explosion, PlayerHurt, WaveSpawned, ScoutDetect, ScoutScream, ScoutDead } [Flags] public enum StateFlags : byte { None = 0, Reset = 1, BoundarySwitch = 2, Hot = 4 } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct StatePacket { public int Seq; public Phase Phase; public byte Pool; public StateFlags Flags; public uint Track; public uint PlaylistHash; public double PhaseAt; public double LoopAnchor; public double SuspenseAnchor; public double StingerAt; public double LeadInAt; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct SignalPacket { public SignalKind Kind; public ushort Enemy; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PreloadPacket { public uint Track; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PausePacket { public byte Paused; public double Length; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct HelloPacket { public uint PlaylistHash; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PingPacket { public double ClientT; } [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct PongPacket { public double ClientT; public double HostT; } internal struct Ramp { private double _t0; private double _dur; private float _from; private float _to; public float Target => _to; public static Ramp Const(float v) { return new Ramp { _from = v, _to = v }; } public float Eval(double t) { if (t <= _t0) { return _from; } if (_dur <= 0.0 || t >= _t0 + _dur) { return _to; } return _from + (_to - _from) * (float)((t - _t0) / _dur); } public Ramp To(float target, double at, double dur) { return new Ramp { _from = Eval(at), _to = target, _t0 = at, _dur = Math.Max(0.0, dur) }; } } internal static class SyncClock { private static readonly Stopwatch Sw = Stopwatch.StartNew(); private static double _lastDsp; private static double _lastDspWall; private static double _pausedTotal; private static double _heldAt; private static double _frozenNow; private static bool _localPaused; private static bool _hostPaused; private static bool _heldAsHost; private static readonly List<(double rtt, double offset)> Samples = new List<(double, double)>(); private static double _nextPing; public static double Raw { get { double dspTime = AudioSettings.dspTime; double totalSeconds = Sw.Elapsed.TotalSeconds; if (dspTime != _lastDsp) { _lastDsp = dspTime; _lastDspWall = totalSeconds; } return dspTime + Math.Min(totalSeconds - _lastDspWall, 0.1); } } public static bool Paused { get; private set; } public static int Jumps { get; private set; } public static double PausedTotal => _pausedTotal; public static double Now { get { if (!Paused) { return Raw - _pausedTotal; } return _frozenNow; } } public static double Offset { get; private set; } public static double Rtt { get; private set; } = double.NaN; public static bool Synced { get { if (!IsHost) { return Samples.Count > 0; } return true; } } public static bool IsHost { get; set; } = true; public static void SetLocalPaused(bool paused) { if (paused != _localPaused) { _localPaused = paused; UpdateHold(); } } public static void OnHostPause(bool paused, double length) { if (!IsHost) { if (!paused && _hostPaused) { _pausedTotal += length; } _hostPaused = paused; UpdateHold(); } } private static void UpdateHold() { bool flag = _localPaused || (_hostPaused && !IsHost); if (flag == Paused) { return; } double raw = Raw; if (flag) { _heldAt = raw; _frozenNow = raw - _pausedTotal; _heldAsHost = IsHost; Jumps++; if (IsHost) { MusicNet.SendPause(paused: true, 0.0); } } else if (_heldAsHost && IsHost) { double num = raw - _heldAt; _pausedTotal += num; MusicNet.SendPause(paused: false, num); } Paused = flag; } public static double ToLocal(double hostTime) { return hostTime - Offset - _pausedTotal; } public static double ToDsp(double t) { return t + _pausedTotal; } public static StatePacket ToWire(StatePacket s) { double pausedTotal = _pausedTotal; if (s.PhaseAt > 0.0) { s.PhaseAt += pausedTotal; } if (s.LoopAnchor > 0.0) { s.LoopAnchor += pausedTotal; } if (s.SuspenseAnchor > 0.0) { s.SuspenseAnchor += pausedTotal; } if (s.StingerAt > 0.0) { s.StingerAt += pausedTotal; } if (s.LeadInAt > 0.0) { s.LeadInAt += pausedTotal; } return s; } public static void Reset() { Samples.Clear(); Offset = 0.0; Rtt = double.NaN; _nextPing = 0.0; _hostPaused = false; UpdateHold(); } public static void Tick() { if (IsHost) { Offset = 0.0; return; } double raw = Raw; if (!(raw < _nextPing)) { _nextPing = raw + ((Samples.Count < 8) ? 0.25 : 2.0); MusicNet.SendPing(raw); } } public static void OnPong(double clientSent, double hostTime) { double raw = Raw; double num = raw - clientSent; if (!(num < 0.0) && !(num > 2.0)) { Samples.Add((num, hostTime + num / 2.0 - raw)); if (Samples.Count > 12) { Samples.RemoveAt(0); } (double rtt, double offset) tuple = Samples.OrderBy(((double rtt, double offset) s) => s.rtt).First(); Offset = tuple.offset; (Rtt, _) = tuple; } } } } namespace GTFOCustomMusic.Director { internal static class Engagement { private sealed class Tracked { public EnemyAgent Agent; public bool Awake; public bool Wave; public bool Seen; } private const double WaveLinger = 45.0; private const double ScreamHold = 20.0; private static readonly HashSet<IntPtr> Alarms = new HashSet<IntPtr>(); private static double _alarmEndedAt = double.NegativeInfinity; private static bool _wasAlarm; private const double PollInterval = 0.25; private const double WaveSpawnGrace = 5.0; private static readonly Dictionary<IntPtr, Tracked> Enemies = new Dictionary<IntPtr, Tracked>(); private static readonly Dictionary<IntPtr, (LG_WardenObjective_Reactor Reactor, eReactorStatus Status)> Reactors = new Dictionary<IntPtr, (LG_WardenObjective_Reactor, eReactorStatus)>(); private static double _nextPoll; private static double _levelStart = double.PositiveInfinity; public static double LastActivity { get; private set; } = double.NegativeInfinity; public static double LastContact { get; private set; } = double.NegativeInfinity; public static double LastCombatActivity { get; private set; } = double.NegativeInfinity; public static double LastWake { get; private set; } = double.NegativeInfinity; public static double LastScout { get; private set; } = double.NegativeInfinity; public static double LastScream { get; private set; } = double.NegativeInfinity; public static bool ScoutNearby => SyncClock.Now - LastScout < (double)PluginConfig.ScoutHold.Value; public static bool SurvivalMusic { get; private set; } public static bool ObjectiveComplete { get; private set; } public static bool ExitScanStarted { get; private set; } public static int AwakeCount { get; private set; } public static int WaveAlive { get; private set; } public static bool AlarmActive { get { if (Alarms.Count <= 0) { return SurvivalMusic; } return true; } } public static bool Hot { get { if (!AlarmActive && !(SyncClock.Now - LastScream < 20.0)) { if (WaveAlive > 0) { return SyncClock.Now - Math.Max(_alarmEndedAt, LastCombatActivity) < 45.0; } return false; } return true; } } public static void Reset() { Alarms.Clear(); LastActivity = (LastContact = (LastCombatActivity = (LastWake = (LastScout = (LastScream = (_alarmEndedAt = double.NegativeInfinity)))))); _wasAlarm = false; bool num6 = (ExitScanStarted = false); ObjectiveComplete = num6; SurvivalMusic = num6; AwakeCount = (WaveAlive = 0); ResetEnemies(); } private static void ResetEnemies() { _levelStart = SyncClock.Now; foreach (Tracked value in Enemies.Values) { value.Awake = false; } foreach (IntPtr item in Reactors.Keys.ToList()) { Reactors[item] = (Reactors[item].Reactor, (eReactorStatus)0); } } public static void Signal(SignalKind kind, ushort enemy, string from) { double now = SyncClock.Now; Plugin.Verbose($"Signal {kind} enemy={enemy} from={from} awake={AwakeCount} wave={WaveAlive} alarm={AlarmActive}"); LastActivity = now; switch (kind) { case SignalKind.MeleeAwake: case SignalKind.GunHit: case SignalKind.PlayerHurt: LastContact = now; LastCombatActivity = now; break; case SignalKind.ScoutDetect: LastScout = now; break; case SignalKind.ScoutDead: LastScout = double.NegativeInfinity; break; case SignalKind.ScoutScream: LastScream = now; LastCombatActivity = now; break; default: LastCombatActivity = now; break; case SignalKind.StealthHit: case SignalKind.WaveSpawned: break; } } public static void AlarmPuzzle(IntPtr puzzle, bool active) { if (active ? Alarms.Add(puzzle) : Alarms.Remove(puzzle)) { LastActivity = SyncClock.Now; NoteAlarmState(); Plugin.Verbose($"Alarm puzzle {(active ? "started" : "ended")} ({Alarms.Count} active, {WaveAlive} wave enemies alive)"); } } public static void Reactor(IntPtr reactor, string status, bool active) { bool num = (active ? Alarms.Add(reactor) : Alarms.Remove(reactor)); Plugin.Verbose($"Reactor {status}: alarm {(active ? "on" : "off")} ({Alarms.Count} active, {WaveAlive} wave enemies fighting)"); if (num) { LastActivity = SyncClock.Now; NoteAlarmState(); } } public static void ExitScan() { if (!ExitScanStarted) { ExitScanStarted = true; LastActivity = SyncClock.Now; Plugin.Verbose("Extraction scan started -> extraction context"); } } public static void StopAllAlarms() { Alarms.Clear(); NoteAlarmState(); Plugin.Verbose("All alarms stopped"); } private static void NoteAlarmState() { if (_wasAlarm && !AlarmActive) { _alarmEndedAt = SyncClock.Now; } _wasAlarm = AlarmActive; } public static void ReactorBuilt(LG_WardenObjective_Reactor r) { if ((Object)(object)r != (Object)null) { Reactors[((Il2CppObjectBase)r).Pointer] = (r, (eReactorStatus)0); } } private static void PollReactors() { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Invalid comparison between Unknown and I4 //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00cf: Invalid comparison between Unknown and I4 foreach (KeyValuePair<IntPtr, (LG_WardenObjective_Reactor, eReactorStatus)> item in Reactors.ToList()) { item.Deconstruct(out var key, out var value); (LG_WardenObjective_Reactor, eReactorStatus) tuple = value; IntPtr intPtr = key; var (val, val2) = tuple; if ((Object)(object)val == (Object)null || ((Il2CppObjectBase)val).WasCollected) { Reactors.Remove(intPtr); continue; } eReactorStatus status = val.m_currentState.status; if (status != val2) { Reactors[intPtr] = (val, status); key = intPtr; string status2 = $"{status} (wave {val.m_currentWaveCount})"; bool active = ((status - 2 <= 1 || (int)status == 8) ? true : false); Reactor(key, status2, active); } } } public static void ClearEnemies() { Enemies.Clear(); Reactors.Clear(); _levelStart = double.PositiveInfinity; AwakeCount = (WaveAlive = 0); } public static void EnemySpawned(EnemyAgent enemy) { if (!((Object)(object)enemy == (Object)null)) { Enemies[((Il2CppObjectBase)enemy).Pointer] = new Tracked { Agent = enemy, Wave = (SyncClock.Now - _levelStart > 5.0) }; } } public static void EnemyMode(EnemyAgent enemy) { if (!((Object)(object)enemy == (Object)null)) { if (!Enemies.TryGetValue(((Il2CppObjectBase)enemy).Pointer, out var value)) { EnemySpawned(enemy); } if (Enemies.TryGetValue(((Il2CppObjectBase)enemy).Pointer, out value)) { Poll(value, SyncClock.Now); } Recount(); } } public static void EnemyGone(EnemyAgent enemy, bool died) { if (!((Object)(object)enemy == (Object)null) && Enemies.Remove(((Il2CppObjectBase)enemy).Pointer, out var value)) { if (died) { LastCombatActivity = Math.Max(LastCombatActivity, SyncClock.Now); } Recount(); if (value.Awake || value.Wave) { Plugin.Verbose($"Enemy {((Agent)enemy).GlobalID} {(died ? "died" : "despawned")}{(value.Wave ? " (wave)" : "")} ({AwakeCount} awake, {WaveAlive} wave)"); } } } private static bool Poll(Tracked t, double now) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Invalid comparison between Unknown and I4 //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Invalid comparison between Unknown and I4 //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) EnemyAgent agent = t.Agent; if ((Object)(object)agent == (Object)null || ((Il2CppObjectBase)agent).WasCollected || !((Agent)agent).Alive) { return false; } ushort globalID = ((Agent)agent).GlobalID; AgentMode val = (AgentMode)(((Object)(object)agent.AI != (Object)null) ? ((int)((AgentAI)agent.AI).m_mode) : 0); if (!t.Seen && (int)val != 0) { t.Seen = true; if (t.Wave && (int)val != 1) { t.Wave = false; } if (t.Wave) { Plugin.Verbose($"Wave enemy {globalID} spawned ({val})"); } } bool flag = (int)val == 1; if (flag && !t.Awake) { LastWake = (LastActivity = now); t.Awake = true; Plugin.Verbose($"Enemy {globalID} awake{(t.Wave ? " (wave)" : "")}"); } else if (!flag && t.Awake) { t.Awake = false; Plugin.Verbose($"Enemy {globalID} -> {val}"); } return true; } private static void Recount() { int num = 0; int num2 = 0; foreach (Tracked value in Enemies.Values) { if (value.Awake) { num++; } if (value.Wave && value.Awake) { num2++; } } AwakeCount = num; WaveAlive = num2; } public static void Tick() { //IL_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Invalid comparison between Unknown and I4 //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Invalid comparison between Unknown and I4 double now = SyncClock.Now; if (now < _nextPoll) { return; } _nextPoll = now + 0.25; List<IntPtr> list = null; foreach (var (item, t) in Enemies) { if (!Poll(t, now)) { (list ?? (list = new List<IntPtr>())).Add(item); } } if (list != null) { foreach (IntPtr item2 in list) { Enemies.Remove(item2); } } Recount(); MUS_State val = MusicManager.CurrentState(); bool flag = val - 10 <= 3; bool flag2 = flag; if (flag2 != SurvivalMusic) { Plugin.Verbose($"GTFO music state {val} (survival={flag2})"); } SurvivalMusic = flag2; if (flag2) { LastActivity = now; } PollReactors(); NoteAlarmState(); pWardenObjectiveState currentState = WardenObjectiveManager.CurrentState; int num; if (currentState != null) { num = (((int)currentState.main_status == 40) ? 1 : 0); if (num != 0 && !ObjectiveComplete) { Plugin.Verbose("Main objective complete -> extraction context"); } } else { num = 0; } ObjectiveComplete = (byte)num != 0; } } internal static class MusicDirector { internal static Random Rng = new Random(); internal static Func<Pool, TrackDef> PickOverride; private static readonly Dictionary<Pool, TrackDef> Next = new Dictionary<Pool, TrackDef>(); private static readonly Dictionary<Pool, uint> LastPicked = new Dictionary<Pool, uint>(); private static int _seq = Environment.TickCount & 0x3FFFFFFF; private static StatePacket _state; private static TrackDef _track; private static double _cooldownEnd; private static double _quietSince = double.NegativeInfinity; private static double _combatSince; private static double _trackSince; private static bool _pendingReset; private static bool _lastMeme; private static bool _seriousEpisode; private static TrackDef _lastTrack; private static double _lastEnd; private static double _lastLoopAnchor; private static double _lastSuspenseAnchor; private static int _resumes; private const int MaxResumes = 2; private static double _resumeLoop; private static double _resumeSuspense; private const int LeadInRunOnBars = 8; private const double LeadInRunOnSeconds = 15.0; private const double MaxQuantizeWait = 2.5; private static bool MemeOk { get { if (!_lastMeme) { return !_seriousEpisode; } return false; } } public static Phase Phase => _state.Phase; public static Pool Pool => (Pool)_state.Pool; public static StatePacket Current => _state; private static Pool DesiredPool { get { if ((PluginConfig.Extraction.Value != ExtractionTrigger.ExitScan || !Engagement.ExitScanStarted) && (PluginConfig.Extraction.Value != ExtractionTrigger.ObjectiveComplete || !Engagement.ObjectiveComplete)) { if (!Engagement.Hot) { return Pool.Combat; } return Pool.Alarm; } return Pool.Extraction; } } private static Pool EpisodePool { get { Pool pool = DesiredPool; if (_seriousEpisode && pool == Pool.Alarm) { pool = (Pool)_state.Pool; } return (Pool)Math.Max(_state.Pool, (byte)pool); } } public static void OnLevelStart() { Engagement.Reset(); _track = null; _state = default(StatePacket); _pendingReset = true; _quietSince = double.NegativeInfinity; _lastMeme = (_seriousEpisode = false); _lastTrack = null; _resumes = 0; Broadcast(Phase.Idle, SyncClock.Now); PickNextAll(); } public static void OnLevelEnd() { Engagement.Reset(); Engagement.ClearEnemies(); _track = null; _state = default(StatePacket); } private static bool Commit(double now, double since) { int awakeCount = Engagement.AwakeCount; if (Engagement.LastScream > since && now - Engagement.LastScream < (double)PluginConfig.ContactWindow.Value) { return true; } bool flag = Engagement.LastContact > since && now - Engagement.LastContact < (double)PluginConfig.ContactWindow.Value; if (Engagement.Hot) { return flag; } bool flag2 = Engagement.LastWake > since; if (!(awakeCount >= PluginConfig.RoomWakeThreshold.Value && flag2)) { if (flag) { return awakeCount >= PluginConfig.ContactMinAwake.Value; } return false; } return true; } public static void Tick() { if (PlaylistStore.Tracks.Count == 0) { return; } double now = SyncClock.Now; float value = PluginConfig.ScheduleLead.Value; int awakeCount = Engagement.AwakeCount; bool hot = Engagement.Hot; bool flag = Commit(now, _quietSince); double num = now - Engagement.LastActivity; double num2 = now - Math.Max(_combatSince, Math.Max(Engagement.LastCombatActivity, Engagement.LastWake)); switch (_state.Phase) { case Phase.Idle: if (flag) { StartEpisode(); EnterCombat(now + (double)value, (_resumeLoop > 0.0) ? _resumeLoop : (now + (double)value), 0.0); _resumeLoop = (_resumeSuspense = 0.0); } else if ((Engagement.LastActivity > _quietSince && num < 0.5) || hot) { StartEpisode(); EnterSuspense(now + (double)value); } break; case Phase.Suspense: if (flag) { CommitFromSuspense(now, value); } else if (!hot && !Engagement.ScoutNearby && num > (double)PluginConfig.SuspenseHold.Value && (awakeCount == 0 || num > (double)PluginConfig.CombatMaxQuietWithAwake.Value)) { _quietSince = now; EndEpisode(now + (double)value); } else if ((uint)EpisodePool != _state.Pool && !PoolHas(EpisodePool, _track)) { _state.Pool = (byte)EpisodePool; _track = TakeNext(EpisodePool, _track); _trackSince = now; EnterSuspense(now + (double)value); } else if (hot != ((_state.Flags & StateFlags.Hot) != 0) && _state.LeadInAt > 0.0 && now + (double)value < _state.LoopAnchor) { Broadcast(Phase.Suspense, now + (double)value); } break; case Phase.Combat: { bool num3 = awakeCount == 0 && !hot && num2 > (double)PluginConfig.CombatQuietTime.Value; bool flag2 = !Engagement.AlarmActive && num2 > (double)PluginConfig.CombatMaxQuietWithAwake.Value; if (num3 || flag2) { _quietSince = now; _cooldownEnd = now + (double)value + (double)PluginConfig.CooldownFade.Value; Broadcast(Phase.Cooldown, now + (double)value); } else { MaybeSwitchAtBoundary(now, value); } break; } case Phase.Cooldown: if (flag) { Broadcast(Phase.Combat, now + (double)value); } else if (now > _cooldownEnd) { EndEpisode(now); } break; } } private static void EndEpisode(double at) { _lastTrack = _track; _lastEnd = SyncClock.Now; _lastLoopAnchor = _state.LoopAnchor; _lastSuspenseAnchor = _state.SuspenseAnchor; Broadcast(Phase.Idle, at); _track = null; } private static void StartEpisode() { double now = SyncClock.Now; Pool pool = (Pool)(_state.Pool = (byte)DesiredPool); _seriousEpisode = Engagement.ScoutNearby; _resumeLoop = (_resumeSuspense = 0.0); TrackDef lastTrack = _lastTrack; _lastTrack = null; if (lastTrack != null && now - _lastEnd < (double)PluginConfig.SameTrackWindow.Value && PoolHas(pool, lastTrack) && (!lastTrack.Meme || !_seriousEpisode) && _resumes < 2) { _resumes++; _track = lastTrack; if (_lastLoopAnchor > 0.0 && _lastLoopAnchor <= now) { _resumeLoop = _lastLoopAnchor; } if (_lastSuspenseAnchor > 0.0 && _lastSuspenseAnchor <= now) { _resumeSuspense = _lastSuspenseAnchor; } Plugin.Verbose($"Episode resumes: pool={pool} track={_track.Id} ({now - _lastEnd:F1}s after the last one, {_resumes}/{2})"); } else { _resumes = 0; _track = TakeNext(pool, null); _trackSince = now; DefaultInterpolatedStringHandler defaultInterpolatedStringHandler = new DefaultInterpolatedStringHandler(27, 4); defaultInterpolatedStringHandler.AppendLiteral("Episode start: pool="); defaultInterpolatedStringHandler.AppendFormatted(pool); defaultInterpolatedStringHandler.AppendLiteral(" track="); defaultInterpolatedStringHandler.AppendFormatted(_track?.Id); TrackDef track = _track; defaultInterpolatedStringHandler.AppendFormatted((track != null && track.Meme) ? " (meme)" : ""); defaultInterpolatedStringHandler.AppendFormatted(_seriousEpisode ? " (scout: no memes)" : ""); Plugin.Verbose(defaultInterpolatedStringHandler.ToStringAndClear()); } } private static void EnterSuspense(double at) { if (_track != null) { _state.StingerAt = (_state.LeadInAt = (_state.SuspenseAnchor = (_state.LoopAnchor = 0.0))); if (_track.LeadInFile != null && (!_track.LeadInAlarmOnly || Engagement.Hot) && _resumeLoop <= 0.0 && _resumeSuspense <= 0.0) { _state.LeadInAt = at; _state.LoopAnchor = at + _track.LeadInSeconds; } else if (_track.SuspenseFile != null) { _state.SuspenseAnchor = ((_resumeSuspense > 0.0) ? _resumeSuspense : at); } else if (_resumeLoop > 0.0) { _state.LoopAnchor = _resumeLoop; } else { _state.LoopAnchor = at; } _resumeLoop = (_resumeSuspense = 0.0); Broadcast(Phase.Suspense, at); } } private static void EnterCombat(double at, double loopAnchor, double stingerAt, double leadInAt = 0.0) { if (_track != null) { _state.LoopAnchor = loopAnchor; _state.StingerAt = stingerAt; _state.LeadInAt = leadInAt; Broadcast(Phase.Combat, at); } } private static void CommitFromSuspense(double now, double lead) { double num = now + lead; bool flag = Engagement.LastScream > _quietSince && now - Engagement.LastScream < (double)PluginConfig.ContactWindow.Value; StingerUse value = PluginConfig.Stingers.Value; bool flag2 = _track.StingerFile != null && (value == StingerUse.Always || (value == StingerUse.ScoutScream && flag)); double num2 = ((_state.LeadInAt > 0.0) ? _state.LoopAnchor : 0.0); if (num2 > num) { double barSeconds = _track.BarSeconds; double num3 = (flag2 ? _track.StingerSeconds : ((barSeconds > 0.0) ? (8.0 * barSeconds) : 15.0)); if (num2 - num <= num3 + 0.05) { EnterCombat(num, num2, 0.0, _state.LeadInAt); return; } double num4 = ((barSeconds > 0.0) ? (num2 - Math.Floor((num2 - num) / barSeconds) * barSeconds) : num); if (num4 - num > 2.5) { num4 = num; } if (flag2) { EnterCombat(num4, num4 + _track.StingerSeconds, num4); } else { EnterCombat(num4, num4, 0.0); } } else { num = QuantizeToBar(num); if (flag2) { EnterCombat(num, num + _track.StingerSeconds, num); } else if (_state.SuspenseAnchor > 0.0) { EnterCombat(num, num, 0.0); } else { EnterCombat(num, _state.LoopAnchor, 0.0); } } } private static double QuantizeToBar(double t) { bool flag = _state.SuspenseAnchor > 0.0; double num = (flag ? _track.SuspenseBarSeconds : _track.BarSeconds); double num2 = (flag ? _track.SuspensePeriod : _track.LoopPeriod); if (num <= 0.0 || num2 <= 0.0) { return t; } double num3 = (flag ? _state.SuspenseAnchor : _state.LoopAnchor); double num4 = num3 + Math.Max(0.0, Math.Floor((t - num3) / num2)) * num2; double num5 = num4 + (double)_track.FirstBeat; double num6 = Math.Min(num5 + Math.Max(0.0, Math.Ceiling((t - num5) / num)) * num, num4 + num2 + (double)_track.FirstBeat); if (!(num6 - t < 2.5)) { return t; } return num6; } private static void MaybeSwitchAtBoundary(double now, double lead) { double loopPeriod = _track.LoopPeriod; double loopAnchor = _state.LoopAnchor; if (now < loopAnchor) { return; } long num = (long)Math.Floor((now - loopAnchor) / loopPeriod); double num2 = loopAnchor + (double)(num + 1) * loopPeriod; double num3 = num2 - now; if (num3 > 1.5 || num3 < lead) { return; } Pool episodePool = EpisodePool; float value = PluginConfig.RotateAfter.Value; bool flag = (uint)episodePool != _state.Pool && !PoolHas(episodePool, _track); if (flag || (episodePool != Pool.Extraction && value > 0f && now - _trackSince >= (double)value && PlaylistStore.PoolTracks(episodePool).Count > 1)) { TrackDef trackDef = TakeNext(episodePool, _track); if (trackDef != null && trackDef != _track) { Plugin.Verbose($"Loop seam switch {_track.Id} -> {trackDef.Id}{(trackDef.Meme ? " (meme)" : "")} after {now - _trackSince:F0}s{(flag ? " (pool)" : "")}"); _track = trackDef; _trackSince = now; _state.Pool = (byte)episodePool; _state.LoopAnchor = num2; _state.StingerAt = (_state.LeadInAt = 0.0); _state.SuspenseAnchor = 0.0; Broadcast(Phase.Combat, num2, StateFlags.BoundarySwitch); } } } private static bool PoolHas(Pool pool, TrackDef t) { if (t != null) { return PlaylistStore.PoolTracks(pool).Contains(t); } return false; } private static TrackDef TakeNext(Pool pool, TrackDef exclude) { TrackDef trackDef = PickOverride?.Invoke(pool); TrackDef value; if (trackDef != null) { value = trackDef; } else if (!Next.TryGetValue(pool, out value) || value == exclude || !PoolHas(pool, value) || (value.Meme && !MemeOk)) { value = Pick(pool, exclude, MemeOk); } if (value == null) { return null; } LastPicked[pool] = value.Key; _lastMeme = value.Meme; foreach (Pool item in Next.Keys.ToList()) { if (item != pool && value.Meme) { TrackDef trackDef2 = Next[item]; if (trackDef2 != null && trackDef2.Meme) { Next[item] = Pick(item, value, memeOk: false); } } } Next[pool] = Pick(pool, value, !value.Meme); SendPreloads(value); return value; } private static TrackDef Pick(Pool pool, TrackDef exclude, bool memeOk) { float num = pool switch { Pool.Alarm => PluginConfig.MemeChanceAlarm.Value, Pool.Extraction => PluginConfig.MemeChanceExtraction.Value, _ => PluginConfig.MemeChanceCombat.Value, }; bool meme = memeOk && Rng.NextDouble() < (double)num; List<TrackDef> all = (from t in PlaylistStore.PoolTracks(pool) where t.Meme == meme select t).ToList(); if (all.Count == 0) { all = (from t in PlaylistStore.PoolTracks(pool) where !t.Meme select t).ToList(); } if (all.Count == 0) { all = PlaylistStore.PoolTracks(pool); } uint value; List<TrackDef> list = all.Where((TrackDef t) => t != exclude && (!LastPicked.TryGetValue(pool, out value) || t.Key != value || all.Count <= 2)).ToList(); if (list.Count == 0) { list = all; } if (list.Count != 0) { return list[Rng.Next(list.Count)]; } return null; } private static void PickNextAll() { foreach (Pool value2 in Enum.GetValues(typeof(Pool))) { if (!Next.TryGetValue(value2, out var value) || !PoolHas(value2, value) || (value.Meme && !MemeOk)) { Next[value2] = Pick(value2, null, MemeOk); } } SendPreloads(); } public static IEnumerable<TrackDef> PreloadSet(TrackDef extra = null) { return (from t in new TrackDef[2] { extra, _track }.Concat(Next.Values) where t != null select t).Distinct(); } private static void SendPreloads(TrackDef extra = null) { foreach (TrackDef item in PreloadSet(extra)) { ClipCache.Request(item); MusicNet.SendPreload(item.Key); } } private static void Broadcast(Phase phase, double at, StateFlags flags = StateFlags.None) { if (phase == Phase.Combat && _state.Phase != Phase.Combat) { _combatSince = SyncClock.Now; } _state.Seq = ++_seq; _state.Phase = phase; _state.PhaseAt = at; _state.Track = ((phase != Phase.Idle) ? (_track?.Key ?? 0) : 0u); _state.PlaylistHash = PlaylistStore.Hash; _state.Flags = (StateFlags)((uint)flags | (uint)(_pendingReset ? 1 : 0) | (uint)(Engagement.Hot ? 4 : 0)); _pendingReset = false; if (phase == Phase.Idle) { _state.LoopAnchor = (_state.SuspenseAnchor = (_state.StingerAt = (_state.LeadInAt = 0.0))); } Plugin.Verbose($"-> {phase} track={_track?.Id} pool={_state.Pool} at+{at - SyncClock.Now:F2}s awake={Engagement.AwakeCount} alarm={Engagement.AlarmActive}"); StatePacket statePacket = SyncClock.ToWire(_state); MusicNet.SendState(statePacket); MusicPlayer.Apply(statePacket); } } } namespace GTFOCustomMusic.Audio { public sealed class AudioFile { public string Name { get; private set; } public byte[] Bytes { get; private set; } public bool IsOgg { get; private set; } public int Channels { get; private set; } public int Rate { get; private set; } public long Frames { get; private set; } public double Seconds => (double)Frames / (double)Rate; public AudioFile Source { get; private set; } public float CutoffHz { get; private set; } public static AudioFile Muffled(AudioFile src, float cutoffHz) { return new AudioFile { Name = src.Name + " (muffled)", Bytes = src.Bytes, IsOgg = src.IsOgg, Channels = src.Channels, Rate = src.Rate, Frames = src.Frames, Source = src, CutoffHz = cutoffHz }; } public static AudioFile Read(string folder, string relPath) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown string path = Path.Combine(folder, relPath); AudioFile audioFile = new AudioFile { Name = relPath, Bytes = File.ReadAllBytes(path) }; string text = Path.GetExtension(relPath).ToLowerInvariant(); if (text == ".ogg") { audioFile.IsOgg = true; VorbisReader val = new VorbisReader((Stream)new MemoryStream(audioFile.Bytes), true); try { audioFile.Channels = val.Channels; audioFile.Rate = val.SampleRate; audioFile.Frames = val.TotalSamples; } finally { ((IDisposable)val)?.Dispose(); } } else { if (!(text == ".wav")) { throw new NotSupportedException(relPath + ": only .ogg and .wav are supported (convert with tools/convert.sh)"); } Wav.Header header = Wav.ReadHeader(audioFile.Bytes); audioFile.Channels = header.Channels; audioFile.Rate = header.Rate; audioFile.Frames = header.DataLength / header.BlockAlign; } int channels = audioFile.Channels; if ((channels < 1 || channels > 2) ? true : false) { throw new NotSupportedException($"{relPath}: {audioFile.Channels} channels"); } if (audioFile.Frames <= 0) { throw new InvalidDataException(relPath + ": empty or unknown length"); } return audioFile; } public float[] Decode() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Expected O, but got Unknown int num = checked((int)(Frames * Channels)); float[] array = new float[num]; if (IsOgg) { VorbisReader val = new VorbisReader((Stream)new MemoryStream(Bytes), true); try { int num2; for (int i = 0; i < num; i += num2) { num2 = val.ReadSamples(array, i, Math.Min(65536, num - i)); if (num2 <= 0) { break; } } } finally { ((IDisposable)val)?.Dispose(); } } else { Wav.Decode(Bytes, array); } if (Source != null) { Dsp.ZeroPhaseLowPass(array, Channels, Rate, CutoffHz); } return array; } } internal static class Wav { public struct Header { public int Format; public int Channels; public int Rate; public int BlockAlign; public int Bits; public int DataOffset; public int DataLength; } public static Header ReadHeader(byte[] b) { if (b.Length < 12 || b[0] != 82 || b[1] != 73 || b[2] != 70 || b[3] != 70 || b[8] != 87 || b[9] != 65 || b[10] != 86 || b[11] != 69) { throw new InvalidDataException("not a RIFF/WAVE file"); } Header result = default(Header); int num = 12; while (num + 8 <= b.Length) { string text = Encoding.ASCII.GetString(b, num, 4); int num2 = BitConverter.ToInt32(b, num + 4); int num3 = num + 8; if (text == "fmt ") { result.Format = BitConverter.ToUInt16(b, num3); result.Channels = BitConverter.ToUInt16(b, num3 + 2); result.Rate = BitConverter.ToInt32(b, num3 + 4); result.BlockAlign = BitConverter.ToUInt16(b, num3 + 12); result.Bits = BitConverter.ToUInt16(b, num3 + 14); if (result.Format == 65534 && num2 >= 26) { result.Format = BitConverter.ToUInt16(b, num3 + 24); } } else if (text == "data") { result.DataOffset = num3; result.DataLength = Math.Min(num2, b.Length - num3); break; } num = num3 + num2 + (num2 & 1); } if (result.DataOffset == 0 || result.BlockAlign == 0) { throw new InvalidDataException("missing fmt/data chunk"); } bool flag = result.Format == 1; if (flag) { int bits = result.Bits; bool flag2 = ((bits == 16 || bits == 24 || bits == 32) ? true : false); flag = flag2; } if (!flag && (result.Format != 3 || result.Bits != 32)) { throw new NotSupportedException($"WAV format {result.Format}/{result.Bits}-bit"); } return result; } public static void Decode(byte[] b, float[] dst) { Header header = ReadHeader(b); int num = header.Bits / 8; int num2 = header.DataOffset; int num3 = 0; while (num3 < dst.Length && num2 + num <= header.DataOffset + header.DataLength) { dst[num3] = ((header.Format == 3) ? BitConverter.ToSingle(b, num2) : ((header.Bits == 16) ? ((float)BitConverter.ToInt16(b, num2) / 32768f) : ((header.Bits == 24) ? ((float)((b[num2] | (b[num2 + 1] << 8) | (b[num2 + 2] << 16)) << 8 >> 8) / 8388608f) : ((float)BitConverter.ToInt32(b, num2) / 2.1474836E+09f)))); num3++; num2 += num; } } } internal static class Dsp { public static void ZeroPhaseLowPass(float[] pcm, int channels, int rate, float cutoffHz) { int num = pcm.Length / channels; if (num == 0) { return; } double num2 = Math.PI * 2.0 * (double)Math.Clamp(cutoffHz, 20f, (float)rate * 0.45f) / (double)rate; double num3 = Math.Cos(num2); double num4 = Math.Sin(num2) / (2.0 * Math.Sqrt(0.5)); double num5 = 1.0 + num4; double b0 = (1.0 - num3) / 2.0 / num5; double b1 = (1.0 - num3) / num5; double a1 = -2.0 * num3 / num5; double a2 = (1.0 - num4) / num5; int num6 = Math.Min(num, rate); int c; for (c = 0; c < channels; c++) { double x1 = 0.0; double x2 = 0.0; double y1 = 0.0; double y2 = 0.0; for (int i = num - num6; i < num; i++) { Step(i, write: false); } for (int j = 0; j < num; j++) { Step(j, write: true); } x1 = (x2 = (y1 = (y2 = 0.0))); for (int num7 = num6 - 1; num7 >= 0; num7--) { Step(num7, write: false); } for (int num8 = num - 1; num8 >= 0; num8--) { Step(num8, write: true); } void Step(int frame, bool write) { int num9 = frame * channels + c; double num10 = pcm[num9]; double num11 = b0 * num10 + b1 * x1 + b0 * x2 - a1 * y1 - a2 * y2; x2 = x1; x1 = num10; y2 = y1; y1 = num11; if (write) { pcm[num9] = (float)num11; } } } } } internal static class BootSound { private static AudioSource _src; public static bool Playing { get { if ((Object)(object)_src != (Object)null) { return _src.isPlaying; } return false; } } public static void OnPlaylistLoaded() { if (PlaylistStore.Boot != null) { ClipCache.Request(PlaylistStore.Boot); } } public static bool OnEvent(string name) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Expected O, but got Unknown //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Expected O, but got Unknown AudioFile boot = PlaylistStore.Boot; if (boot == null || name == null || !string.Equals(name, PluginConfig.BootSoundEvent.Value, StringComparison.OrdinalIgnoreCase)) { return false; } if (Playing) { return PluginConfig.BootSoundReplaces.Value; } AudioClip val = ClipCache.Get(boot); bool flag = default(bool); if ((Object)(object)val == (Object)null) { ManualLogSource l = Plugin.L; BepInExWarningLogInterpolatedStringHandler val2 = new BepInExWarningLogInterpolatedStringHandler(62, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" posted before the boot sound "); ((BepInExLogInterpolatedStringHandler)val2).AppendFormatted<string>(boot.Name); ((BepInExLogInterpolatedStringHandler)val2).AppendLiteral(" was ready, GTFO's plays instead"); } l.LogWarning(val2); return false; } if ((Object)(object)_src == (Object)null) { GameObject val3 = new GameObject("GTFOCustomMusicBoot") { hideFlags = (HideFlags)61 }; Object.DontDestroyOnLoad((Object)val3); _src = val3.AddComponent<AudioSource>(); _src.playOnAwake = false; _src.spatialBlend = 0f; } _src.clip = val; Tick(); _src.Play(); ManualLogSource l2 = Plugin.L; BepInExMessageLogInterpolatedStringHandler val4 = new BepInExMessageLogInterpolatedStringHandler(15, 2, ref flag); if (flag) { ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral("Boot sound "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<string>(boot.Name); ((BepInExLogInterpolatedStringHandler)val4).AppendLiteral(" on "); ((BepInExLogInterpolatedStringHandler)val4).AppendFormatted<string>(name); } l2.LogMessage(val4); return PluginConfig.BootSoundReplaces.Value; } public static void Tick() { if (!((Object)(object)_src == (Object)null)) { _src.volume = Mathf.Clamp01(PlaylistStore.BootVolume * PluginConfig.MasterVolume.Value * MusicPlayer.GameVolumeMul * MusicPlayer.FocusMul); } } } internal static class ClipCache { private sealed class Entry { public AudioFile File; public Task<float[]> Decoding; public float[] Pcm; public int Uploaded; public AudioClip Clip; public bool Ready; public float LastUsed; public volatile bool Dropped; } private const int UploadFloatsPerFrame = 262144; private const double UploadMsPerFrame = 2.0; private const int ChunkFloats = 65536; private const int MaxTracksDecoded = 7; private const float ProtectSeconds = 5f; private static readonly BlockingCollection<(Entry e, TaskCompletionSource<float[]> done)> DecodeQueue = new BlockingCollection<(Entry, TaskCompletionSource<float[]>)>(); private static Thread _decoder; private static readonly Dictionary<AudioFile, E
NVorbis.dll
Decompiled 9 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Text; using System.Threading; using NVorbis.Contracts; using NVorbis.Contracts.Ogg; using NVorbis.Ogg; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("Andrew Ward")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © Andrew Ward 2021")] [assembly: AssemblyDescription("A fully managed implementation of a Xiph.org Foundation Ogg Vorbis decoder.")] [assembly: AssemblyFileVersion("0.10.5.0")] [assembly: AssemblyInformationalVersion("0.10.5")] [assembly: AssemblyProduct("NVorbis")] [assembly: AssemblyTitle("NVorbis")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/NVorbis/NVorbis")] [assembly: NeutralResourcesLanguage("en")] [assembly: AssemblyVersion("0.10.5.0")] namespace NVorbis { internal class Codebook : ICodebook { private class FastRange : IReadOnlyList<int>, IEnumerable<int>, IEnumerable, IReadOnlyCollection<int> { [ThreadStatic] private static FastRange _cachedRange; private int _start; private int _count; public int this[int index] { get { if (index > _count) { throw new ArgumentOutOfRangeException(); } return _start + index; } } public int Count => _count; internal static FastRange Get(int start, int count) { FastRange obj = _cachedRange ?? (_cachedRange = new FastRange()); obj._start = start; obj._count = count; return obj; } private FastRange() { } public IEnumerator<int> GetEnumerator() { throw new NotSupportedException(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } private int[] _lengths; private float[] _lookupTable; private IReadOnlyList<HuffmanListNode> _overflowList; private IReadOnlyList<HuffmanListNode> _prefixList; private int _prefixBitLength; private int _maxBits; public float this[int entry, int dim] => _lookupTable[entry * Dimensions + dim]; public int Dimensions { get; private set; } public int Entries { get; private set; } public int MapType { get; private set; } public void Init(IPacket packet, IHuffman huffman) { if (packet.ReadBits(24) != 5653314) { throw new InvalidDataException("Book header had invalid signature!"); } Dimensions = (int)packet.ReadBits(16); Entries = (int)packet.ReadBits(24); _lengths = new int[Entries]; InitTree(packet, huffman); InitLookupTable(packet); } private void InitTree(IPacket packet, IHuffman huffman) { int num = 0; bool flag; int num5; if (packet.ReadBit()) { int num2 = (int)packet.ReadBits(5) + 1; int num3 = 0; while (num3 < Entries) { int num4 = (int)packet.ReadBits(Utils.ilog(Entries - num3)); while (--num4 >= 0) { _lengths[num3++] = num2; } num2++; } num = 0; flag = false; num5 = num2; } else { num5 = -1; flag = packet.ReadBit(); for (int i = 0; i < Entries; i++) { if (!flag || packet.ReadBit()) { _lengths[i] = (int)packet.ReadBits(5) + 1; num++; } else { _lengths[i] = -1; } if (_lengths[i] > num5) { num5 = _lengths[i]; } } } if ((_maxBits = num5) > -1) { int[] array = null; if (flag && num >= Entries >> 2) { array = new int[Entries]; Array.Copy(_lengths, array, Entries); flag = false; } int num6 = (flag ? num : 0); int[] array2 = null; int[] array3 = null; if (!flag) { array3 = new int[Entries]; } else if (num6 != 0) { array = new int[num6]; array3 = new int[num6]; array2 = new int[num6]; } if (!ComputeCodewords(flag, array3, array, _lengths, Entries, array2)) { throw new InvalidDataException(); } IReadOnlyList<int> readOnlyList = array2; IReadOnlyList<int> value = readOnlyList ?? FastRange.Get(0, array3.Length); huffman.GenerateTable(value, array ?? _lengths, array3); _prefixList = huffman.PrefixTree; _prefixBitLength = huffman.TableBits; _overflowList = huffman.OverflowList; } } private bool ComputeCodewords(bool sparse, int[] codewords, int[] codewordLengths, int[] len, int n, int[] values) { int num = 0; uint[] array = new uint[32]; int i; for (i = 0; i < n && len[i] <= 0; i++) { } if (i == n) { return true; } AddEntry(sparse, codewords, codewordLengths, 0u, i, num++, len[i], values); for (int j = 1; j <= len[i]; j++) { array[j] = (uint)(1 << 32 - j); } for (int j = i + 1; j < n; j++) { int num2 = len[j]; if (num2 <= 0) { continue; } while (num2 > 0 && array[num2] == 0) { num2--; } if (num2 == 0) { return false; } uint num3 = array[num2]; array[num2] = 0u; AddEntry(sparse, codewords, codewordLengths, Utils.BitReverse(num3), j, num++, len[j], values); if (num2 != len[j]) { for (int num4 = len[j]; num4 > num2; num4--) { array[num4] = num3 + (uint)(1 << 32 - num4); } } } return true; } private void AddEntry(bool sparse, int[] codewords, int[] codewordLengths, uint huffCode, int symbol, int count, int len, int[] values) { if (sparse) { codewords[count] = (int)huffCode; codewordLengths[count] = len; values[count] = symbol; } else { codewords[symbol] = (int)huffCode; } } private void InitLookupTable(IPacket packet) { MapType = (int)packet.ReadBits(4); if (MapType == 0) { return; } float num = Utils.ConvertFromVorbisFloat32((uint)packet.ReadBits(32)); float num2 = Utils.ConvertFromVorbisFloat32((uint)packet.ReadBits(32)); int count = (int)packet.ReadBits(4) + 1; bool flag = packet.ReadBit(); int num3 = Entries * Dimensions; float[] array = new float[num3]; if (MapType == 1) { num3 = lookup1_values(); } uint[] array2 = new uint[num3]; for (int i = 0; i < num3; i++) { array2[i] = (uint)packet.ReadBits(count); } if (MapType == 1) { for (int j = 0; j < Entries; j++) { double num4 = 0.0; int num5 = 1; for (int k = 0; k < Dimensions; k++) { int num6 = j / num5 % num3; double num7 = (double)((float)array2[num6] * num2 + num) + num4; array[j * Dimensions + k] = (float)num7; if (flag) { num4 = num7; } num5 *= num3; } } } else { for (int l = 0; l < Entries; l++) { double num8 = 0.0; int num9 = l * Dimensions; for (int m = 0; m < Dimensions; m++) { double num10 = (double)((float)array2[num9] * num2 + num) + num8; array[l * Dimensions + m] = (float)num10; if (flag) { num8 = num10; } num9++; } } } _lookupTable = array; } private int lookup1_values() { int num = (int)Math.Floor(Math.Exp(Math.Log(Entries) / (double)Dimensions)); if (Math.Floor(Math.Pow(num + 1, Dimensions)) <= (double)Entries) { num++; } return num; } public int DecodeScalar(IPacket packet) { int index = (int)packet.TryPeekBits(_prefixBitLength, out var bitsRead); if (bitsRead == 0) { return -1; } HuffmanListNode huffmanListNode = _prefixList[index]; if (huffmanListNode != null) { packet.SkipBits(huffmanListNode.Length); return huffmanListNode.Value; } index = (int)packet.TryPeekBits(_maxBits, out var _); for (int i = 0; i < _overflowList.Count; i++) { huffmanListNode = _overflowList[i]; if (huffmanListNode.Bits == (index & huffmanListNode.Mask)) { packet.SkipBits(huffmanListNode.Length); return huffmanListNode.Value; } } return -1; } } public abstract class DataPacket : IPacket { [Flags] protected enum PacketFlags : byte { IsResync = 1, IsEndOfStream = 2, IsShort = 4, User0 = 8, User1 = 0x10, User2 = 0x20, User3 = 0x40, User4 = 0x80 } private ulong _bitBucket; private int _bitCount; private byte _overflowBits; private PacketFlags _packetFlags; private int _readBits; public int ContainerOverheadBits { get; set; } public long? GranulePosition { get; set; } public bool IsResync { get { return GetFlag(PacketFlags.IsResync); } set { SetFlag(PacketFlags.IsResync, value); } } public bool IsShort { get { return GetFlag(PacketFlags.IsShort); } private set { SetFlag(PacketFlags.IsShort, value); } } public bool IsEndOfStream { get { return GetFlag(PacketFlags.IsEndOfStream); } set { SetFlag(PacketFlags.IsEndOfStream, value); } } public int BitsRead => _readBits; public int BitsRemaining => TotalBits - _readBits; protected abstract int TotalBits { get; } private bool GetFlag(PacketFlags flag) { return _packetFlags.HasFlag(flag); } private void SetFlag(PacketFlags flag, bool value) { if (value) { _packetFlags |= flag; } else { _packetFlags &= (PacketFlags)(byte)(~(int)flag); } } protected abstract int ReadNextByte(); public virtual void Done() { } public virtual void Reset() { _bitBucket = 0uL; _bitCount = 0; _overflowBits = 0; _readBits = 0; } ulong IPacket.ReadBits(int count) { if (count == 0) { return 0uL; } int bitsRead; ulong result = TryPeekBits(count, out bitsRead); SkipBits(count); return result; } public ulong TryPeekBits(int count, out int bitsRead) { switch (count) { default: throw new ArgumentOutOfRangeException("count"); case 0: bitsRead = 0; return 0uL; case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 19: case 20: case 21: case 22: case 23: case 24: case 25: case 26: case 27: case 28: case 29: case 30: case 31: case 32: case 33: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 46: case 47: case 48: case 49: case 50: case 51: case 52: case 53: case 54: case 55: case 56: case 57: case 58: case 59: case 60: case 61: case 62: case 63: case 64: break; } while (_bitCount < count) { int num = ReadNextByte(); if (num == -1) { bitsRead = _bitCount; return _bitBucket; } _bitBucket = (ulong)((long)(num & 0xFF) << _bitCount) | _bitBucket; _bitCount += 8; if (_bitCount > 64) { _overflowBits = (byte)(num >> 72 - _bitCount); } } ulong num2 = _bitBucket; if (count < 64) { num2 &= (ulong)((1L << count) - 1); } bitsRead = count; return num2; } public void SkipBits(int count) { if (count <= 0) { return; } if (_bitCount > count) { if (count > 63) { _bitBucket = 0uL; } else { _bitBucket >>= count; } if (_bitCount > 64) { int num = _bitCount - 64; _bitBucket |= (ulong)_overflowBits << _bitCount - count - num; if (num > count) { _overflowBits = (byte)(_overflowBits >> count); } } _bitCount -= count; _readBits += count; return; } if (_bitCount == count) { _bitBucket = 0uL; _bitCount = 0; _readBits += count; return; } count -= _bitCount; _readBits += _bitCount; _bitCount = 0; _bitBucket = 0uL; while (count > 8) { if (ReadNextByte() == -1) { count = 0; IsShort = true; break; } count -= 8; _readBits += 8; } if (count > 0) { int num2 = ReadNextByte(); if (num2 == -1) { IsShort = true; return; } _bitBucket = (ulong)(num2 >> count); _bitCount = 8 - count; _readBits += count; } } } public static class Extensions { public static int Read(this IPacket packet, byte[] buffer, int index, int count) { if (index < 0 || index >= buffer.Length) { throw new ArgumentOutOfRangeException("index"); } if (count < 0 || index + count > buffer.Length) { throw new ArgumentOutOfRangeException("count"); } for (int i = 0; i < count; i++) { int bitsRead; byte b = (byte)packet.TryPeekBits(8, out bitsRead); if (bitsRead == 0) { return i; } buffer[index++] = b; packet.SkipBits(8); } return count; } public static byte[] ReadBytes(this IPacket packet, int count) { byte[] array = new byte[count]; int num = packet.Read(array, 0, count); if (num < count) { byte[] array2 = new byte[num]; Buffer.BlockCopy(array, 0, array2, 0, num); return array2; } return array; } public static bool ReadBit(this IPacket packet) { return packet.ReadBits(1) == 1; } public static byte PeekByte(this IPacket packet) { int bitsRead; return (byte)packet.TryPeekBits(8, out bitsRead); } public static byte ReadByte(this IPacket packet) { return (byte)packet.ReadBits(8); } public static short ReadInt16(this IPacket packet) { return (short)packet.ReadBits(16); } public static int ReadInt32(this IPacket packet) { return (int)packet.ReadBits(32); } public static long ReadInt64(this IPacket packet) { return (long)packet.ReadBits(64); } public static ushort ReadUInt16(this IPacket packet) { return (ushort)packet.ReadBits(16); } public static uint ReadUInt32(this IPacket packet) { return (uint)packet.ReadBits(32); } public static ulong ReadUInt64(this IPacket packet) { return packet.ReadBits(64); } public static void SkipBytes(this IPacket packet, int count) { packet.SkipBits(count * 8); } } internal class Factory : IFactory { public IHuffman CreateHuffman() { return new Huffman(); } public IMdct CreateMdct() { return new Mdct(); } public ICodebook CreateCodebook() { return new Codebook(); } public IFloor CreateFloor(IPacket packet) { return (int)packet.ReadBits(16) switch { 0 => new Floor0(), 1 => new Floor1(), _ => throw new InvalidDataException("Invalid floor type!"), }; } public IMapping CreateMapping(IPacket packet) { if (packet.ReadBits(16) != 0L) { throw new InvalidDataException("Invalid mapping type!"); } return new Mapping(); } public IMode CreateMode() { return new Mode(); } public IResidue CreateResidue(IPacket packet) { return (int)packet.ReadBits(16) switch { 0 => new Residue0(), 1 => new Residue1(), 2 => new Residue2(), _ => throw new InvalidDataException("Invalid residue type!"), }; } } internal class Floor0 : IFloor { private class Data : IFloorData { internal float[] Coeff; internal float Amp; public bool ExecuteChannel { get { if (ForceEnergy || Amp > 0f) { return !ForceNoEnergy; } return false; } } public bool ForceEnergy { get; set; } public bool ForceNoEnergy { get; set; } } private int _order; private int _rate; private int _bark_map_size; private int _ampBits; private int _ampOfs; private int _ampDiv; private ICodebook[] _books; private int _bookBits; private Dictionary<int, float[]> _wMap; private Dictionary<int, int[]> _barkMaps; public void Init(IPacket packet, int channels, int block0Size, int block1Size, ICodebook[] codebooks) { _order = (int)packet.ReadBits(8); _rate = (int)packet.ReadBits(16); _bark_map_size = (int)packet.ReadBits(16); _ampBits = (int)packet.ReadBits(6); _ampOfs = (int)packet.ReadBits(8); _books = new ICodebook[(int)packet.ReadBits(4) + 1]; if (_order < 1 || _rate < 1 || _bark_map_size < 1 || _books.Length == 0) { throw new InvalidDataException(); } _ampDiv = (1 << _ampBits) - 1; for (int i = 0; i < _books.Length; i++) { int num = (int)packet.ReadBits(8); if (num < 0 || num >= codebooks.Length) { throw new InvalidDataException(); } ICodebook codebook = codebooks[num]; if (codebook.MapType == 0 || codebook.Dimensions < 1) { throw new InvalidDataException(); } _books[i] = codebook; } _bookBits = Utils.ilog(_books.Length); _barkMaps = new Dictionary<int, int[]> { [block0Size] = SynthesizeBarkCurve(block0Size / 2), [block1Size] = SynthesizeBarkCurve(block1Size / 2) }; _wMap = new Dictionary<int, float[]> { [block0Size] = SynthesizeWDelMap(block0Size / 2), [block1Size] = SynthesizeWDelMap(block1Size / 2) }; } private int[] SynthesizeBarkCurve(int n) { float num = (float)_bark_map_size / toBARK(_rate / 2); int[] array = new int[n + 1]; for (int i = 0; i < n - 1; i++) { array[i] = Math.Min(_bark_map_size - 1, (int)Math.Floor(toBARK((float)_rate / 2f / (float)n * (float)i) * num)); } array[n] = -1; return array; } private static float toBARK(double lsp) { return (float)(13.1 * Math.Atan(0.00074 * lsp) + 2.24 * Math.Atan(1.85E-08 * lsp * lsp) + 0.0001 * lsp); } private float[] SynthesizeWDelMap(int n) { float num = (float)(Math.PI / (double)_bark_map_size); float[] array = new float[n]; for (int i = 0; i < n; i++) { array[i] = 2f * (float)Math.Cos(num * (float)i); } return array; } public IFloorData Unpack(IPacket packet, int blockSize, int channel) { Data data = new Data { Coeff = new float[_order + 1] }; data.Amp = packet.ReadBits(_ampBits); if (data.Amp > 0f) { Array.Clear(data.Coeff, 0, data.Coeff.Length); data.Amp = data.Amp / (float)_ampDiv * (float)_ampOfs; uint num = (uint)packet.ReadBits(_bookBits); if (num >= _books.Length) { data.Amp = 0f; return data; } ICodebook codebook = _books[num]; int i = 0; while (i < _order) { int num2 = codebook.DecodeScalar(packet); if (num2 == -1) { data.Amp = 0f; return data; } int num3 = 0; for (; i < _order; i++) { if (num3 >= codebook.Dimensions) { break; } data.Coeff[i] = codebook[num2, num3]; num3++; } } float num4 = 0f; int num5 = 0; while (num5 < _order) { int num6 = 0; while (num5 < _order && num6 < codebook.Dimensions) { data.Coeff[num5] += num4; num5++; num6++; } num4 = data.Coeff[num5 - 1]; } } return data; } public void Apply(IFloorData floorData, int blockSize, float[] residue) { if (!(floorData is Data data)) { throw new ArgumentException("Incorrect packet data!"); } int num = blockSize / 2; if (data.Amp > 0f) { int[] array = _barkMaps[blockSize]; float[] array2 = _wMap[blockSize]; int num2 = 0; for (num2 = 0; num2 < _order; num2++) { data.Coeff[num2] = 2f * (float)Math.Cos(data.Coeff[num2]); } num2 = 0; while (num2 < num) { int num3 = array[num2]; float num4 = 0.5f; float num5 = 0.5f; float num6 = array2[num3]; int i; for (i = 1; i < _order; i += 2) { num5 *= num6 - data.Coeff[i - 1]; num4 *= num6 - data.Coeff[i]; } if (i == _order) { num5 *= num6 - data.Coeff[i - 1]; num4 *= num4 * (4f - num6 * num6); num5 *= num5; } else { num4 *= num4 * (2f - num6); num5 *= num5 * (2f + num6); } num5 = data.Amp / (float)Math.Sqrt(num4 + num5) - (float)_ampOfs; num5 = (float)Math.Exp(num5 * 0.11512925f); residue[num2] *= num5; while (array[++num2] == num3) { residue[num2] *= num5; } } } else { Array.Clear(residue, 0, num); } } } internal class Floor1 : IFloor { private class Data : IFloorData { internal int[] Posts = new int[64]; internal int PostCount; public bool ExecuteChannel { get { if (ForceEnergy || PostCount > 0) { return !ForceNoEnergy; } return false; } } public bool ForceEnergy { get; set; } public bool ForceNoEnergy { get; set; } } private int[] _partitionClass; private int[] _classDimensions; private int[] _classSubclasses; private int[] _xList; private int[] _classMasterBookIndex; private int[] _hNeigh; private int[] _lNeigh; private int[] _sortIdx; private int _multiplier; private int _range; private int _yBits; private ICodebook[] _classMasterbooks; private ICodebook[][] _subclassBooks; private int[][] _subclassBookIndex; private static readonly int[] _rangeLookup = new int[4] { 256, 128, 86, 64 }; private static readonly int[] _yBitsLookup = new int[4] { 8, 7, 7, 6 }; private static readonly float[] inverse_dB_table = new float[256] { 1.0649863E-07f, 1.1341951E-07f, 1.2079015E-07f, 1.2863978E-07f, 1.369995E-07f, 1.459025E-07f, 1.5538409E-07f, 1.6548181E-07f, 1.7623574E-07f, 1.8768856E-07f, 1.998856E-07f, 2.128753E-07f, 2.2670913E-07f, 2.4144197E-07f, 2.5713223E-07f, 2.7384212E-07f, 2.9163792E-07f, 3.1059022E-07f, 3.307741E-07f, 3.5226967E-07f, 3.7516213E-07f, 3.995423E-07f, 4.255068E-07f, 4.5315863E-07f, 4.8260745E-07f, 5.1397E-07f, 5.4737063E-07f, 5.829419E-07f, 6.208247E-07f, 6.611694E-07f, 7.041359E-07f, 7.4989464E-07f, 7.98627E-07f, 8.505263E-07f, 9.057983E-07f, 9.646621E-07f, 1.0273513E-06f, 1.0941144E-06f, 1.1652161E-06f, 1.2409384E-06f, 1.3215816E-06f, 1.4074654E-06f, 1.4989305E-06f, 1.5963394E-06f, 1.7000785E-06f, 1.8105592E-06f, 1.9282195E-06f, 2.053526E-06f, 2.1869757E-06f, 2.3290977E-06f, 2.4804558E-06f, 2.6416496E-06f, 2.813319E-06f, 2.9961443E-06f, 3.1908505E-06f, 3.39821E-06f, 3.619045E-06f, 3.8542307E-06f, 4.1047006E-06f, 4.371447E-06f, 4.6555283E-06f, 4.958071E-06f, 5.280274E-06f, 5.623416E-06f, 5.988857E-06f, 6.3780467E-06f, 6.7925284E-06f, 7.2339453E-06f, 7.704048E-06f, 8.2047E-06f, 8.737888E-06f, 9.305725E-06f, 9.910464E-06f, 1.0554501E-05f, 1.1240392E-05f, 1.1970856E-05f, 1.2748789E-05f, 1.3577278E-05f, 1.4459606E-05f, 1.5399271E-05f, 1.6400005E-05f, 1.7465769E-05f, 1.8600793E-05f, 1.9809577E-05f, 2.1096914E-05f, 2.2467912E-05f, 2.3928002E-05f, 2.5482977E-05f, 2.7139005E-05f, 2.890265E-05f, 3.078091E-05f, 3.2781227E-05f, 3.4911533E-05f, 3.718028E-05f, 3.9596467E-05f, 4.2169668E-05f, 4.491009E-05f, 4.7828602E-05f, 5.0936775E-05f, 5.424693E-05f, 5.7772202E-05f, 6.152657E-05f, 6.552491E-05f, 6.9783084E-05f, 7.4317984E-05f, 7.914758E-05f, 8.429104E-05f, 8.976875E-05f, 9.560242E-05f, 0.00010181521f, 0.00010843174f, 0.00011547824f, 0.00012298267f, 0.00013097477f, 0.00013948625f, 0.00014855085f, 0.00015820454f, 0.00016848555f, 0.00017943469f, 0.00019109536f, 0.00020351382f, 0.0002167393f, 0.00023082423f, 0.00024582449f, 0.00026179955f, 0.00027881275f, 0.00029693157f, 0.00031622787f, 0.00033677815f, 0.00035866388f, 0.00038197188f, 0.00040679457f, 0.00043323037f, 0.0004613841f, 0.0004913675f, 0.00052329927f, 0.0005573062f, 0.0005935231f, 0.0006320936f, 0.0006731706f, 0.000716917f, 0.0007635063f, 0.00081312325f, 0.00086596457f, 0.00092223985f, 0.0009821722f, 0.0010459992f, 0.0011139743f, 0.0011863665f, 0.0012634633f, 0.0013455702f, 0.0014330129f, 0.0015261382f, 0.0016253153f, 0.0017309374f, 0.0018434235f, 0.0019632196f, 0.0020908006f, 0.0022266726f, 0.0023713743f, 0.0025254795f, 0.0026895993f, 0.0028643848f, 0.0030505287f, 0.003248769f, 0.0034598925f, 0.0036847359f, 0.0039241905f, 0.0041792067f, 0.004450795f, 0.004740033f, 0.005048067f, 0.0053761187f, 0.005725489f, 0.0060975635f, 0.0064938175f, 0.0069158226f, 0.0073652514f, 0.007843887f, 0.008353627f, 0.008896492f, 0.009474637f, 0.010090352f, 0.01074608f, 0.011444421f, 0.012188144f, 0.012980198f, 0.013823725f, 0.014722068f, 0.015678791f, 0.016697686f, 0.017782796f, 0.018938422f, 0.020169148f, 0.021479854f, 0.022875736f, 0.02436233f, 0.025945531f, 0.027631618f, 0.029427277f, 0.031339627f, 0.03337625f, 0.035545226f, 0.037855156f, 0.0403152f, 0.042935107f, 0.045725275f, 0.048696756f, 0.05186135f, 0.05523159f, 0.05882085f, 0.062643364f, 0.06671428f, 0.07104975f, 0.075666964f, 0.08058423f, 0.08582105f, 0.09139818f, 0.097337745f, 0.1036633f, 0.11039993f, 0.11757434f, 0.12521498f, 0.13335215f, 0.14201812f, 0.15124726f, 0.16107617f, 0.1715438f, 0.18269168f, 0.19456401f, 0.20720787f, 0.22067343f, 0.23501402f, 0.25028655f, 0.26655158f, 0.28387362f, 0.3023213f, 0.32196787f, 0.34289113f, 0.36517414f, 0.3889052f, 0.41417846f, 0.44109413f, 0.4697589f, 0.50028646f, 0.53279793f, 0.5674221f, 0.6042964f, 0.64356697f, 0.6853896f, 0.72993004f, 0.777365f, 0.8278826f, 0.88168305f, 0.9389798f, 1f }; public void Init(IPacket packet, int channels, int block0Size, int block1Size, ICodebook[] codebooks) { int num = -1; _partitionClass = new int[(uint)packet.ReadBits(5)]; for (int i = 0; i < _partitionClass.Length; i++) { _partitionClass[i] = (int)packet.ReadBits(4); if (_partitionClass[i] > num) { num = _partitionClass[i]; } } _classDimensions = new int[++num]; _classSubclasses = new int[num]; _classMasterbooks = new ICodebook[num]; _classMasterBookIndex = new int[num]; _subclassBooks = new ICodebook[num][]; _subclassBookIndex = new int[num][]; for (int j = 0; j < num; j++) { _classDimensions[j] = (int)packet.ReadBits(3) + 1; _classSubclasses[j] = (int)packet.ReadBits(2); if (_classSubclasses[j] > 0) { _classMasterBookIndex[j] = (int)packet.ReadBits(8); _classMasterbooks[j] = codebooks[_classMasterBookIndex[j]]; } _subclassBooks[j] = new ICodebook[1 << _classSubclasses[j]]; _subclassBookIndex[j] = new int[_subclassBooks[j].Length]; for (int k = 0; k < _subclassBooks[j].Length; k++) { int num2 = (int)packet.ReadBits(8) - 1; if (num2 >= 0) { _subclassBooks[j][k] = codebooks[num2]; } _subclassBookIndex[j][k] = num2; } } _multiplier = (int)packet.ReadBits(2); _range = _rangeLookup[_multiplier]; _yBits = _yBitsLookup[_multiplier]; _multiplier++; int num3 = (int)packet.ReadBits(4); List<int> list = new List<int>(); list.Add(0); list.Add(1 << num3); for (int l = 0; l < _partitionClass.Length; l++) { int num4 = _partitionClass[l]; for (int m = 0; m < _classDimensions[num4]; m++) { list.Add((int)packet.ReadBits(num3)); } } _xList = list.ToArray(); _lNeigh = new int[list.Count]; _hNeigh = new int[list.Count]; _sortIdx = new int[list.Count]; _sortIdx[0] = 0; _sortIdx[1] = 1; for (int n = 2; n < _lNeigh.Length; n++) { _lNeigh[n] = 0; _hNeigh[n] = 1; _sortIdx[n] = n; for (int num5 = 2; num5 < n; num5++) { int num6 = _xList[num5]; if (num6 < _xList[n]) { if (num6 > _xList[_lNeigh[n]]) { _lNeigh[n] = num5; } } else if (num6 < _xList[_hNeigh[n]]) { _hNeigh[n] = num5; } } } for (int num7 = 0; num7 < _sortIdx.Length - 1; num7++) { for (int num8 = num7 + 1; num8 < _sortIdx.Length; num8++) { if (_xList[num7] == _xList[num8]) { throw new InvalidDataException(); } if (_xList[_sortIdx[num7]] > _xList[_sortIdx[num8]]) { int num9 = _sortIdx[num7]; _sortIdx[num7] = _sortIdx[num8]; _sortIdx[num8] = num9; } } } } public IFloorData Unpack(IPacket packet, int blockSize, int channel) { Data data = new Data(); if (packet.ReadBit()) { int num = 2; data.Posts[0] = (int)packet.ReadBits(_yBits); data.Posts[1] = (int)packet.ReadBits(_yBits); for (int i = 0; i < _partitionClass.Length; i++) { int num2 = _partitionClass[i]; int num3 = _classDimensions[num2]; int num4 = _classSubclasses[num2]; int num5 = (1 << num4) - 1; uint num6 = 0u; if (num4 > 0 && (num6 = (uint)_classMasterbooks[num2].DecodeScalar(packet)) == uint.MaxValue) { num = 0; break; } for (int j = 0; j < num3; j++) { ICodebook codebook = _subclassBooks[num2][num6 & num5]; num6 >>= num4; if (codebook != null && (data.Posts[num] = codebook.DecodeScalar(packet)) == -1) { num = 0; i = _partitionClass.Length; break; } num++; } } data.PostCount = num; } return data; } public void Apply(IFloorData floorData, int blockSize, float[] residue) { if (!(floorData is Data data)) { throw new ArgumentException("Incorrect packet data!", "packetData"); } int num = blockSize / 2; if (data.PostCount > 0) { bool[] array = UnwrapPosts(data); int num2 = 0; int num3 = data.Posts[0] * _multiplier; for (int i = 1; i < data.PostCount; i++) { int num4 = _sortIdx[i]; if (array[num4]) { int num5 = _xList[num4]; int num6 = data.Posts[num4] * _multiplier; if (num2 < num) { RenderLineMulti(num2, num3, Math.Min(num5, num), num6, residue); } num2 = num5; num3 = num6; } if (num2 >= num) { break; } } if (num2 < num) { RenderLineMulti(num2, num3, num, num3, residue); } } else { Array.Clear(residue, 0, num); } } private bool[] UnwrapPosts(Data data) { bool[] array = new bool[64]; array[0] = true; array[1] = true; int[] array2 = new int[64]; array2[0] = data.Posts[0]; array2[1] = data.Posts[1]; for (int i = 2; i < data.PostCount; i++) { int num = _lNeigh[i]; int num2 = _hNeigh[i]; int num3 = RenderPoint(_xList[num], array2[num], _xList[num2], array2[num2], _xList[i]); int num4 = data.Posts[i]; int num5 = _range - num3; int num6 = num3; int num7 = ((num5 >= num6) ? (num6 * 2) : (num5 * 2)); if (num4 != 0) { array[num] = true; array[num2] = true; array[i] = true; if (num4 >= num7) { if (num5 > num6) { array2[i] = num4 - num6 + num3; } else { array2[i] = num3 - num4 + num5 - 1; } } else if (num4 % 2 == 1) { array2[i] = num3 - (num4 + 1) / 2; } else { array2[i] = num3 + num4 / 2; } } else { array[i] = false; array2[i] = num3; } } for (int j = 0; j < data.PostCount; j++) { data.Posts[j] = array2[j]; } return array; } private int RenderPoint(int x0, int y0, int x1, int y1, int X) { int num = y1 - y0; int num2 = x1 - x0; int num3 = Math.Abs(num) * (X - x0) / num2; if (num < 0) { return y0 - num3; } return y0 + num3; } private void RenderLineMulti(int x0, int y0, int x1, int y1, float[] v) { int num = y1 - y0; int num2 = x1 - x0; int num3 = Math.Abs(num); int num4 = 1 - ((num >> 31) & 1) * 2; int num5 = num / num2; int num6 = x0; int num7 = y0; int num8 = -num2; v[x0] *= inverse_dB_table[y0]; num3 -= Math.Abs(num5) * num2; while (++num6 < x1) { num7 += num5; num8 += num3; if (num8 >= 0) { num8 -= num2; num7 += num4; } v[num6] *= inverse_dB_table[num7]; } } } internal class Huffman : IHuffman, IComparer<HuffmanListNode> { private const int MAX_TABLE_BITS = 10; public int TableBits { get; private set; } public IReadOnlyList<HuffmanListNode> PrefixTree { get; private set; } public IReadOnlyList<HuffmanListNode> OverflowList { get; private set; } public void GenerateTable(IReadOnlyList<int> values, int[] lengthList, int[] codeList) { HuffmanListNode[] array = new HuffmanListNode[lengthList.Length]; int num = 0; for (int i = 0; i < array.Length; i++) { array[i] = new HuffmanListNode { Value = values[i], Length = ((lengthList[i] <= 0) ? 99999 : lengthList[i]), Bits = codeList[i], Mask = (1 << lengthList[i]) - 1 }; if (lengthList[i] > 0 && num < lengthList[i]) { num = lengthList[i]; } } Array.Sort(array, 0, array.Length, this); int num2 = ((num > 10) ? 10 : num); List<HuffmanListNode> list = new List<HuffmanListNode>(1 << num2); List<HuffmanListNode> list2 = null; for (int j = 0; j < array.Length && array[j].Length < 99999; j++) { int length = array[j].Length; if (length > num2) { list2 = new List<HuffmanListNode>(array.Length - j); for (; j < array.Length && array[j].Length < 99999; j++) { list2.Add(array[j]); } continue; } int num3 = 1 << num2 - length; HuffmanListNode huffmanListNode = array[j]; for (int k = 0; k < num3; k++) { int num4 = (k << length) | huffmanListNode.Bits; while (list.Count <= num4) { list.Add(null); } list[num4] = huffmanListNode; } } while (list.Count < 1 << num2) { list.Add(null); } TableBits = num2; PrefixTree = list; OverflowList = list2; } int IComparer<HuffmanListNode>.Compare(HuffmanListNode x, HuffmanListNode y) { int num = x.Length - y.Length; if (num == 0) { return x.Bits - y.Bits; } return num; } } [Obsolete("Moved to NVorbis.Contracts.IContainerReader", true)] public interface IContainerReader : NVorbis.Contracts.IContainerReader, IDisposable { [Obsolete("Use Streams.Select(s => s.StreamSerial).ToArray() instead.", true)] int[] StreamSerials { get; } [Obsolete("No longer supported.", true)] int PagesRead { get; } [Obsolete("Moved to NewStreamCallback.", true)] event EventHandler<NewStreamEventArgs> NewStream; [Obsolete("Renamed to TryInit().", true)] bool Init(); [Obsolete("No longer supported.", true)] int GetTotalPageCount(); } [Obsolete("Moved to NVorbis.Contracts.IPacketProvider", true)] public interface IPacketProvider : NVorbis.Contracts.IPacketProvider { [Obsolete("Moved to per-stream IStreamStats instance on IStreamDecoder.Stats or VorbisReader.Stats.", true)] long ContainerBits { get; } [Obsolete("No longer supported.", true)] event EventHandler ParameterChange; [Obsolete("No longer supported.", true)] int GetTotalPageCount(); [Obsolete("Getting a packet by index is no longer supported.", true)] DataPacket GetPacket(int packetIndex); [Obsolete("Moved to long SeekTo(long, int, GetPacketGranuleCount)", true)] DataPacket FindPacket(long granulePos, Func<DataPacket, DataPacket, int> packetGranuleCountCallback); [Obsolete("Seeking to a specified packet is no longer supported. See SeekTo(...) instead.", true)] void SeekToPacket(DataPacket packet, int preRoll); } [Obsolete("Moved to NVorbis.Contracts.IStreamStats", true)] public interface IVorbisStreamStatus : IStreamStats { [Obsolete("No longer supported.", true)] TimeSpan PageLatency { get; } [Obsolete("No longer supported.", true)] TimeSpan PacketLatency { get; } [Obsolete("No longer supported.", true)] TimeSpan SecondLatency { get; } [Obsolete("No longer supported.", true)] int PagesRead { get; } [Obsolete("No longer supported.", true)] int TotalPages { get; } [Obsolete("Use IStreamDecoder.HasClipped instead. VorbisReader.HasClipped will return the same value for the stream it is handling.", true)] bool Clipped { get; } } internal class Mapping : IMapping { private IMdct _mdct; private int[] _couplingAngle; private int[] _couplingMangitude; private IFloor[] _submapFloor; private IResidue[] _submapResidue; private IFloor[] _channelFloor; private IResidue[] _channelResidue; public void Init(IPacket packet, int channels, IFloor[] floors, IResidue[] residues, IMdct mdct) { int num = 1; if (packet.ReadBit()) { num += (int)packet.ReadBits(4); } int num2 = 0; if (packet.ReadBit()) { num2 = (int)packet.ReadBits(8) + 1; } int count = Utils.ilog(channels - 1); _couplingAngle = new int[num2]; _couplingMangitude = new int[num2]; for (int i = 0; i < num2; i++) { int num3 = (int)packet.ReadBits(count); int num4 = (int)packet.ReadBits(count); if (num3 == num4 || num3 > channels - 1 || num4 > channels - 1) { throw new InvalidDataException("Invalid magnitude or angle in mapping header!"); } _couplingAngle[i] = num4; _couplingMangitude[i] = num3; } if (packet.ReadBits(2) != 0L) { throw new InvalidDataException("Reserved bits not 0 in mapping header."); } int[] array = new int[channels]; if (num > 1) { for (int j = 0; j < channels; j++) { array[j] = (int)packet.ReadBits(4); if (array[j] > num) { throw new InvalidDataException("Invalid channel mux submap index in mapping header!"); } } } _submapFloor = new IFloor[num]; _submapResidue = new IResidue[num]; for (int k = 0; k < num; k++) { packet.SkipBits(8); int num5 = (int)packet.ReadBits(8); if (num5 >= floors.Length) { throw new InvalidDataException("Invalid floor number in mapping header!"); } int num6 = (int)packet.ReadBits(8); if (num6 >= residues.Length) { throw new InvalidDataException("Invalid residue number in mapping header!"); } _submapFloor[k] = floors[num5]; _submapResidue[k] = residues[num6]; } _channelFloor = new IFloor[channels]; _channelResidue = new IResidue[channels]; for (int l = 0; l < channels; l++) { _channelFloor[l] = _submapFloor[array[l]]; _channelResidue[l] = _submapResidue[array[l]]; } _mdct = mdct; } public void DecodePacket(IPacket packet, int blockSize, int channels, float[][] buffer) { int num = blockSize >> 1; IFloorData[] array = new IFloorData[_channelFloor.Length]; bool[] array2 = new bool[_channelFloor.Length]; for (int i = 0; i < _channelFloor.Length; i++) { array[i] = _channelFloor[i].Unpack(packet, blockSize, i); array2[i] = !array[i].ExecuteChannel; Array.Clear(buffer[i], 0, num); } for (int j = 0; j < _couplingAngle.Length; j++) { if (array[_couplingAngle[j]].ExecuteChannel || array[_couplingMangitude[j]].ExecuteChannel) { array[_couplingAngle[j]].ForceEnergy = true; array[_couplingMangitude[j]].ForceEnergy = true; } } for (int k = 0; k < _submapFloor.Length; k++) { for (int l = 0; l < _channelFloor.Length; l++) { if (_submapFloor[k] != _channelFloor[l] || _submapResidue[k] != _channelResidue[l]) { array[l].ForceNoEnergy = true; } } _submapResidue[k].Decode(packet, array2, blockSize, buffer); } for (int num2 = _couplingAngle.Length - 1; num2 >= 0; num2--) { if (array[_couplingAngle[num2]].ExecuteChannel || array[_couplingMangitude[num2]].ExecuteChannel) { float[] array3 = buffer[_couplingMangitude[num2]]; float[] array4 = buffer[_couplingAngle[num2]]; for (int m = 0; m < num; m++) { float num3 = array3[m]; float num4 = array4[m]; float num5; float num6; if (num3 > 0f) { if (num4 > 0f) { num5 = num3; num6 = num3 - num4; } else { num6 = num3; num5 = num3 + num4; } } else if (num4 > 0f) { num5 = num3; num6 = num3 + num4; } else { num6 = num3; num5 = num3 - num4; } array3[m] = num5; array4[m] = num6; } } } for (int n = 0; n < _channelFloor.Length; n++) { if (array[n].ExecuteChannel) { _channelFloor[n].Apply(array[n], blockSize, buffer[n]); _mdct.Reverse(buffer[n], blockSize); } else { Array.Clear(buffer[n], num, num); } } } } internal class Mdct : IMdct { private class MdctImpl { private readonly int _n; private readonly int _n2; private readonly int _n4; private readonly int _n8; private readonly int _ld; private readonly float[] _a; private readonly float[] _b; private readonly float[] _c; private readonly ushort[] _bitrev; public MdctImpl(int n) { _n = n; _n2 = n >> 1; _n4 = _n2 >> 1; _n8 = _n4 >> 1; _ld = Utils.ilog(n) - 1; _a = new float[_n2]; _b = new float[_n2]; _c = new float[_n4]; int num; int num2 = (num = 0); while (num2 < _n4) { _a[num] = (float)Math.Cos((float)(4 * num2) * MathF.PI / (float)n); _a[num + 1] = (float)(0.0 - Math.Sin((float)(4 * num2) * MathF.PI / (float)n)); _b[num] = (float)Math.Cos((float)(num + 1) * MathF.PI / (float)n / 2f) * 0.5f; _b[num + 1] = (float)Math.Sin((float)(num + 1) * MathF.PI / (float)n / 2f) * 0.5f; num2++; num += 2; } num2 = (num = 0); while (num2 < _n8) { _c[num] = (float)Math.Cos((float)(2 * (num + 1)) * MathF.PI / (float)n); _c[num + 1] = (float)(0.0 - Math.Sin((float)(2 * (num + 1)) * MathF.PI / (float)n)); num2++; num += 2; } _bitrev = new ushort[_n8]; for (int i = 0; i < _n8; i++) { _bitrev[i] = (ushort)(Utils.BitReverse((uint)i, _ld - 3) << 2); } } internal void CalcReverse(float[] buffer) { float[] array = new float[_n2]; int num = _n2 - 2; int num2 = 0; int i = 0; for (int n = _n2; i != n; i += 4) { array[num + 1] = buffer[i] * _a[num2] - buffer[i + 2] * _a[num2 + 1]; array[num] = buffer[i] * _a[num2 + 1] + buffer[i + 2] * _a[num2]; num -= 2; num2 += 2; } i = _n2 - 3; while (num >= 0) { array[num + 1] = (0f - buffer[i + 2]) * _a[num2] - (0f - buffer[i]) * _a[num2 + 1]; array[num] = (0f - buffer[i + 2]) * _a[num2 + 1] + (0f - buffer[i]) * _a[num2]; num -= 2; num2 += 2; i -= 4; } float[] array2 = array; int num3 = _n2 - 8; int num4 = _n4; int num5 = 0; int num6 = _n4; int num7 = 0; while (num3 >= 0) { float num8 = array2[num4 + 1] - array2[num5 + 1]; float num9 = array2[num4] - array2[num5]; buffer[num6 + 1] = array2[num4 + 1] + array2[num5 + 1]; buffer[num6] = array2[num4] + array2[num5]; buffer[num7 + 1] = num8 * _a[num3 + 4] - num9 * _a[num3 + 5]; buffer[num7] = num9 * _a[num3 + 4] + num8 * _a[num3 + 5]; num8 = array2[num4 + 3] - array2[num5 + 3]; num9 = array2[num4 + 2] - array2[num5 + 2]; buffer[num6 + 3] = array2[num4 + 3] + array2[num5 + 3]; buffer[num6 + 2] = array2[num4 + 2] + array2[num5 + 2]; buffer[num7 + 3] = num8 * _a[num3] - num9 * _a[num3 + 1]; buffer[num7 + 2] = num9 * _a[num3] + num8 * _a[num3 + 1]; num3 -= 8; num6 += 4; num7 += 4; num4 += 4; num5 += 4; } int n2 = _n >> 4; int num10 = _n2 - 1; _ = _n4; step3_iter0_loop(n2, buffer, num10 - 0, -_n8); step3_iter0_loop(_n >> 4, buffer, _n2 - 1 - _n4, -_n8); int lim = _n >> 5; int num11 = _n2 - 1; _ = _n8; step3_inner_r_loop(lim, buffer, num11 - 0, -(_n >> 4), 16); step3_inner_r_loop(_n >> 5, buffer, _n2 - 1 - _n8, -(_n >> 4), 16); step3_inner_r_loop(_n >> 5, buffer, _n2 - 1 - _n8 * 2, -(_n >> 4), 16); step3_inner_r_loop(_n >> 5, buffer, _n2 - 1 - _n8 * 3, -(_n >> 4), 16); int j; for (j = 2; j < _ld - 3 >> 1; j++) { int num12 = _n >> j + 2; int num13 = num12 >> 1; int num14 = 1 << j + 1; for (int k = 0; k < num14; k++) { step3_inner_r_loop(_n >> j + 4, buffer, _n2 - 1 - num12 * k, -num13, 1 << j + 3); } } for (; j < _ld - 6; j++) { int num15 = _n >> j + 2; int num16 = 1 << j + 3; int num17 = num15 >> 1; int num18 = _n >> j + 6; int n3 = 1 << j + 1; int num19 = _n2 - 1; int num20 = 0; for (int num21 = num18; num21 > 0; num21--) { step3_inner_s_loop(n3, buffer, num19, -num17, num20, num16, num15); num20 += num16 * 4; num19 -= 8; } } step3_inner_s_loop_ld654(_n >> 5, buffer, _n2 - 1, _n); int num22 = 0; int num23 = _n4 - 4; int num24 = _n2 - 4; while (num23 >= 0) { int num25 = _bitrev[num22]; array2[num24 + 3] = buffer[num25]; array2[num24 + 2] = buffer[num25 + 1]; array2[num23 + 3] = buffer[num25 + 2]; array2[num23 + 2] = buffer[num25 + 3]; num25 = _bitrev[num22 + 1]; array2[num24 + 1] = buffer[num25]; array2[num24] = buffer[num25 + 1]; array2[num23 + 1] = buffer[num25 + 2]; array2[num23] = buffer[num25 + 3]; num23 -= 4; num24 -= 4; num22 += 2; } int num26 = 0; int num27 = 0; int num28 = _n2 - 4; while (num27 < num28) { float num29 = array2[num27] - array2[num28 + 2]; float num30 = array2[num27 + 1] + array2[num28 + 3]; float num31 = _c[num26 + 1] * num29 + _c[num26] * num30; float num32 = _c[num26 + 1] * num30 - _c[num26] * num29; float num33 = array2[num27] + array2[num28 + 2]; float num34 = array2[num27 + 1] - array2[num28 + 3]; array2[num27] = num33 + num31; array2[num27 + 1] = num34 + num32; array2[num28 + 2] = num33 - num31; array2[num28 + 3] = num32 - num34; num29 = array2[num27 + 2] - array2[num28]; num30 = array2[num27 + 3] + array2[num28 + 1]; num31 = _c[num26 + 3] * num29 + _c[num26 + 2] * num30; num32 = _c[num26 + 3] * num30 - _c[num26 + 2] * num29; num33 = array2[num27 + 2] + array2[num28]; num34 = array2[num27 + 3] - array2[num28 + 1]; array2[num27 + 2] = num33 + num31; array2[num27 + 3] = num34 + num32; array2[num28] = num33 - num31; array2[num28 + 1] = num32 - num34; num26 += 4; num27 += 4; num28 -= 4; } int num35 = _n2 - 8; int num36 = _n2 - 8; int num37 = 0; int num38 = _n2 - 4; int num39 = _n2; int num40 = _n - 4; while (num36 >= 0) { float num41 = array[num36 + 6] * _b[num35 + 7] - array[num36 + 7] * _b[num35 + 6]; float num42 = (0f - array[num36 + 6]) * _b[num35 + 6] - array[num36 + 7] * _b[num35 + 7]; buffer[num37] = num41; buffer[num38 + 3] = 0f - num41; buffer[num39] = num42; buffer[num40 + 3] = num42; float num43 = array[num36 + 4] * _b[num35 + 5] - array[num36 + 5] * _b[num35 + 4]; float num44 = (0f - array[num36 + 4]) * _b[num35 + 4] - array[num36 + 5] * _b[num35 + 5]; buffer[num37 + 1] = num43; buffer[num38 + 2] = 0f - num43; buffer[num39 + 1] = num44; buffer[num40 + 2] = num44; num41 = array[num36 + 2] * _b[num35 + 3] - array[num36 + 3] * _b[num35 + 2]; num42 = (0f - array[num36 + 2]) * _b[num35 + 2] - array[num36 + 3] * _b[num35 + 3]; buffer[num37 + 2] = num41; buffer[num38 + 1] = 0f - num41; buffer[num39 + 2] = num42; buffer[num40 + 1] = num42; num43 = array[num36] * _b[num35 + 1] - array[num36 + 1] * _b[num35]; num44 = (0f - array[num36]) * _b[num35] - array[num36 + 1] * _b[num35 + 1]; buffer[num37 + 3] = num43; buffer[num38] = 0f - num43; buffer[num39 + 3] = num44; buffer[num40] = num44; num35 -= 8; num36 -= 8; num37 += 4; num39 += 4; num38 -= 4; num40 -= 4; } } private void step3_iter0_loop(int n, float[] e, int i_off, int k_off) { int num = i_off; int num2 = num + k_off; int num3 = 0; for (int num4 = n >> 2; num4 > 0; num4--) { float num5 = e[num] - e[num2]; float num6 = e[num - 1] - e[num2 - 1]; e[num] += e[num2]; e[num - 1] += e[num2 - 1]; e[num2] = num5 * _a[num3] - num6 * _a[num3 + 1]; e[num2 - 1] = num6 * _a[num3] + num5 * _a[num3 + 1]; num3 += 8; num5 = e[num - 2] - e[num2 - 2]; num6 = e[num - 3] - e[num2 - 3]; e[num - 2] += e[num2 - 2]; e[num - 3] += e[num2 - 3]; e[num2 - 2] = num5 * _a[num3] - num6 * _a[num3 + 1]; e[num2 - 3] = num6 * _a[num3] + num5 * _a[num3 + 1]; num3 += 8; num5 = e[num - 4] - e[num2 - 4]; num6 = e[num - 5] - e[num2 - 5]; e[num - 4] += e[num2 - 4]; e[num - 5] += e[num2 - 5]; e[num2 - 4] = num5 * _a[num3] - num6 * _a[num3 + 1]; e[num2 - 5] = num6 * _a[num3] + num5 * _a[num3 + 1]; num3 += 8; num5 = e[num - 6] - e[num2 - 6]; num6 = e[num - 7] - e[num2 - 7]; e[num - 6] += e[num2 - 6]; e[num - 7] += e[num2 - 7]; e[num2 - 6] = num5 * _a[num3] - num6 * _a[num3 + 1]; e[num2 - 7] = num6 * _a[num3] + num5 * _a[num3 + 1]; num3 += 8; num -= 8; num2 -= 8; } } private void step3_inner_r_loop(int lim, float[] e, int d0, int k_off, int k1) { int num = d0; int num2 = num + k_off; int num3 = 0; for (int num4 = lim >> 2; num4 > 0; num4--) { float num5 = e[num] - e[num2]; float num6 = e[num - 1] - e[num2 - 1]; e[num] += e[num2]; e[num - 1] += e[num2 - 1]; e[num2] = num5 * _a[num3] - num6 * _a[num3 + 1]; e[num2 - 1] = num6 * _a[num3] + num5 * _a[num3 + 1]; num3 += k1; num5 = e[num - 2] - e[num2 - 2]; num6 = e[num - 3] - e[num2 - 3]; e[num - 2] += e[num2 - 2]; e[num - 3] += e[num2 - 3]; e[num2 - 2] = num5 * _a[num3] - num6 * _a[num3 + 1]; e[num2 - 3] = num6 * _a[num3] + num5 * _a[num3 + 1]; num3 += k1; num5 = e[num - 4] - e[num2 - 4]; num6 = e[num - 5] - e[num2 - 5]; e[num - 4] += e[num2 - 4]; e[num - 5] += e[num2 - 5]; e[num2 - 4] = num5 * _a[num3] - num6 * _a[num3 + 1]; e[num2 - 5] = num6 * _a[num3] + num5 * _a[num3 + 1]; num3 += k1; num5 = e[num - 6] - e[num2 - 6]; num6 = e[num - 7] - e[num2 - 7]; e[num - 6] += e[num2 - 6]; e[num - 7] += e[num2 - 7]; e[num2 - 6] = num5 * _a[num3] - num6 * _a[num3 + 1]; e[num2 - 7] = num6 * _a[num3] + num5 * _a[num3 + 1]; num3 += k1; num -= 8; num2 -= 8; } } private void step3_inner_s_loop(int n, float[] e, int i_off, int k_off, int a, int a_off, int k0) { float num = _a[a]; float num2 = _a[a + 1]; float num3 = _a[a + a_off]; float num4 = _a[a + a_off + 1]; float num5 = _a[a + a_off * 2]; float num6 = _a[a + a_off * 2 + 1]; float num7 = _a[a + a_off * 3]; float num8 = _a[a + a_off * 3 + 1]; int num9 = i_off; int num10 = num9 + k_off; for (int num11 = n; num11 > 0; num11--) { float num12 = e[num9] - e[num10]; float num13 = e[num9 - 1] - e[num10 - 1]; e[num9] += e[num10]; e[num9 - 1] += e[num10 - 1]; e[num10] = num12 * num - num13 * num2; e[num10 - 1] = num13 * num + num12 * num2; num12 = e[num9 - 2] - e[num10 - 2]; num13 = e[num9 - 3] - e[num10 - 3]; e[num9 - 2] += e[num10 - 2]; e[num9 - 3] += e[num10 - 3]; e[num10 - 2] = num12 * num3 - num13 * num4; e[num10 - 3] = num13 * num3 + num12 * num4; num12 = e[num9 - 4] - e[num10 - 4]; num13 = e[num9 - 5] - e[num10 - 5]; e[num9 - 4] += e[num10 - 4]; e[num9 - 5] += e[num10 - 5]; e[num10 - 4] = num12 * num5 - num13 * num6; e[num10 - 5] = num13 * num5 + num12 * num6; num12 = e[num9 - 6] - e[num10 - 6]; num13 = e[num9 - 7] - e[num10 - 7]; e[num9 - 6] += e[num10 - 6]; e[num9 - 7] += e[num10 - 7]; e[num10 - 6] = num12 * num7 - num13 * num8; e[num10 - 7] = num13 * num7 + num12 * num8; num9 -= k0; num10 -= k0; } } private void step3_inner_s_loop_ld654(int n, float[] e, int i_off, int base_n) { int num = base_n >> 3; float num2 = _a[num]; int num3 = i_off; int num4 = num3 - 16 * n; while (num3 > num4) { float num5 = e[num3] - e[num3 - 8]; float num6 = e[num3 - 1] - e[num3 - 9]; e[num3] += e[num3 - 8]; e[num3 - 1] += e[num3 - 9]; e[num3 - 8] = num5; e[num3 - 9] = num6; num5 = e[num3 - 2] - e[num3 - 10]; num6 = e[num3 - 3] - e[num3 - 11]; e[num3 - 2] += e[num3 - 10]; e[num3 - 3] += e[num3 - 11]; e[num3 - 10] = (num5 + num6) * num2; e[num3 - 11] = (num6 - num5) * num2; num5 = e[num3 - 12] - e[num3 - 4]; num6 = e[num3 - 5] - e[num3 - 13]; e[num3 - 4] += e[num3 - 12]; e[num3 - 5] += e[num3 - 13]; e[num3 - 12] = num6; e[num3 - 13] = num5; num5 = e[num3 - 14] - e[num3 - 6]; num6 = e[num3 - 7] - e[num3 - 15]; e[num3 - 6] += e[num3 - 14]; e[num3 - 7] += e[num3 - 15]; e[num3 - 14] = (num5 + num6) * num2; e[num3 - 15] = (num5 - num6) * num2; iter_54(e, num3); iter_54(e, num3 - 8); num3 -= 16; } } private void iter_54(float[] e, int z) { float num = e[z] - e[z - 4]; float num2 = e[z] + e[z - 4]; float num3 = e[z - 2] + e[z - 6]; float num4 = e[z - 2] - e[z - 6]; e[z] = num2 + num3; e[z - 2] = num2 - num3; float num5 = e[z - 3] - e[z - 7]; e[z - 4] = num + num5; e[z - 6] = num - num5; float num6 = e[z - 1] - e[z - 5]; float num7 = e[z - 1] + e[z - 5]; float num8 = e[z - 3] + e[z - 7]; e[z - 1] = num7 + num8; e[z - 3] = num7 - num8; e[z - 5] = num6 - num4; e[z - 7] = num6 + num4; } } private const float M_PI = MathF.PI; private Dictionary<int, MdctImpl> _setupCache = new Dictionary<int, MdctImpl>(); public void Reverse(float[] samples, int sampleCount) { if (!_setupCache.TryGetValue(sampleCount, out var value)) { value = new MdctImpl(sampleCount); _setupCache[sampleCount] = value; } value.CalcReverse(samples); } } internal class Mode : IMode { private struct OverlapInfo { public int PacketStartIndex; public int PacketTotalLength; public int PacketValidLength; } private const float M_PI2 = MathF.PI / 2f; private int _channels; private bool _blockFlag; private int _blockSize; private IMapping _mapping; private float[][] _windows; private OverlapInfo[] _overlapInfo; public void Init(IPacket packet, int channels, int block0Size, int block1Size, IMapping[] mappings) { _channels = channels; _blockFlag = packet.ReadBit(); if (packet.ReadBits(32) != 0L) { throw new InvalidDataException("Mode header had invalid window or transform type!"); } int num = (int)packet.ReadBits(8); if (num >= mappings.Length) { throw new InvalidDataException("Mode header had invalid mapping index!"); } _mapping = mappings[num]; if (_blockFlag) { _blockSize = block1Size; _windows = new float[4][] { CalcWindow(block0Size, block1Size, block0Size), CalcWindow(block1Size, block1Size, block0Size), CalcWindow(block0Size, block1Size, block1Size), CalcWindow(block1Size, block1Size, block1Size) }; _overlapInfo = new OverlapInfo[4] { CalcOverlap(block0Size, block1Size, block0Size), CalcOverlap(block1Size, block1Size, block0Size), CalcOverlap(block0Size, block1Size, block1Size), CalcOverlap(block1Size, block1Size, block1Size) }; } else { _blockSize = block0Size; _windows = new float[1][] { CalcWindow(block0Size, block0Size, block0Size) }; } } private static float[] CalcWindow(int prevBlockSize, int blockSize, int nextBlockSize) { float[] array = new float[blockSize]; int num = prevBlockSize / 2; int num2 = nextBlockSize / 2; int num3 = blockSize / 4 - num / 2; int num4 = blockSize - blockSize / 4 - num2 / 2; for (int i = 0; i < num; i++) { float num5 = (float)Math.Sin(((double)i + 0.5) / (double)num * 1.5707963705062866); num5 *= num5; array[num3 + i] = (float)Math.Sin(num5 * (MathF.PI / 2f)); } for (int j = num3 + num; j < num4; j++) { array[j] = 1f; } for (int k = 0; k < num2; k++) { float num6 = (float)Math.Sin(((double)(num2 - k) - 0.5) / (double)num2 * 1.5707963705062866); num6 *= num6; array[num4 + k] = (float)Math.Sin(num6 * (MathF.PI / 2f)); } return array; } private static OverlapInfo CalcOverlap(int prevBlockSize, int blockSize, int nextBlockSize) { int num = prevBlockSize / 4; int num2 = nextBlockSize / 4; int packetStartIndex = blockSize / 4 - num; int num3 = blockSize / 4 * 3 + num2; int packetValidLength = num3 - num2 * 2; OverlapInfo result = default(OverlapInfo); result.PacketStartIndex = packetStartIndex; result.PacketValidLength = packetValidLength; result.PacketTotalLength = num3; return result; } private bool GetPacketInfo(IPacket packet, out int windowIndex, out int packetStartIndex, out int packetValidLength, out int packetTotalLength) { if (packet.IsShort) { windowIndex = 0; packetStartIndex = 0; packetValidLength = 0; packetTotalLength = 0; return false; } if (_blockFlag) { bool flag = packet.ReadBit(); bool flag2 = packet.ReadBit(); windowIndex = (flag ? 1 : 0) + (flag2 ? 2 : 0); OverlapInfo overlapInfo = _overlapInfo[windowIndex]; packetStartIndex = overlapInfo.PacketStartIndex; packetValidLength = overlapInfo.PacketValidLength; packetTotalLength = overlapInfo.PacketTotalLength; } else { windowIndex = 0; packetStartIndex = 0; packetValidLength = _blockSize / 2; packetTotalLength = _blockSize; } return true; } public bool Decode(IPacket packet, float[][] buffer, out int packetStartindex, out int packetValidLength, out int packetTotalLength) { if (GetPacketInfo(packet, out var windowIndex, out packetStartindex, out packetValidLength, out packetTotalLength)) { _mapping.DecodePacket(packet, _blockSize, _channels, buffer); float[] array = _windows[windowIndex]; for (int i = 0; i < _blockSize; i++) { for (int j = 0; j < _channels; j++) { buffer[j][i] *= array[i]; } } return true; } return false; } public int GetPacketSampleCount(IPacket packet) { GetPacketInfo(packet, out var _, out var packetStartIndex, out var packetValidLength, out var _); return packetValidLength - packetStartIndex; } } [Serializable] public class NewStreamEventArgs : EventArgs { public IStreamDecoder StreamDecoder { get; } public bool IgnoreStream { get; set; } public NewStreamEventArgs(IStreamDecoder streamDecoder) { StreamDecoder = streamDecoder ?? throw new ArgumentNullException("streamDecoder"); } } internal class Residue0 : IResidue { private int _channels; private int _begin; private int _end; private int _partitionSize; private int _classifications; private int _maxStages; private ICodebook[][] _books; private ICodebook _classBook; private int[] _cascade; private int[][] _decodeMap; private static int icount(int v) { int num = 0; while (v != 0) { num += v & 1; v >>= 1; } return num; } public virtual void Init(IPacket packet, int channels, ICodebook[] codebooks) { _begin = (int)packet.ReadBits(24); _end = (int)packet.ReadBits(24); _partitionSize = (int)packet.ReadBits(24) + 1; _classifications = (int)packet.ReadBits(6) + 1; _classBook = codebooks[(uint)packet.ReadBits(8)]; _cascade = new int[_classifications]; int num = 0; for (int i = 0; i < _classifications; i++) { int num2 = (int)packet.ReadBits(3); if (packet.ReadBit()) { _cascade[i] = ((int)packet.ReadBits(5) << 3) | num2; } else { _cascade[i] = num2; } num += icount(_cascade[i]); } int[] array = new int[num]; for (int j = 0; j < num; j++) { array[j] = (int)packet.ReadBits(8); if (codebooks[array[j]].MapType == 0) { throw new InvalidDataException(); } } int entries = _classBook.Entries; int num3 = _classBook.Dimensions; int num4 = 1; while (num3 > 0) { num4 *= _classifications; if (num4 > entries) { throw new InvalidDataException(); } num3--; } _books = new ICodebook[_classifications][]; num = 0; int num5 = 0; for (int k = 0; k < _classifications; k++) { int num6 = Utils.ilog(_cascade[k]); _books[k] = new ICodebook[num6]; if (num6 <= 0) { continue; } num5 = Math.Max(num5, num6); for (int l = 0; l < num6; l++) { if ((_cascade[k] & (1 << l)) > 0) { _books[k][l] = codebooks[array[num++]]; } } } _maxStages = num5; _decodeMap = new int[num4][]; for (int m = 0; m < num4; m++) { int num7 = m; int num8 = num4 / _classifications; _decodeMap[m] = new int[_classBook.Dimensions]; for (int n = 0; n < _classBook.Dimensions; n++) { int num9 = num7 / num8; num7 -= num9 * num8; num8 /= _classifications; _decodeMap[m][n] = num9; } } _channels = channels; } public virtual void Decode(IPacket packet, bool[] doNotDecodeChannel, int blockSize, float[][] buffer) { int num = ((_end < blockSize / 2) ? _end : (blockSize / 2)) - _begin; if (num <= 0 || Array.IndexOf(doNotDecodeChannel, value: false) == -1) { return; } int num2 = num / _partitionSize; int num3 = (num2 + _classBook.Dimensions - 1) / _classBook.Dimensions; int[,][] array = new int[_channels, num3][]; for (int i = 0; i < _maxStages; i++) { int j = 0; int num4 = 0; while (j < num2) { if (i == 0) { for (int k = 0; k < _channels; k++) { int num5 = _classBook.DecodeScalar(packet); if (num5 >= 0 && num5 < _decodeMap.Length) { array[k, num4] = _decodeMap[num5]; continue; } j = num2; i = _maxStages; break; } } int num6 = 0; for (; j < num2; j++) { if (num6 >= _classBook.Dimensions) { break; } int offset = _begin + j * _partitionSize; for (int l = 0; l < _channels; l++) { int num7 = array[l, num4][num6]; if ((_cascade[num7] & (1 << i)) != 0) { ICodebook codebook = _books[num7][i]; if (codebook != null && WriteVectors(codebook, packet, buffer, l, offset, _partitionSize)) { j = num2; i = _maxStages; break; } } } num6++; } num4++; } } } protected virtual bool WriteVectors(ICodebook codebook, IPacket packet, float[][] residue, int channel, int offset, int partitionSize) { float[] array = residue[channel]; int num = partitionSize / codebook.Dimensions; int[] array2 = new int[num]; for (int i = 0; i < num; i++) { if ((array2[i] = codebook.DecodeScalar(packet)) == -1) { return true; } } for (int j = 0; j < codebook.Dimensions; j++) { int num2 = 0; while (num2 < num) { array[offset] += codebook[array2[num2], j]; num2++; offset++; } } return false; } } internal class Residue1 : Residue0 { protected override bool WriteVectors(ICodebook codebook, IPacket packet, float[][] residue, int channel, int offset, int partitionSize) { float[] array = residue[channel]; int num = 0; while (num < partitionSize) { int num2 = codebook.DecodeScalar(packet); if (num2 == -1) { return true; } for (int i = 0; i < codebook.Dimensions; i++) { array[offset + num] += codebook[num2, i]; num++; } } return false; } } internal class Residue2 : Residue0 { private int _channels; public override void Init(IPacket packet, int channels, ICodebook[] codebooks) { _channels = channels; base.Init(packet, 1, codebooks); } public override void Decode(IPacket packet, bool[] doNotDecodeChannel, int blockSize, float[][] buffer) { base.Decode(packet, doNotDecodeChannel, blockSize * _channels, buffer); } protected override bool WriteVectors(ICodebook codebook, IPacket packet, float[][] residue, int channel, int offset, int partitionSize) { int num = 0; offset /= _channels; int num2 = 0; while (num2 < partitionSize) { int num3 = codebook.DecodeScalar(packet); if (num3 == -1) { return true; } int num4 = 0; while (num4 < codebook.Dimensions) { residue[num][offset] += codebook[num3, num4]; if (++num == _channels) { num = 0; offset++; } num4++; num2++; } } return false; } } public sealed class StreamDecoder : IStreamDecoder, IDisposable { private NVorbis.Contracts.IPacketProvider _packetProvider; private IFactory _factory; private StreamStats _stats; private byte _channels; private int _sampleRate; private int _block0Size; private int _block1Size; private IMode[] _modes; private int _modeFieldBits; private string _vendor; private string[] _comments; private ITagData _tags; private long _currentPosition; private bool _hasClipped; private bool _hasPosition; private bool _eosFound; private float[][] _nextPacketBuf; private float[][] _prevPacketBuf; private int _prevPacketStart; private int _prevPacketEnd; private int _prevPacketStop; private static readonly byte[] PacketSignatureStream = new byte[11] { 1, 118, 111, 114, 98, 105, 115, 0, 0, 0, 0 }; private static readonly byte[] PacketSignatureComments = new byte[7] { 3, 118, 111, 114, 98, 105, 115 }; private static readonly byte[] PacketSignatureBooks = new byte[7] { 5, 118, 111, 114, 98, 105, 115 }; internal static Func<IFactory> CreateFactory { get; set; } = () => new Factory(); public int Channels => _channels; public int SampleRate => _sampleRate; public int UpperBitrate { get; private set; } public int NominalBitrate { get; private set; } public int LowerBitrate { get; private set; } public ITagData Tags => _tags ?? (_tags = new TagData(_vendor, _comments)); public TimeSpan TotalTime => TimeSpan.FromSeconds((double)TotalSamples / (double)_sampleRate); public long TotalSamples => (_packetProvider ?? throw new ObjectDisposedException("StreamDecoder")).GetGranuleCount(); public TimeSpan TimePosition { get { return TimeSpan.FromSeconds((double)_currentPosition / (double)_sampleRate); } set { SeekTo(value); } } public long SamplePosition { get { return _currentPosition; } set { SeekTo(value); } } public bool ClipSamples { get; set; } public bool HasClipped => _hasClipped; public bool IsEndOfStream { get { if (_eosFound) { return _prevPacketBuf == null; } return false; } } public IStreamStats Stats => _stats; public StreamDecoder(NVorbis.Contracts.IPacketProvider packetProvider) : this(packetProvider, new Factory()) { } internal StreamDecoder(NVorbis.Contracts.IPacketProvider packetProvider, IFactory factory) { _packetProvider = packetProvider ?? throw new ArgumentNullException("packetProvider"); _factory = factory ?? throw new ArgumentNullException("factory"); _stats = new StreamStats(); _currentPosition = 0L; ClipSamples = true; IPacket packet = _packetProvider.PeekNextPacket(); if (!ProcessHeaderPackets(packet)) { _packetProvider = null; packet.Reset(); throw GetInvalidStreamException(packet); } } private static Exception GetInvalidStreamException(IPacket packet) { try { ulong num = packet.ReadBits(64); if (num == 7233173838382854223L) { return new ArgumentException("Found OPUS bitstream."); } if ((num & 0xFF) == 127) { return new ArgumentException("Found FLAC bitstream."); } switch (num) { case 2314885909937746003uL: return new ArgumentException("Found Speex bitstream."); case 28254585843050854uL: return new ArgumentException("Found Skeleton metadata bitstream."); default: if ((num & 0xFFFFFFFFFFFF00L) == 27428895509214208L) { return new ArgumentException("Found Theora bitsream."); } return new ArgumentException("Could not find Vorbis data to decode."); } } finally { packet.Reset(); } } private bool ProcessHeaderPackets(IPacket packet) { if (!ProcessHeaderPacket(packet, LoadStreamHeader, delegate { _packetProvider.GetNextPacket().Done(); })) { return false; } if (!ProcessHeaderPacket(_packetProvider.GetNextPacket(), LoadComments, delegate(IPacket pkt) { pkt.Done(); })) { return false; } if (!ProcessHeaderPacket(_packetProvider.GetNextPacket(), LoadBooks, delegate(IPacket pkt) { pkt.Done(); })) { return false; } _currentPosition = 0L; ResetDecoder(); return true; } private static bool ProcessHeaderPacket(IPacket packet, Func<IPacket, bool> processAction, Action<IPacket> doneAction) { if (packet != null) { try { return processAction(packet); } finally { doneAction(packet); } } return false; } private static bool ValidateHeader(IPacket packet, byte[] expected) { for (int i = 0; i < expected.Length; i++) { if (expected[i] != packet.ReadBits(8)) { return false; } } return true; } private static string ReadString(IPacket packet) { int num = (int)packet.ReadBits(32); if (num == 0) { return string.Empty; } byte[] array = new byte[num]; if (packet.Read(array, 0, num) < num) { throw new InvalidDataException("Could not read full string!"); } return Encoding.UTF8.GetString(array); } private bool LoadStreamHeader(IPacket packet) { if (!ValidateHeader(packet, PacketSignatureStream)) { return false; } _channels = (byte)packet.ReadBits(8); _sampleRate = (int)packet.ReadBits(32); UpperBitrate = (int)packet.ReadBits(32); NominalBitrate = (int)packet.ReadBits(32); LowerBitrate = (int)packet.ReadBits(32); _block0Size = 1 << (int)packet.ReadBits(4); _block1Size = 1 << (int)packet.ReadBits(4); if (NominalBitrate == 0 && UpperBitrate > 0 && LowerBitrate > 0) { NominalBitrate = (UpperBitrate + LowerBitrate) / 2; } _stats.SetSampleRate(_sampleRate); _stats.AddPacket(-1, packet.BitsRead, packet.BitsRemaining, packet.ContainerOverheadBits); return true; } private bool LoadComments(IPacket packet) { if (!ValidateHeader(packet, PacketSignatureComments)) { return false; } _vendor = ReadString(packet); _comments = new string[packet.ReadBits(32)]; for (int i = 0; i < _comments.Length; i++) { _comments[i] = ReadString(packet); } _stats.AddPacket(-1, packet.BitsRead, packet.BitsRemaining, packet.ContainerOverheadBits); return true; } private bool LoadBooks(IPacket packet) { if (!ValidateHeader(packet, PacketSignatureBooks)) { return false; } IMdct mdct = _factory.CreateMdct(); IHuffman huffman = _factory.CreateHuffman(); ICodebook[] array = new ICodebook[packet.ReadBits(8) + 1]; for (int i = 0; i < array.Length; i++) { array[i] = _factory.CreateCodebook(); array[i].Init(packet, huffman); } int num = (int)packet.ReadBits(6) + 1; packet.SkipBits(16 * num); IFloor[] array2 = new IFloor[packet.ReadBits(6) + 1]; for (int j = 0; j < array2.Length; j++) { array2[j] = _factory.CreateFloor(packet); array2[j].Init(packet, _channels, _block0Size, _block1Size, array); } IResidue[] array3 = new IResidue[packet.ReadBits(6) + 1]; for (int k = 0; k < array3.Length; k++) { array3[k] = _factory.CreateResidue(packet); array3[k].Init(packet, _channels, array); } IMapping[] array4 = new IMapping[packet.ReadBits(6) + 1]; for (int l = 0; l < array4.Length; l++) { array4[l] = _factory.CreateMapping(packet); array4[l].Init(packet, _channels, array2, array3, mdct); } _modes = new IMode[packet.ReadBits(6) + 1]; for (int m = 0; m < _modes.Length; m++) { _modes[m] = _factory.CreateMode(); _modes[m].Init(packet, _channels, _block0Size, _block1Size, array4); } if (!packet.ReadBit()) { throw new InvalidDataException("Book packet did not end on correct bit!"); } _modeFieldBits = Utils.ilog(_modes.Length - 1); _stats.AddPacket(-1, packet.BitsRead, packet.BitsRemaining, packet.ContainerOverheadBits); return true; } private void ResetDecoder() { _prevPacketBuf = null; _prevPacketStart = 0; _prevPacketEnd = 0; _prevPacketStop = 0; _nextPacketBuf = null; _eosFound = false; _hasClipped = false; _hasPosition = false; } public int Read(Span<float> buffer, int offset, int count) { if (buffer == null) { throw new ArgumentNullException("buffer"); } if (offset < 0 || offset + count > buffer.Length) { throw new ArgumentOutOfRangeException("offset"); } if (count % _channels != 0) { throw new ArgumentOutOfRangeException("count", "Must be a multiple of Channels!"); } if (_packetProvider == null) { throw new ObjectDisposedException("StreamDecoder"); } if (count == 0) { return 0; } int num = offset; int num2 = offset + count; while (num < num2) { if (_prevPacketStart == _prevPacketEnd) { if (_eosFound) { _nextPacketBuf = null; _prevPacketBuf = null; break; } if (!ReadNextPacket((num - offset) / _channels, out var samplePosition)) { _prevPacketEnd = _prevPacketStop; } if (samplePosition.HasValue && !_hasPosition) { _hasPosition = true; _currentPosition = samplePosition.Value - (_prevPacketEnd - _prevPacketStart) - (num - offset) / _channels; } } int num3 = Math.Min((num2 - num) / _channels, _prevPacketEnd - _prevPacketStart); if (num3 > 0) { num = ((!ClipSamples) ? (num + CopyBuffer(buffer, num, num3)) : (num + ClippingCopyBuffer(buffer, num, num3))); } } count = num - offset; _currentPosition += count / _channels; return count; } private int ClippingCopyBuffer(Span<float> target, int targetIndex, int count) { int num = targetIndex; while (count > 0) { for (int i = 0; i < _channels; i++) { target[num++] = Utils.ClipValue(_prevPacketBuf[i][_prevPacketStart], ref _hasClipped); } _prevPacketStart++; count--; } return num - targetIndex; } private int CopyBuffer(Span<float> target, int targetIndex, int count) { int num = targetIndex; while (count > 0) { for (int i = 0; i < _channels; i++) { target[num++] = _prevPacketBuf[i][_prevPacketStart]; } _prevPacketStart++; count--; } return num - targetIndex; } private bool ReadNextPacket(int bufferedSamples, out long? samplePosition) { int packetStartindex; int packetValidLength; int packetTotalLength; bool isEndOfStream; int bitsRead; int bitsRemaining; int containerOverheadBits; float[][] array = DecodeNextPacket(out packetStartindex, out packetValidLength, out packetTotalLength, out isEndOfStream, out samplePosition, out bitsRead, out bitsRemaining, out containerOverheadBits); _eosFound |= isEndOfStream; if (array == null) { _stats.AddPacket(0, bitsRead, bitsRemaining, containerOverheadBits); return false; } if (samplePosition.HasValue && isEndOfStream) { long num = _currentPosition + bufferedSamples + packetValidLength - packetStartindex; int num2 = (int)(samplePosition.Value - num); if (num2 < 0) { packetValidLength += num2; } } if (_prevPacketEnd > 0) { OverlapBuffers(_prevPacketBuf, array, _prevPacketStart, _prevPacketStop, packetStartindex, _channels); _prevPacketStart = packetStartindex; } else if (_prevPacketBuf == null) { _prevPacketStart = packetValidLength; } _stats.AddPacket(packetValidLength - _prevPacketStart, bitsRead, bitsRemaining, containerOverheadBits); _nextPacketBuf = _prevPacketBuf; _prevPacketEnd = packetValidLength; _prevPacketStop = packetTotalLength; _prevPacketBuf = array; return true; } private float[][] DecodeNextPacket(out int packetStartindex, out int packetValidLength, out int packetTotalLength, out bool isEndOfStream, out long? samplePosition, out int bitsRead, out int bitsRemaining, out int containerOverheadBits) { IPacket packet = null; try { if ((packet = _packetProvider.GetNextPacket()) == null) { isEndOfStream = true; } else { isEndOfStream = packet.IsEndOfStream; if (packet.IsResync) { _hasPosition = false; } containerOverheadBits = packet.ContainerOverheadBits; if (packet.ReadBit()) { bitsRemaining = packet.BitsRemaining + 1; } else { IMode mode = _modes[(uint)packet.ReadBits(_modeFieldBits)]; if (_nextPacketBuf == null) { _nextPacketBuf = new float[_channels][]; for (int i = 0; i < _channels; i++) { _nextPacketBuf[i] = new float[_block1Size]; } } if (mode.Decode(packet, _nextPacketBuf, out packetStartindex, out packetValidLength, out packetTotalLength)) { samplePosition = packet.GranulePosition; bitsRead = packet.BitsRead; bitsRemaining = packet.BitsRemaining; return _nextPacketBuf; } bitsRemaining = packet.BitsRead + packet.BitsRemaining; } } packetStartindex = 0; packetValidLength = 0; packetTotalLength = 0; samplePosition = null; bitsRead = 0; bitsRemaining = 0; containerOverheadBits = 0; return null; } finally { packet?.Done(); } } private static void OverlapBuffers(float[][] previous, float[][] next, int prevStart, int prevLen, int nextStart, int channels) { while (prevStart < prevLen) { for (int i = 0; i < channels; i++) { next[i][nextStart] += previous[i][prevStart]; } prevStart++; nextStart++; } } public void SeekTo(TimeSpan timePosition, SeekOrigin seekOrigin = SeekOrigin.Begin) { SeekTo((long)((double)SampleRate * timePosition.TotalSeconds), seekOrigin); } public void SeekTo(long samplePosition, SeekOrigin seekOrigin = SeekOrigin.Begin) { if (_packetProvider == null) { throw new ObjectDisposedException("StreamDecoder"); } if (!_packetProvider.CanSeek) { throw new InvalidOperationException("Seek is not supported by the Contracts.IPacketProvider instance."); } switch (seekOrigin) { case SeekOrigin.Current: samplePosition = SamplePosition - samplePosition; break; case SeekOrigin.End: samplePosition = TotalSamples - samplePosition; break; default: throw new ArgumentOutOfRangeException("seekOrigin"); case SeekOrigin.Begin: break; } if (samplePosition < 0) { throw new ArgumentOutOfRangeException("samplePosition"); } int num; if (samplePosition == 0L) { _packetProvider.SeekTo(0L, 0, GetPacketGranules); num = 0; } else { long num2 = _packetProvider.SeekTo(samplePosition, 1, GetPacketGranules); num = (int)(samplePosition - num2); } ResetDecoder(); _hasPosition = true; if (!ReadNextPacket(0, out var samplePosition2)) { _eosFound = true; if (_packetProvider.GetGranuleCount() != samplePosition) { throw new InvalidOperationException("Could not read pre-roll packet! Try seeking again prior to reading more samples."); } _prevPacketStart = _prevPacketStop; _currentPosition = samplePosition; return; } if (!ReadNextPacket(0, out samplePosition2)) { ResetDecoder(); _eosFound = true; throw new InvalidOperationException("Could not read pre-roll packet! Try seeking again prior to reading more samples."); } _prevPacketStart += num; _currentPosition = samplePosition; } private int GetPacketGranules(IPacket curPacket) { if (curPacket.IsResync) { return 0; } if (curPacket.ReadBit()) { return 0; } int num = (int)curPacket.ReadBits(_modeFieldBits); if (num < 0 || num >= _modes.Length) { return 0; } return _modes[num].GetPacketSampleCount(curPacket); } public void Dispose() { (_packetProvider as IDisposable)?.Dispose(); _packetProvider = null; } } internal class StreamStats : IStreamStats { private int _sampleRate; private readonly int[] _packetBits = new int[2]; private readonly int[] _packetSamples = new int[2]; private int _packetIndex; private long _totalSamples; private long _audioBits; private long _headerBits; private long _containerBits; private long _wasteBits; private object _lock = new object(); private int _packetCount; public int EffectiveBitRate { get { long totalSamples; long num; lock (_lock) { totalSamples = _totalSamples; num = _audioBits + _headerBits + _containerBits + _wasteBits; } if (totalSamples > 0) { return (int)((double)num / (double)totalSamples * (double)_sampleRate); } return 0; } } public int InstantBitRate { get { int num; int num2; lock (_lock) { num = _packetBits[0] + _packetBits[1]; num2 = _packetSamples[0] + _packetSamples[1]; } if (num2 > 0) { return (int)((double)num / (double)num2 * (double)_sampleRate); } return 0; } } public long ContainerBits => _containerBits; public long OverheadBits => _headerBits; public long AudioBits => _audioBits; public long WasteBits => _wasteBits; public int PacketCount => _packetCount; public void ResetStats() { lock (_lock) { _packetBits[0] = (_packetBits[1] = 0); _packetSamples[0] = (_packetSamples[1] = 0); _packetIndex = 0; _packetCount = 0; _audioBits = 0L; _totalSamples = 0L; _headerBits = 0L; _containerBits = 0L; _wasteBits = 0L; } } internal void SetSampleRate(int sampleRate) { lock (_lock) { _sampleRate = sampleRate; ResetStats(); } } internal void AddPacket(int samples, int bits, int waste, int container) { lock (_lock) { if (samples >= 0) { _audioBits += bits; _wasteBits += waste; _containerBits += container; _totalSamples += samples; _packetBits[_packetIndex] = bits + waste; _packetSamples[_packetIndex] = samples; if (++_packetIndex == 2) { _packetIndex = 0; } } else { _headerBits += bits; _wasteBits += waste; _containerBits += container; } } } } internal class TagData : ITagData { private static IReadOnlyList<string> s_emptyList = new List<string>(); private Dictionary<string, IReadOnlyList<string>> _tags; public IReadOnlyDictionary<string, IReadOnlyList<string>> All => _tags; public string EncoderVendor { get; } public string Title => GetTagSingle("TITLE"); public string Version => GetTagSingle("VERSION"); public string Album => GetTagSingle("ALBUM"); public string TrackNumber => GetTagSingle("TRACKNUMBER"); public string Artist => GetTagSingle("ARTIST"); public IReadOnlyList<string> Performers => GetTagMulti("PERFORMER"); public string Copyright => GetTagSingle("COPYRIGHT"); public string License => GetTagSingle("LICENSE"); public string Organization => GetTagSingle("ORGANIZATION"); public string Description => GetTagSingle("DESCRIPTION"); public IReadOnlyList<string> Genres => GetTagMulti("GENRE"); public IReadOnlyList<string> Dates => GetTagMulti("DATE"); public IReadOnlyList<string> Locations => GetTagMulti("LOCATION"); public string Contact => GetTagSingle("CONTACT"); public string Isrc => GetTagSingle("ISRC"); public TagData(string vendor, string[] comments) { EncoderVendor = vendor; Dictionary<string, IReadOnlyList<string>> dictionary = new Dictionary<string, IReadOnlyList<string>>(); for (int i = 0; i < comments.Length; i++) { string[] array = comments[i].Split(new char[1] { '=' }); if (array.Length == 1) { array = new string[2] { array[0], string.Empty }; } int num = array[0].IndexOf('['); if (num > -1) { array[1] = array[0].Substring(num + 1, array[0].Length - num - 2).ToUpper(CultureInfo.CurrentCulture) + ": " + array[1]; array[0] = array[0].Substring(0, num); } if (dictionary.TryGetValue(array[0].ToUpperInvariant(), out var value)) { ((List<string>)value).Add(array[1]); continue; } dictionary.Add(array[0].ToUpperInvariant(), new List<string> { array[1] }); } _tags = dictionary; } public string GetTagSingle(string key, bool concatenate = false) { IReadOnlyList<string> tagMulti = GetTagMulti(key); if (tagMulti.Count > 0) { if (concatenate) { return string.Join(Environment.NewLine, tagMulti.ToArray()); } return tagMulti[tagMulti.Count - 1]; } return string.Empty; } public IReadOnlyList<string> GetTagMulti(string key) { if (_tags.TryGetValue(key.ToUpperInvariant(), out var value)) { return value; } return s_emptyList; } } internal static class Utils { internal static int ilog(int x) { int num = 0; while (x > 0) { num++; x >>= 1; } return num; } internal static uint BitReverse(uint n) { return BitReverse(n, 32); } internal static uint BitReverse(uint n, int bits) { n = ((n & 0xAAAAAAAAu) >> 1) | ((n & 0x55555555) << 1); n = ((n & 0xCCCCCCCCu) >> 2) | ((n & 0x33333333) << 2); n = ((n & 0xF0F0F0F0u) >> 4) | ((n & 0xF0F0F0F) << 4); n = ((n & 0xFF00FF00u) >> 8) | ((n & 0xFF00FF) << 8); return ((n >> 16) | (n << 16)) >> 32 - bits; } internal static float ClipValue(float value, ref bool clipped) { if (value > MathF.PI * 113f / 355f) { clipped = true; return MathF.PI * 113f / 355f; } if (value < MathF.PI * -113f / 355f) { clipped = true; return MathF.PI * -113f / 355f; } return value; } internal static float ConvertFromVorbisFloat32(uint bits) { int num = (int)bits >> 31; double y = (int)(((bits & 0x7FE00000) >> 21) - 788); return (float)(((bits & 0x1FFFFF) ^ num) + (num & 1)) * (float)Math.Pow(2.0, y); } } public sealed class VorbisReader : IVorbisReader, IDisposable { private readonly List<IStreamDecoder> _decoders; private readonly NVorbis.Contracts.IContainerReader _containerReader; private readonly bool _closeOnDispose; private IStreamDecoder _streamDecoder; internal static Func<Stream, bool, NVorbis.Contracts.IContainerReader> CreateContainerReader { get; set; } = (Stream s, bool cod) => new ContainerReader(s, cod); internal static Func<NVorbis.Contracts.IPacketProvider, IStreamDecoder> CreateStreamDecoder { get; set; } = (NVorbis.Contracts.IPacketProvider pp) => new StreamDecoder(pp, new Factory()); public IReadOnlyList<IStreamDecoder> Streams => _decoders; public int Channels => _streamDecoder.Channels; public int SampleRate => _streamDecoder.SampleRate; public int UpperBitrate => _streamDecoder.UpperBitrate; public int NominalBitrate => _streamDecoder.NominalBitrate; public int LowerBitrate => _streamDecoder.LowerBitrate; public ITagData Tags => _streamDecoder.Tags; [Obsolete("Use .Tags.EncoderVendor instead.")] public string Vendor => _streamDecoder.Tags.EncoderVendor; [Obsolete("Use .Tags.All instead.")] public string[] Comments => _streamDecoder.Tags.All.SelectMany((KeyValuePair<string, IReadOnlyList<string>> k) => k.Value, (KeyValuePair<string, IReadOnlyList<string>> kvp, string Item) => kvp.Key + "=" + Item).ToArray(); [Obsolete("No longer supported. Will receive a new stream when parameters change.", true)] public bool IsParameterChange { get { throw new NotSupportedException(); } } public long ContainerOverheadBits => _containerReader?.ContainerBits ?? 0; public long ContainerWasteBits => _containerReader?.WasteBits ?? 0; public int StreamIndex => _decoders.IndexOf(_streamDecoder); [Obsolete("Use .Streams.Count instead.")] public int StreamCount => _decoders.Count; [Obsolete("Use VorbisReader.TimePosition instead.")] public TimeSpan DecodedTime { get { return _streamDecoder.TimePosition; } set { TimePosition = value; } } [Obsolete("Use VorbisReader.SamplePosition instead.")] public long DecodedPosition { get { return _streamDecoder.SamplePosition; } set { SamplePosition = value; } } public TimeSpan TotalTime => _streamDecoder.TotalTime; public long TotalSamples => _streamDecoder.TotalSamples; public TimeSpan TimePosition { get { return _streamDecoder.TimePosition; } set { _streamDecoder.TimePosition = value; } } public long SamplePosition { get { return _streamDecoder.SamplePosition; } set { _streamDecoder.SamplePosition = value; } } public bool IsEndOfStream => _streamDecoder.IsEndOfStream; public bool ClipSamples { get { return _streamDecoder.ClipSamples; } set { _streamDecoder.ClipSamples = value; } } public bool HasClipped => _streamDecoder.HasClipped; public IStreamStats StreamStats => _streamDecoder.Stats; [Obsolete("Use Streams[*].Stats instead.", true)] public IVorbisStreamStatus[] Stats { get { throw new NotSupportedException(); } } public event EventHandler<NewStreamEventArgs> NewStream; public VorbisReader(string fileName) : this(File.OpenRead(fileName)) { } public VorbisReader(Stream stream, bool closeOnDispose = true) { _decoders = new List<IStreamDecoder>(); NVorbis.Contracts.IContainerReader containerReader = CreateContainerReader(stream, closeOnDispose); containerReader.NewStreamCallback = ProcessNewStream; if (!containerReader.TryInit() || _decoders.Count == 0) { containerReader.NewStreamCallback = null; containerReader.Dispose(); if (closeOnDispose) { stream.Dispose(); } throw new ArgumentException("Could not load the specified container!", "containerReader"); } _closeOnDispose = closeOnDispose; _containerReader = containerReader; _streamDecoder = _decoders[0]; } [Obsolete("Use \"new StreamDecoder(Contracts.IPacketProvider)\" and the container's NewStreamCallback or Streams property instead.", true)] public VorbisReader(NVorbis.Contracts.IContainerReader containerReader) { throw new NotSupportedException(); } [Obsolete("Use \"new StreamDecoder(Contracts.IPacketProvider)\" instead.", true)] public VorbisReader(NVorbis.Contracts.IPacketProvider packetProvider) { throw new NotSupportedException(); } private bool ProcessNewStream(NVorbis.Contracts.IPacketProvider packetProvider) { IStreamDecoder streamDecoder = CreateStreamDecoder(packetProvider); streamDecoder.ClipSamples = true; NewStreamEventArgs newStreamEventArgs = new NewStreamEventArgs(streamDecoder); this.NewStream?.Invoke(this, newStreamEventArgs); if (!newStreamEventArgs.IgnoreStream) { _decoders.Add(streamDecoder); return true; } return false; } public void Dispose() { if (_decoders != null) { foreach (IStreamDecoder decoder in _decoders) { decoder.Dispose(); } _decoders.Clear(); } if (_containerReader != null) { _containerReader.NewStreamCallback = null; if (_closeOnDispose) { _containerReader.Dispose(); } } } public bool FindNextStream() { if (_containerReader == null) { return false; } return _containerReader.FindNextStream(); } public bool SwitchStreams(int index) { if (index < 0 || index >= _decoders.Count) { throw new ArgumentOutOfRangeException("index"); } IStreamDecoder streamDecoder = _decoders[index]; IStreamDecoder streamDecoder2 = _streamDecoder; if (streamDecoder == streamDecoder2) { return false; } streamDecoder.ClipSamples = streamDecoder2.ClipSamples; _streamDecoder = streamDecoder; if (streamDecoder.Channels == streamDecoder2.Channels) { return streamDecoder.SampleRate != streamDecoder2.SampleRate; } return true; } public void SeekTo(TimeSpan timePosition, SeekOrigin seekOrigin = SeekOrigin.Begin) { _streamDecoder.SeekTo(timePosition, seekOrigin); } public void SeekTo(long samplePosition, SeekOrigin seekOrigin = SeekOrigin.Begin) { _streamDecoder.SeekTo(samplePosition, seekOrigin); } public int ReadSamples(float[] buffer, int offset, int count) { count -= count % _streamDecoder.Channels; if (count > 0) { return _streamDecoder.Read(buffer, offset, count); } return 0; } public int ReadSamples(Span<float> buffer) { int count = buffer.Length - buffer.Length % _streamDecoder.Channels; if (!buffer.IsEmpty) { return _streamDecoder.Read(buffer, 0, count); } return 0; } [Obsolete("No longer needed.", true)] public void ClearParameterChange() { throw new NotSupportedException(); } } } namespace NVorbis.Ogg { public sealed class ContainerReader : NVorbis.Contracts.IContainerReader, IDisposable { private IPageReader _reader; private List<WeakReference<NVorbis.Contracts.IPacketProvider>> _packetProviders; private bool _foundStream; internal static Func<Stream, bool, Func<NVorbis.Contracts.IPacketProvider, bool>, IPageReader> CreatePageReader { get; set; } = (Stream s, bool cod, Func<NVorbis.Contracts.IPacketProvider, bool> cb) => new PageReader(s, cod, cb); internal static Func<Stream, bool, Func<NVorbis.Contracts.IPacketProvider, bool>, IPageReader> CreateForwardOnlyPageReader { get; set; } = (Stream s, bool cod, Func<NVorbis.Contracts.IPacketProvider, bool> cb) => new ForwardOnlyPageReader(s, cod, cb); public NewStreamHandler NewStreamCallback { get; set; } public bool CanSeek { get; } public long WasteBits => _reader.WasteBits; public long ContainerBits => _reader.ContainerBits; public IReadOnlyList<NVorbis.Contracts.IPacketProvider> GetStreams() { List<NVorbis.Contracts.IPacketProvider> list = new List<NVorbis.Contracts.IPacketProvider>(_packetProviders.Count); for (int i = 0; i < _packetProviders.Count; i++) { if (_packetProviders[i].TryGetTarget(out var target)) { list.Add(target); continue; } list.RemoveAt(i); i--; } return list; } public ContainerReader(Stream stream, bool closeOnDispose) { if (stream == null) { throw new ArgumentNullException("stream"); } _packetProviders = new List<WeakReference<NVorbis.Contracts.IPacketProvider>>(); if (stream.CanSeek) { _reader = CreatePageReader(stream, closeOnDispose, ProcessNewStream); CanSeek = true; } else { _reader = CreateForwardOnlyPageReader(stream, closeOnDispose, ProcessNewStream); } } public bool TryInit() { return FindNextStream(); } public bool FindNextStream() { _reader.Lock(); try { _foundStream = false; while (_reader.ReadNextPage()) { if (_foundStream) { return true; } } return false; } finally { _reader.Release(); } } private bool ProcessNewStream(NVorbis.Contracts.IPacketProvider packetProvider) { bool flag = _reader.Release(); try { NewStreamHandler newStreamCallback = NewStreamCallback; if (newStreamCallback == null || newStreamCallback(packetProvider)) { _packetProviders.Add(new WeakReference<NVorbis.Contracts.IPacketProvider>(packetProvider)); _foundStream = true; return true; } return false; } finally { if (flag) { _reader.Lock(); } } } public void Dispose() { _reader?.Dispose(); _reader = null; } } internal class Crc : ICrc { private const uint CRC32_POLY = 79764919u; private static readonly uint[] s_crcTable; private uint _crc; static Crc() { s_crcTable = new uint[256]; for (uint num = 0u; num < 256; num++) { uint num2 = num << 24; for (int i = 0; i < 8; i++) { num2 = (num2 << 1) ^ ((num2 >= 2147483648u) ? 79764919u : 0u); } s_crcTable[num] = num2; } } public Crc() { Reset(); } public void Reset() { _crc = 0u; } public void Update(int nextVal) { _crc = (_crc << 8) ^ s_crcTable[nextVal ^ (_crc >> 24)]; } public bool Test(uint checkCrc) { return _crc == checkCrc; } } internal class ForwardOnlyPacketProvider : DataPacket, IForwardOnlyPacketProvider, NVorbis.Contracts.IPacketProvider { private int _lastSeqNo; private readonly Queue<(byte[] buf, bool isResync)> _pageQueue = new Queue<(byte[], bool)>(); private readonly IPageReader _reader; private byte[] _pageBuf; private int _packetIndex; private bool _isEndOfStream; private int _dataStart; private bool _lastWasPeek; private Memory<byte> _packetBuf; private int _dataIndex; public bool CanSeek => false; public int StreamSerial { get; } protected override int TotalBits => _packetBuf.Length * 8; public ForwardOnlyPacketProvider(IPageReader reader, int streamSerial) { _reader = reader; StreamSerial = streamSerial; _packetIndex = int.MaxValue; } public bool AddPage(byte[] buf, bool isResync) { if ((buf[5] & 2u) != 0) { if (_isEndOfStream) { return false; } isResync = true; _lastSeqNo = BitConverter.ToInt32(buf, 18); } else { int num = BitConverter.ToInt32(buf, 18); isResync |= num != _lastSeqNo + 1; _lastSeqNo = num; } int num2 = 0; for (int i = 0; i < buf[26]; i++) { num2 += buf[27 + i]; } if (num2 == 0) { return false; } _pageQueue.Enqueue((buf, isResync)); return true; } public void SetEndOfStream() { _isEndOfStream = true; } public IPacket GetNextPacket() { if (_packetBuf.Length > 0) { if (!_lastWasPeek) { throw new InvalidOperationException("Must call Done() on previous packet first."); } _lastWasPeek = false; return this; } _lastWasPeek = false; if (GetPacket()) { return this; } return null; } public IPacket PeekNextPacket() { if (_packetBuf.Length > 0) { if (!_lastWasPeek) { throw new InvalidOperationException("Must call Done() on previous packet first."); } return this; } _lastWasPeek = true; if (GetPacket()) { return this; } return null; } private bool GetPacket() { byte[] pageBuf; bool isResync; int packetIndex; bool isContinuation; bool isContinued; int dataStart; if (_pageBuf != null && _packetIndex < 27 + _pageBuf[26]) { pageBuf = _pageBuf; isResync = false; dataStart = _dataStart; packetIndex = _packetIndex; isContinuation = false; isContinued = pageBuf[26 + pageBuf[26]] == byte.MaxValue; } else if (!ReadNextPage(out pageBuf, out isResync, out dataStart, out packetIndex, out isContinuation, out isContinued)) { return false; } int num = dataStart; bool flag = packetIndex == 27; if (isContinuation && flag) { isResync = true; num += GetPacketLength(pageBuf, ref packetIndex); if (packetIndex == 27 + pageBuf[26]) { return GetPacket(); } } if (!flag) { num = 0; } int packetLength = GetPacketLength(pageBuf, ref packetIndex); Memory<byte> memory = new Memory<byte>(pageBuf, dataStart, packetLength); dataStart += packetLength; bool flag2 = packetIndex == 27 + pageBuf[26]; if (isContinued) { if (flag2) { flag2 = false; } else { int packetIndex2 = packetIndex; GetPacketLength(pageBuf, ref packetIndex2); flag2 = packetIndex2 == 27 + pageBuf[26]; } } bool flag3 = false; long? granulePosition = null; if (flag2) { granulePosition = BitConverter.ToInt64(pageBuf, 6); if ((pageBuf[5] & 4u) != 0 || (_isEndOfStream && _pageQueue.Count == 0)) { flag3 = true; } } else { while (isContinued && packetIndex == 27 + pageBuf[26] && ReadNextPage(out pageBuf, out isResync, out dataStart, out packetIndex, out isContinuation, out isContinued) && !isResync && isContinuation) { num += 27 + pageBuf[26]; Memory<byte> memory2 = memory; int packetLength2 = GetPacketLength(pageBuf, ref packetIndex); memory = new Memory<byte>(new byte[memory2.Length + packetLength2]); memory2.CopyTo(memory); new Memory<byte>(pageBuf, dataStart, packetLength2).CopyTo(memory.Slice(memory2.Length)); dataStart += packetLength2; } } base.IsResync = isResync; base.GranulePosition = granulePosition; base.IsEndOfStream = flag3; base.ContainerOverheadBits = num * 8; _pageBuf = pageBuf; _dataStart = dataStart; _packetIndex = packetIndex; _packetBuf = memory; _isEndOfStream |= flag3; Reset(); return true; } private bool ReadNextPage(out byte[] pageBuf, out bool isResync, out int dataStart, out int packetIndex, out bool isContinuation, out bool isContinued) { while (_pageQueue.Count == 0) { if (_isEndOfStream || !_reader.ReadNextPage()) { pageBuf = null; isResync = false; dataStart = 0; packetIndex = 0; isContinuation = false; isContinued = false; return false; } } (byte[], bool) tuple = _pageQueue.Dequeue(); pageBuf = tuple.Item1; isResync = tuple.Item2; dataStart = pageBuf[26] + 27; packetIndex = 27; isContinuation = (pageBuf[5] & 1) != 0; isContinued = pageBuf[26 + pageBuf[26]] == byte.MaxValue; return true; } private int GetPacketLength(byte[] pageBuf, ref int packetIndex) { int num = 0;