Decompiled source of BaconMemory v1.0.0

BepInEx/plugins/BaconMemory.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Text;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using TMPro;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BaconMemory")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("BaconMemory")]
[assembly: AssemblyTitle("BaconMemory")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "Code generated with Claude Code, Anthropic's AI coding agent")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 BaconMemory
{
	internal static class GameSounds
	{
		private const string SoundsKey = "BaconMemory.GameSounds";

		private static readonly Dictionary<int, AudioClip> Freed = new Dictionary<int, AudioClip>();

		private static readonly Dictionary<int, float> OneShotUntil = new Dictionary<int, float>();

		private static IDictionary _replacements;

		private static object _context;

		private static PropertyInfo _condition;

		private static PropertyInfo _parent;

		private static PropertyInfo _sounds;

		internal static long SavedNow
		{
			get
			{
				//IL_0027: Unknown result type (might be due to invalid IL or missing references)
				long num = 0L;
				foreach (AudioClip value in Freed.Values)
				{
					if ((Object)(object)value != (Object)null && (int)value.loadState == 0)
					{
						num += (long)value.samples * (long)value.channels * 2;
					}
				}
				return num;
			}
		}

		internal static void Apply(Harmony harmony)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			if (Settings.FreeVanillaSounds.Value)
			{
				FindSoundApi();
				MethodInfo methodInfo = AccessTools.Method(typeof(AudioSource), "PlayOneShot", new Type[2]
				{
					typeof(AudioClip),
					typeof(float)
				}, (Type[])null);
				harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(GameSounds), "NoteOneShot", (Type[])null)
				{
					priority = 800
				}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				SceneManager.sceneLoaded += delegate(Scene scene, LoadSceneMode _)
				{
					Free(((Scene)(ref scene)).name);
				};
			}
		}

		private static void NoteOneShot(AudioClip clip)
		{
			if (clip != null && (Object)(object)clip != (Object)null)
			{
				OneShotUntil[((Object)clip).GetInstanceID()] = Time.realtimeSinceStartup + clip.length;
			}
		}

		private static void Free(string scene)
		{
			//IL_0159: Unknown result type (might be due to invalid IL or missing references)
			//IL_016a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0170: Invalid comparison between Unknown and I4
			try
			{
				if (!(AppDomain.CurrentDomain.GetData("BaconMemory.GameSounds") is Dictionary<string, List<long>> dictionary))
				{
					return;
				}
				HashSet<string> hashSet = AlwaysSwapped();
				foreach (int item in (from p in Freed
					where (Object)(object)p.Value == (Object)null
					select p.Key).ToList())
				{
					Freed.Remove(item);
				}
				HashSet<int> hashSet2 = new HashSet<int>(from s in Object.FindObjectsOfType<AudioSource>()
					where s.isPlaying && (Object)(object)s.clip != (Object)null
					select ((Object)s.clip).GetInstanceID());
				float realtimeSinceStartup = Time.realtimeSinceStartup;
				float num = Settings.ShortestSeconds.Value;
				long num2 = 0L;
				int num3 = 0;
				AudioClip[] array = Resources.FindObjectsOfTypeAll<AudioClip>();
				foreach (AudioClip clip in array)
				{
					int instanceID = ((Object)clip).GetInstanceID();
					if (instanceID >= 0 && (int)clip.loadType == 0 && (int)clip.loadState == 2 && clip.preloadAudioData && !Freed.ContainsKey(instanceID) && (clip.length < num || hashSet.Contains(((Object)clip).name)) && dictionary.TryGetValue(((Object)clip).name, out var value) && value.Exists((long n) => Math.Abs(n - clip.samples) <= 2) && !hashSet2.Contains(instanceID) && (!OneShotUntil.TryGetValue(instanceID, out var value2) || !(value2 > realtimeSinceStartup)) && clip.UnloadAudioData())
					{
						Freed[instanceID] = clip;
						num2 += (long)clip.samples * (long)clip.channels * 2;
						num3++;
					}
				}
				if (num3 > 0)
				{
					Plugin.Log.LogInfo((object)$"Game sounds: freed {num3} of the game's sounds after {scene} loaded, {num2 >> 20} MB unpacked");
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Game sounds: freeing failed after " + scene + " loaded: " + ex.Message));
			}
		}

		private static void FindSoundApi()
		{
			Type type = AccessTools.TypeByName("loaforcsSoundAPI.SoundPacks.SoundPackDataHandler");
			Type type2 = AccessTools.TypeByName("loaforcsSoundAPI.SoundPacks.Data.Conditions.Conditional");
			Type type3 = AccessTools.TypeByName("loaforcsSoundAPI.SoundPacks.Data.SoundReplacementGroup");
			_replacements = ((type == null) ? null : (AccessTools.Field(type, "SoundReplacements")?.GetValue(null) as IDictionary));
			_context = AccessTools.Field(AccessTools.TypeByName("loaforcsSoundAPI.SoundPacks.Data.Conditions.DefaultConditionContext"), "DEFAULT")?.GetValue(null);
			_condition = ((type2 == null) ? null : AccessTools.Property(type2, "Condition"));
			_parent = ((type3 == null) ? null : AccessTools.Property(type3, "Parent"));
			_sounds = ((type3 == null) ? null : AccessTools.Property(type3, "Sounds"));
			if (type != null && (_replacements == null || _context == null || _condition == null || _parent == null || _sounds == null))
			{
				Plugin.Log.LogWarning((object)"Game sounds: this SoundAPI version's replacements weren't found, so only the game's short sounds are freed");
			}
		}

		private static HashSet<string> AlwaysSwapped()
		{
			HashSet<string> hashSet = new HashSet<string>();
			if (_replacements == null || _context == null || _condition == null || _parent == null || _sounds == null)
			{
				return hashSet;
			}
			foreach (DictionaryEntry replacement in _replacements)
			{
				if (!(replacement.Key is string item) || !(replacement.Value is IEnumerable enumerable))
				{
					continue;
				}
				foreach (object item2 in enumerable)
				{
					if (On(_parent.GetValue(item2)) && On(item2) && ((IEnumerable)_sounds.GetValue(item2)).Cast<object>().Any(On))
					{
						hashSet.Add(item);
						break;
					}
				}
			}
			return hashSet;
		}

		private static bool On(object conditional)
		{
			object obj = ((conditional == null) ? null : _condition.GetValue(conditional));
			if (obj == null)
			{
				return true;
			}
			if (obj.GetType().Name != "ConfigCondition")
			{
				return false;
			}
			object obj2 = AccessTools.Method(obj.GetType(), "Evaluate", (Type[])null, (Type[])null)?.Invoke(obj, new object[1] { _context });
			bool flag = default(bool);
			int num;
			if (obj2 is bool)
			{
				flag = (bool)obj2;
				num = 1;
			}
			else
			{
				num = 0;
			}
			return (byte)((uint)num & (flag ? 1u : 0u)) != 0;
		}
	}
	internal sealed class MenuCard : MonoBehaviour
	{
		private const string StatsKey = "BaconMemory.SoundBundles";

		private const string SoundsKey = "BaconMemory.ChangedSounds";

		private const int JobsTotal = 3;

		private const int JobsDone = 4;

		private const int State = 5;

		private const int SecondsLeft = 6;

		private const int Permille = 9;

		private const float Delay = 1.5f;

		private const float Shown = 12f;

		private const float FadeIn = 0.4f;

		private const float FadeOut = 0.8f;

		private const float Width = 600f;

		private const float Pad = 18f;

		private const float Frame = 2f;

		private const float Bar = 10f;

		private static readonly Color Orange = Color32.op_Implicit(new Color32((byte)254, (byte)106, (byte)2, byte.MaxValue));

		private static readonly Color TextOrange = Color32.op_Implicit(new Color32((byte)254, (byte)125, (byte)61, byte.MaxValue));

		private static readonly string[] Taglines = new string[5] { "Bacon fried, RAM spared.", "Crispy sounds, lean memory.", "Less RAM, same racket.", "The Company thanks you for your memory.", "Sizzling, not swapping." };

		private float _start = -1f;

		private float _end = float.MaxValue;

		private float _nextCount;

		private bool _working;

		private long _savedNow;

		private string _tagline;

		private CanvasGroup _group;

		private RectTransform _panel;

		private RectTransform _header;

		private RectTransform _barTrack;

		private RectTransform _barFill;

		private TextMeshProUGUI _title;

		private TextMeshProUGUI _body;

		private TextMeshProUGUI _foot;

		internal static void Create()
		{
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Expected O, but got Unknown
			if (!Settings.ShowMenuCard.Value)
			{
				return;
			}
			GameObject val = new GameObject("BaconMemory menu card")
			{
				hideFlags = (HideFlags)61
			};
			Object.DontDestroyOnLoad((Object)(object)val);
			MenuCard card = val.AddComponent<MenuCard>();
			((Behaviour)card).enabled = false;
			bool shown = false;
			SceneManager.sceneLoaded += delegate(Scene scene, LoadSceneMode _)
			{
				if (!shown && !(((Scene)(ref scene)).name != "MainMenu") && !((Object)(object)card == (Object)null))
				{
					shown = true;
					card.Begin();
				}
			};
		}

		private void Begin()
		{
			_tagline = Taglines[new Random().Next(Taglines.Length)];
			_savedNow = SavedNow();
			string text = Lines(Stats());
			if (text != null)
			{
				Build();
				_start = Time.realtimeSinceStartup + 1.5f;
				((Behaviour)this).enabled = true;
				Plugin.Log.LogInfo((object)("Menu card: " + text.Replace('\n', ' ')));
			}
		}

		private static long[] Stats()
		{
			return AppDomain.CurrentDomain.GetData("BaconMemory.SoundBundles") as long[];
		}

		private static long SavedNow()
		{
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Invalid comparison between Unknown and I4
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_0068: Invalid comparison between Unknown and I4
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_0092: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Invalid comparison between Unknown and I4
			long num = SoundStreaming.StreamedSaving + GameSounds.SavedNow;
			if (!(AppDomain.CurrentDomain.GetData("BaconMemory.ChangedSounds") is Dictionary<string, List<long[]>> { Count: not 0 } dictionary))
			{
				return num;
			}
			AudioClip[] array = Resources.FindObjectsOfTypeAll<AudioClip>();
			foreach (AudioClip clip in array)
			{
				bool flag = (int)clip.loadType == 1 && (int)clip.loadState == 2;
				bool flag2 = (int)clip.loadType == 0 && !clip.preloadAudioData && (int)clip.loadState == 0;
				if ((flag || flag2) && dictionary.TryGetValue(((Object)clip).name, out var value))
				{
					long[] array2 = value.FirstOrDefault((long[] s) => Math.Abs(s[0] - clip.samples) <= 2);
					if (array2 != null)
					{
						num += (flag ? Math.Max(0L, array2[1] - array2[2]) : array2[1]);
					}
				}
			}
			return num;
		}

		private string Lines(long[] stats)
		{
			List<string> list = new List<string>();
			long num = ((stats != null) ? stats[5] : 0);
			if (num >= 1 && num <= 3)
			{
				_working = true;
				string arg = ((num == 3) ? "paused while you're on a moon" : ((stats[6] > 0) ? ("about " + Duration(stats[6]) + " left") : "working out the time left"));
				list.Add($"Preparing mod sounds: {stats[4]} of {stats[3]}, {arg}.");
				list.Add("Full speed in this menu, slow while you play.");
			}
			else if (num == 4 && _working)
			{
				list.Add("Mod sounds ready.");
				list.Add("Moons use them now, the rest after a game restart.");
			}
			else
			{
				if ((Settings.KeepModSoundsCompressed.Value || Settings.LoadShortSoundsWhenPlayed.Value) && stats == null)
				{
					list.Add("The preloader isn't running, so mods' sounds and shaders stay as they are. Reinstall BaconMemory.");
				}
				if (_savedNow > 0)
				{
					list.Add("Saving about " + Size(_savedNow) + " of RAM on sounds right now.");
					if ((stats != null && Settings.LoadShortSoundsWhenPlayed.Value) || GameSounds.SavedNow > 0)
					{
						list.Add("Short sounds load the first time they play.");
					}
				}
				if (Shader.maximumChunksOverride > 0)
				{
					list.Add("Mods' shaders keep most of their variants packed.");
				}
			}
			if (list.Count != 0)
			{
				return string.Join("\n", list);
			}
			return null;
		}

		private static string Size(long bytes)
		{
			if (bytes < 1073741824)
			{
				return $"{bytes >> 20} MB";
			}
			return $"{(double)bytes / 1073741824.0:0.0} GB";
		}

		private static string Duration(long seconds)
		{
			if (seconds < 60)
			{
				return $"{seconds} s";
			}
			return $"{seconds / 60} min {seconds % 60} s";
		}

		private void Build()
		{
			//IL_001e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			//IL_0065: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0101: Unknown result type (might be due to invalid IL or missing references)
			//IL_012a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Unknown result type (might be due to invalid IL or missing references)
			//IL_0141: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_016b: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_019b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d7: Unknown result type (might be due to invalid IL or missing references)
			//IL_01f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0234: Unknown result type (might be due to invalid IL or missing references)
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_029e: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e3: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e8: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e9: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ef: Unknown result type (might be due to invalid IL or missing references)
			TMP_FontAsset font = MenuFont();
			GameObject val = new GameObject("Canvas", new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(((Component)this).transform, false);
			Canvas obj = val.AddComponent<Canvas>();
			obj.renderMode = (RenderMode)0;
			obj.sortingOrder = 30000;
			CanvasScaler obj2 = val.AddComponent<CanvasScaler>();
			obj2.uiScaleMode = (ScaleMode)1;
			obj2.referenceResolution = new Vector2(1920f, 1080f);
			obj2.matchWidthOrHeight = 1f;
			_group = val.AddComponent<CanvasGroup>();
			_group.blocksRaycasts = false;
			_group.interactable = false;
			_group.alpha = 0f;
			_panel = Box("Frame", val.transform, Orange);
			RectTransform panel = _panel;
			RectTransform panel2 = _panel;
			Vector2 val2 = default(Vector2);
			((Vector2)(ref val2))..ctor(0.5f, 0f);
			panel2.anchorMax = val2;
			panel.anchorMin = val2;
			_panel.pivot = new Vector2(0.5f, 0f);
			RectTransform val3 = Box("Panel", (Transform)(object)_panel, new Color(0.02f, 0.01f, 0.01f, 1f));
			val3.anchorMin = Vector2.zero;
			val3.anchorMax = Vector2.one;
			val3.offsetMin = new Vector2(2f, 2f);
			val3.offsetMax = new Vector2(-2f, -2f);
			_header = Box("Header", (Transform)(object)val3, Orange);
			_header.anchorMin = new Vector2(0f, 1f);
			_header.anchorMax = Vector2.one;
			_header.pivot = new Vector2(0.5f, 1f);
			_title = Text("Title", (Transform)(object)val3, font, Color.black, 24f, (FontStyles)1);
			_body = Text("Body", (Transform)(object)val3, font, TextOrange, 18f, (FontStyles)0);
			_foot = Text("Tagline", (Transform)(object)val3, font, new Color(TextOrange.r, TextOrange.g, TextOrange.b, 0.6f), 15f, (FontStyles)2);
			((TMP_Text)_title).text = "> BACONMEMORY";
			_barTrack = Box("Bar", (Transform)(object)val3, new Color(Orange.r, Orange.g, Orange.b, 0.2f));
			_barFill = Box("Fill", (Transform)(object)_barTrack, Orange);
			_barFill.anchorMin = Vector2.zero;
			_barFill.anchorMax = new Vector2(0f, 1f);
			val2 = (_barFill.offsetMin = (_barFill.offsetMax = Vector2.zero));
		}

		private static TMP_FontAsset MenuFont()
		{
			return (from t in Object.FindObjectsOfType<TextMeshProUGUI>()
				where (Object)(object)((TMP_Text)t).font != (Object)null && !string.IsNullOrEmpty(((TMP_Text)t).text)
				group t by ((TMP_Text)t).font into g
				orderby g.Count() descending
				select g).FirstOrDefault()?.Key;
		}

		private static RectTransform Box(string name, Transform parent, Color color)
		{
			//IL_0014: 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_0026: 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_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0044: Expected O, but got Unknown
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			Image obj = val.AddComponent<Image>();
			((Graphic)obj).color = color;
			((Graphic)obj).raycastTarget = false;
			return (RectTransform)val.transform;
		}

		private static TextMeshProUGUI Text(string name, Transform parent, TMP_FontAsset font, Color color, float size, FontStyles style)
		{
			//IL_0014: 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_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			GameObject val = new GameObject(name, new Type[1] { typeof(RectTransform) });
			val.transform.SetParent(parent, false);
			TextMeshProUGUI val2 = val.AddComponent<TextMeshProUGUI>();
			if ((Object)(object)font != (Object)null)
			{
				((TMP_Text)val2).font = font;
			}
			((Graphic)val2).color = color;
			((TMP_Text)val2).fontSize = size;
			((TMP_Text)val2).fontStyle = style;
			((TMP_Text)val2).enableWordWrapping = true;
			((Graphic)val2).raycastTarget = false;
			((TMP_Text)val2).alignment = (TextAlignmentOptions)257;
			return val2;
		}

		private void Update()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			float realtimeSinceStartup = Time.realtimeSinceStartup;
			Scene activeScene = SceneManager.GetActiveScene();
			bool flag = ((Scene)(ref activeScene)).name == "MainMenu";
			if (realtimeSinceStartup < _start || !flag)
			{
				if ((Object)(object)_group != (Object)null)
				{
					_group.alpha = 0f;
				}
				return;
			}
			long[] array = Stats();
			long num = ((array != null) ? array[5] : 0);
			bool flag2 = num >= 1 && num <= 3;
			if (flag2)
			{
				_end = float.MaxValue;
			}
			else if (_end == float.MaxValue)
			{
				_end = Math.Max(realtimeSinceStartup, _start + 0.4f) + 12f;
			}
			if (realtimeSinceStartup > _end)
			{
				_group.alpha = 0f;
				((Behaviour)this).enabled = false;
				return;
			}
			if (realtimeSinceStartup >= _nextCount)
			{
				_savedNow = SavedNow();
				_nextCount = realtimeSinceStartup + 2f;
			}
			string text = Lines(array);
			if (text != null)
			{
				if (((TMP_Text)_body).text != text)
				{
					((TMP_Text)_body).text = text;
				}
				if (((TMP_Text)_foot).text != _tagline)
				{
					((TMP_Text)_foot).text = _tagline;
				}
				Layout(flag2, (array != null) ? array[9] : 0);
				_group.alpha = Mathf.Clamp01(Mathf.Min((realtimeSinceStartup - _start) / 0.4f, (_end - realtimeSinceStartup) / 0.8f));
				_panel.anchoredPosition = new Vector2(0f, 28f - (1f - Mathf.Clamp01((realtimeSinceStartup - _start) / 0.4f)) * 16f);
			}
		}

		private void Layout(bool working, long permille)
		{
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Unknown result type (might be due to invalid IL or missing references)
			//IL_008a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			float num = 560f;
			float num2 = Place(_title, num, 6f) + 6f;
			_header.sizeDelta = new Vector2(0f, num2);
			num2 = Place(_body, num, num2 + 12f);
			((Component)_barTrack).gameObject.SetActive(working);
			if (working)
			{
				num2 += 10f;
				RectTransform barTrack = _barTrack;
				RectTransform barTrack2 = _barTrack;
				Vector2 val = default(Vector2);
				((Vector2)(ref val))..ctor(0f, 1f);
				barTrack2.anchorMax = val;
				barTrack.anchorMin = val;
				_barTrack.pivot = new Vector2(0f, 1f);
				_barTrack.anchoredPosition = new Vector2(18f, 0f - num2);
				_barTrack.sizeDelta = new Vector2(num, 10f);
				_barFill.anchorMax = new Vector2(Mathf.Clamp01((float)permille / 1000f), 1f);
				num2 += 10f;
			}
			num2 = Place(_foot, num, num2 + 8f) + 18f;
			_panel.sizeDelta = new Vector2(600f, num2 + 4f);
		}

		private static float Place(TextMeshProUGUI text, float width, float y)
		{
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: 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_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			float y2 = ((TMP_Text)text).GetPreferredValues(((TMP_Text)text).text, width, 0f).y;
			RectTransform rectTransform = ((TMP_Text)text).rectTransform;
			Vector2 val = default(Vector2);
			((Vector2)(ref val))..ctor(0f, 1f);
			rectTransform.anchorMax = val;
			rectTransform.anchorMin = val;
			rectTransform.pivot = new Vector2(0f, 1f);
			rectTransform.anchoredPosition = new Vector2(18f, 0f - y);
			rectTransform.sizeDelta = new Vector2(width, y2);
			return y + y2;
		}
	}
	[BepInPlugin("baconmemory", "BaconMemory", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		internal const string Guid = "baconmemory";

		internal const string Version = "1.0.0";

		internal static ManualLogSource Log;

		private void Awake()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			Log = ((BaseUnityPlugin)this).Logger;
			Settings.Bind(((BaseUnityPlugin)this).Config);
			Harmony val = new Harmony("baconmemory");
			try
			{
				SoundStreaming.Apply(val);
			}
			catch (Exception ex)
			{
				Log.LogWarning((object)("SoundStreaming failed: " + ex.Message));
			}
			try
			{
				GameSounds.Apply(val);
			}
			catch (Exception ex2)
			{
				Log.LogWarning((object)("Freeing the game's sounds failed: " + ex2.Message));
			}
			try
			{
				MenuCard.Create();
			}
			catch (Exception ex3)
			{
				Log.LogWarning((object)("Menu card failed: " + ex3.Message));
			}
			Log.LogInfo((object)"BaconMemory 1.0.0 loaded");
		}
	}
	internal static class Settings
	{
		internal const string Section = "Sounds";

		internal static ConfigEntry<bool> StreamSoundApi;

		internal static ConfigEntry<bool> KeepModSoundsCompressed;

		internal static ConfigEntry<bool> LoadShortSoundsWhenPlayed;

		internal static ConfigEntry<bool> FreeVanillaSounds;

		internal static ConfigEntry<bool> UnpackShadersAsNeeded;

		internal static ConfigEntry<bool> ShowMenuCard;

		internal static ConfigEntry<int> ShortestSeconds;

		internal static ConfigEntry<string> LeaveAlone;

		internal static void Bind(ConfigFile config)
		{
			//IL_008e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Expected O, but got Unknown
			StreamSoundApi = config.Bind<bool>("Sounds", "Stream long SoundAPI sounds", true, "Sounds of sound packs for SoundAPI, such as LethalResonance, stream from their files while they play instead of being unpacked into RAM when the game starts. Saves about 400 MB with LethalResonance and halves its loading time. Takes effect the next time the game starts.");
			KeepModSoundsCompressed = config.Bind<bool>("Sounds", "Keep long mod sounds compressed", true, "Long sounds inside mods' asset bundles, such as Wesley's moons and interiors, stay compressed in RAM and are unpacked bit by bit while they play, instead of being unpacked whole the first time they play and kept. Mod files are never changed: a bundle with sounds to change gets a copy in BepInEx\\cache\\BaconMemory, made in the background once the main menu is up (at full speed in the menu, slowly while you play, never during a round), and the copy loads instead: right away for moons, and after a game restart for the rest. The main menu card shows the progress. Bundles a mod loads with a CRC check are left alone. Changing it makes the copies again at the next start; with the next setting off too, it deletes them.");
			LoadShortSoundsWhenPlayed = config.Bind<bool>("Sounds", "Load short mod sounds when they first play", true, "Short sounds in LethalLevelLoader's bundles (.lethalbundle files, such as Wesley's moons and interiors) load the first time they play instead of when the game starts, so the sounds of moons and interiors you haven't been to yet stay out of RAM. Each one starts 10 to 30 ms late the first time it plays. Only .lethalbundle files, which LethalLevelLoader keeps loaded the whole game: a sound loads from its bundle, so it can't once a mod has unloaded that. Uses the same copies as the setting above. Changing it makes the copies again at the next start.");
			FreeVanillaSounds = config.Bind<bool>("Sounds", "Free vanilla sounds until they play", true, "The game's own sounds load with the scenes that use them and are unpacked into RAM, and the game's files must stay as they are, so they can't get copies. With this on, each time a scene loads, BaconMemory frees the unpacked data of the game's sounds that a SoundAPI pack, such as LethalResonance, always replaces, and of its short sounds. Unity loads a freed sound back the first time it plays, which holds the game for about 1 to 4 ms for a short sound. A sound that has played stays loaded. Long sounds that no pack replaces stay as they are. Takes effect the next time the game starts.");
			ShortestSeconds = config.Bind<int>("Sounds", "Shortest long sound (seconds)", 10, new ConfigDescription("Sounds at least this long are streamed or kept compressed. Shorter ones are unpacked, so they cost no CPU while they play. Changing it makes the copies again at the next start.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(3, 600), Array.Empty<object>()));
			LeaveAlone = config.Bind<string>("Sounds", "Bundles to leave alone", "", "File names of asset bundles whose sounds stay as they are, separated by commas. For a mod that reads its long sounds' samples, which compressed sounds don't allow. Takes effect the next time the game starts.");
			UnpackShadersAsNeeded = config.Bind<bool>("Shaders", "Unpack shaders as needed", true, "Unity unpacks every variant of a shader when it loads, even the ones the game never draws, and mods' shaders hold thousands. With this on, a mod's shader keeps at most two chunks of its variants unpacked at once, and unpacks another when the game needs a variant in it. That saved about 600 MB at the main menu in The Company Adventures. Unpacking a chunk takes a moment, so a variant the game hasn't drawn yet can take a moment longer. The game's own shaders load before BaconMemory and stay as they are. Takes effect the next time the game starts.");
			ShowMenuCard = config.Bind<bool>("Menu", "Show the main menu card", true, "Shows in the main menu how far along the copies are while they're made, and otherwise for a few seconds what BaconMemory does and how much RAM it saves on sounds right now.");
		}
	}
	internal static class SoundStreaming
	{
		private sealed class Sound
		{
			public string Path;

			public AudioType Type;

			public AudioClip Clip;

			public AudioSource Owner;

			public readonly Dictionary<AudioSource, Copy> Copies = new Dictionary<AudioSource, Copy>();
		}

		private sealed class Copy
		{
			public UnityWebRequest Request;

			public AudioClip Clip;
		}

		internal const string SoundApiGuid = "me.loaforc.soundapi";

		private const int CopyWaitMs = 250;

		private static readonly object Lock = new object();

		private static readonly Dictionary<UnityWebRequest, (string path, AudioType type)> Streaming = new Dictionary<UnityWebRequest, (string, AudioType)>();

		private static readonly List<UnityWebRequest> Kept = new List<UnityWebRequest>();

		private static readonly List<Sound> Made = new List<Sound>();

		private static volatile bool _madeAny;

		private static int _total;

		private static long _savedBytes;

		private static readonly Dictionary<AudioClip, Sound> Sounds = new Dictionary<AudioClip, Sound>();

		private static readonly List<UnityWebRequest> Retired = new List<UnityWebRequest>();

		private static double _minSeconds;

		private static bool _waitWorks = true;

		private static int _copies;

		private static PropertyInfo _sourceOf;

		private static MethodInfo _updatesEveryFrame;

		private static bool _warned;

		internal static int StreamedSounds
		{
			get
			{
				lock (Lock)
				{
					return _total;
				}
			}
		}

		internal static long StreamedSaving
		{
			get
			{
				lock (Lock)
				{
					return _savedBytes;
				}
			}
		}

		private static MethodInfo GetAudioClipMethod => AccessTools.Method(typeof(UnityWebRequestMultimedia), "GetAudioClip", new Type[2]
		{
			typeof(string),
			typeof(AudioType)
		}, (Type[])null);

		private static MethodInfo GetContentMethod => AccessTools.Method(typeof(DownloadHandlerAudioClip), "GetContent", (Type[])null, (Type[])null);

		private static MethodInfo DisposeMethod => AccessTools.Method(typeof(UnityWebRequest), "Dispose", (Type[])null, (Type[])null);

		internal static void Apply(Harmony h)
		{
			//IL_01ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b7: Expected O, but got Unknown
			//IL_01cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_01d9: Expected O, but got Unknown
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fb: Expected O, but got Unknown
			//IL_020f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0216: Expected O, but got Unknown
			if (!Settings.StreamSoundApi.Value)
			{
				return;
			}
			_minSeconds = Settings.ShortestSeconds.Value;
			if (!Chainloader.PluginInfos.TryGetValue("me.loaforc.soundapi", out var value) || value.Instance == null)
			{
				return;
			}
			Assembly assembly = ((object)value.Instance).GetType().Assembly;
			Type type = assembly.GetType("loaforcsSoundAPI.SoundPacks.SoundPackLoadPipeline", throwOnError: false);
			Type type2 = assembly.GetType("loaforcsSoundAPI.Core.AudioSourceAdditionalData", throwOnError: false);
			MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "StartWebRequestOperation", (Type[])null, (Type[])null));
			MethodInfo methodInfo2 = ((type == null) ? null : FindClipMaker(type));
			MethodInfo methodInfo3 = ((type2 == null) ? null : AccessTools.PropertySetter(type2, "RealClip"));
			_sourceOf = ((type2 == null) ? null : AccessTools.Property(type2, "Source"));
			_updatesEveryFrame = ((type2 == null) ? null : AccessTools.Method(type2, "RequiresUpdateFunction", (Type[])null, (Type[])null));
			MethodInfo methodInfo4 = AccessTools.Method(typeof(AudioSource), "PlayOneShot", new Type[2]
			{
				typeof(AudioClip),
				typeof(float)
			}, (Type[])null);
			if (methodInfo == null || methodInfo2 == null || methodInfo3 == null || _sourceOf == null || _updatesEveryFrame == null || methodInfo4 == null || !Instructions(methodInfo).Any((CodeInstruction i) => CodeInstructionExtensions.Calls(i, GetAudioClipMethod)))
			{
				Plugin.Log.LogWarning((object)"SoundStreaming: SoundAPI layout changed, skipping");
				return;
			}
			h.Patch((MethodBase)methodInfo, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(SoundStreaming), "StreamLongSounds", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
			h.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(SoundStreaming), "KeepStreamedRequests", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null);
			h.Patch((MethodBase)methodInfo3, new HarmonyMethod(typeof(SoundStreaming), "OwnCopyForPlay", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			HarmonyMethod val = new HarmonyMethod(typeof(SoundStreaming), "OwnCopyForOneShot", (Type[])null);
			val.priority = 200;
			val.after = new string[1] { "me.loaforc.soundapi" };
			h.Patch((MethodBase)methodInfo4, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			SceneManager.sceneUnloaded += delegate
			{
				FreeCopies();
			};
		}

		private static MethodInfo FindClipMaker(Type pipeline)
		{
			Stack<Type> stack = new Stack<Type>(pipeline.GetNestedTypes(AccessTools.all));
			while (stack.Count > 0)
			{
				Type type = stack.Pop();
				Type[] nestedTypes = type.GetNestedTypes(AccessTools.all);
				foreach (Type item in nestedTypes)
				{
					stack.Push(item);
				}
				foreach (MethodInfo declaredMethod in AccessTools.GetDeclaredMethods(type))
				{
					if (declaredMethod.GetMethodBody() != null)
					{
						List<CodeInstruction> source = Instructions(declaredMethod);
						if (source.Any((CodeInstruction val) => CodeInstructionExtensions.Calls(val, GetContentMethod)) && source.Any((CodeInstruction val) => CodeInstructionExtensions.Calls(val, DisposeMethod)))
						{
							return declaredMethod;
						}
					}
				}
			}
			return null;
		}

		private static List<CodeInstruction> Instructions(MethodBase method)
		{
			return (List<CodeInstruction>)typeof(PatchProcessor).GetMethods().First((MethodInfo m) => m.Name == "GetOriginalInstructions" && m.GetParameters().Length == 2 && !m.GetParameters()[1].ParameterType.IsByRef).Invoke(null, new object[2] { method, null });
		}

		private static IEnumerable<CodeInstruction> StreamLongSounds(IEnumerable<CodeInstruction> code)
		{
			MethodInfo getAudioClip = GetAudioClipMethod;
			foreach (CodeInstruction item in code)
			{
				if (CodeInstructionExtensions.Calls(item, getAudioClip))
				{
					item.operand = AccessTools.Method(typeof(SoundStreaming), "GetAudioClip", (Type[])null, (Type[])null);
				}
				yield return item;
			}
		}

		private static IEnumerable<CodeInstruction> KeepStreamedRequests(IEnumerable<CodeInstruction> code)
		{
			MethodInfo getContent = GetContentMethod;
			MethodInfo dispose = DisposeMethod;
			foreach (CodeInstruction item in code)
			{
				if (CodeInstructionExtensions.Calls(item, getContent))
				{
					item.opcode = OpCodes.Call;
					item.operand = AccessTools.Method(typeof(SoundStreaming), "GetContent", (Type[])null, (Type[])null);
				}
				else if (CodeInstructionExtensions.Calls(item, dispose))
				{
					item.opcode = OpCodes.Call;
					item.operand = AccessTools.Method(typeof(SoundStreaming), "DisposeUnlessStreamed", (Type[])null, (Type[])null);
				}
				yield return item;
			}
		}

		private static UnityWebRequest GetAudioClip(string path, AudioType type)
		{
			//IL_0001: 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_000b: Invalid comparison between Unknown and I4
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			UnityWebRequest audioClip = UnityWebRequestMultimedia.GetAudioClip(path, type);
			try
			{
				if ((int)type == 14 && TryReadOgg(path, out var seconds, out var unpacked) && seconds >= _minSeconds)
				{
					((DownloadHandlerAudioClip)audioClip.downloadHandler).streamAudio = true;
					long num = unpacked - new FileInfo(path).Length;
					lock (Lock)
					{
						Streaming[audioClip] = (path, type);
						_total++;
						_savedBytes += num;
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("SoundStreaming: " + Path.GetFileName(path) + ": " + ex.Message));
			}
			return audioClip;
		}

		private static AudioClip GetContent(UnityWebRequest request)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			AudioClip content = DownloadHandlerAudioClip.GetContent(request);
			lock (Lock)
			{
				if (Streaming.TryGetValue(request, out (string, AudioType) value))
				{
					Made.Add(new Sound
					{
						Path = value.Item1,
						Type = value.Item2,
						Clip = content
					});
					_madeAny = true;
				}
			}
			return content;
		}

		private static void DisposeUnlessStreamed(UnityWebRequest request)
		{
			lock (Lock)
			{
				if (Streaming.Remove(request))
				{
					Kept.Add(request);
					if (Kept.Count == _total)
					{
						Plugin.Log.LogInfo((object)$"SoundStreaming: {_total} long sounds stream from their files instead of being unpacked, about {_savedBytes >> 20} MB less RAM");
					}
					return;
				}
			}
			request.Dispose();
		}

		private static void OwnCopyForPlay(object __instance, ref AudioClip value)
		{
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Expected O, but got Unknown
			if (value == null)
			{
				return;
			}
			CollectMade();
			if (!Sounds.TryGetValue(value, out var value2))
			{
				return;
			}
			try
			{
				if (!(bool)_updatesEveryFrame.Invoke(__instance, null))
				{
					value = ClipFor(value2, (AudioSource)_sourceOf.GetValue(__instance));
				}
			}
			catch (Exception e)
			{
				WarnOnce(e);
			}
		}

		private static void OwnCopyForOneShot(AudioSource __instance, ref AudioClip clip)
		{
			if (clip == null)
			{
				return;
			}
			CollectMade();
			if (!Sounds.TryGetValue(clip, out var value))
			{
				return;
			}
			try
			{
				clip = ClipFor(value, __instance);
			}
			catch (Exception e)
			{
				WarnOnce(e);
			}
		}

		private static void CollectMade()
		{
			if (!_madeAny)
			{
				return;
			}
			lock (Lock)
			{
				foreach (Sound item in Made)
				{
					if ((Object)(object)item.Clip != (Object)null)
					{
						Sounds[item.Clip] = item;
					}
				}
				Made.Clear();
				_madeAny = false;
			}
		}

		private static AudioClip ClipFor(Sound sound, AudioSource source)
		{
			if ((Object)(object)source == (Object)null)
			{
				return sound.Clip;
			}
			if ((Object)(object)sound.Owner == (Object)null || (Object)(object)sound.Owner == (Object)(object)source)
			{
				sound.Owner = source;
				return sound.Clip;
			}
			if (sound.Copies.TryGetValue(source, out var value))
			{
				if ((Object)(object)value.Clip != (Object)null)
				{
					((Object)value.Clip).name = ((Object)sound.Clip).name;
					return value.Clip;
				}
				if (!value.Request.isDone)
				{
					return sound.Clip;
				}
				sound.Copies.Remove(source);
			}
			value = MakeCopy(sound, source);
			if ((Object)(object)value?.Clip == (Object)null)
			{
				return sound.Clip;
			}
			((Object)value.Clip).name = ((Object)sound.Clip).name;
			return value.Clip;
		}

		private static Copy MakeCopy(Sound sound, AudioSource source)
		{
			//IL_0025: 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)
			UnityWebRequest audioClip = UnityWebRequestMultimedia.GetAudioClip(sound.Path, sound.Type);
			((DownloadHandlerAudioClip)audioClip.downloadHandler).streamAudio = true;
			UnityWebRequestAsyncOperation val = audioClip.SendWebRequest();
			Copy copy = new Copy
			{
				Request = audioClip
			};
			sound.Copies[source] = copy;
			if (_waitWorks)
			{
				Stopwatch stopwatch = Stopwatch.StartNew();
				while (!audioClip.isDone && stopwatch.ElapsedMilliseconds < 250)
				{
					Thread.Sleep(0);
				}
				if (audioClip.isDone)
				{
					Finish(sound, source, copy);
					return copy;
				}
				_waitWorks = false;
				Plugin.Log.LogWarning((object)$"SoundStreaming: a copy of a long sound wasn't ready after {250} ms, so copies are made in the background from now on");
			}
			((AsyncOperation)val).completed += delegate
			{
				Finish(sound, source, copy);
			};
			return copy;
		}

		private static void Finish(Sound sound, AudioSource source, Copy copy)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Invalid comparison between Unknown and I4
			if ((Object)(object)copy.Clip != (Object)null)
			{
				return;
			}
			AudioClip val = (((int)copy.Request.result == 1) ? DownloadHandlerAudioClip.GetContent(copy.Request) : null);
			Copy value;
			bool flag = sound.Copies.TryGetValue(source, out value) && value == copy;
			if ((Object)(object)val == (Object)null || (Object)(object)source == (Object)null || !flag)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
				Retired.Add(copy.Request);
				if (flag)
				{
					sound.Copies.Remove(source);
				}
			}
			else
			{
				copy.Clip = val;
				if (++_copies == 1)
				{
					Plugin.Log.LogInfo((object)("SoundStreaming: a long sound plays on more than one AudioSource ('" + ((Object)sound.Clip).name + "'), so those get copies of their own"));
				}
			}
		}

		private static void FreeCopies()
		{
			foreach (UnityWebRequest item in Retired)
			{
				item.Dispose();
			}
			Retired.Clear();
			foreach (Sound value in Sounds.Values)
			{
				if (value.Copies.Count == 0)
				{
					continue;
				}
				foreach (AudioSource item2 in value.Copies.Keys.Where((AudioSource s) => (Object)(object)s == (Object)null).ToList())
				{
					Copy copy = value.Copies[item2];
					value.Copies.Remove(item2);
					if ((Object)(object)copy.Clip != (Object)null)
					{
						Object.Destroy((Object)(object)copy.Clip);
					}
					if (copy.Request.isDone)
					{
						Retired.Add(copy.Request);
					}
				}
			}
		}

		private static void WarnOnce(Exception e)
		{
			if (!_warned)
			{
				_warned = true;
				Plugin.Log.LogWarning((object)("SoundStreaming: couldn't give an AudioSource its own copy, it shares the clip: " + e.Message));
			}
		}

		private static bool TryReadOgg(string path, out double seconds, out long unpacked)
		{
			seconds = 0.0;
			unpacked = 0L;
			using (FileStream fileStream = File.OpenRead(path))
			{
				byte[] array = new byte[Math.Min(fileStream.Length, 512L)];
				if (array.Length < 58 || !ReadFully(fileStream, array) || !IsPage(array, 0))
				{
					return false;
				}
				int num = 27 + array[26];
				if (num + 16 > array.Length || array[num] != 1 || Encoding.ASCII.GetString(array, num + 1, 6) != "vorbis")
				{
					return false;
				}
				int num2 = array[num + 11];
				int num3 = BitConverter.ToInt32(array, num + 12);
				byte[] array2 = new byte[Math.Min(fileStream.Length, 65536L)];
				fileStream.Seek(-array2.Length, SeekOrigin.End);
				if (!ReadFully(fileStream, array2))
				{
					return false;
				}
				for (int num4 = array2.Length - 14; num4 >= 0; num4--)
				{
					if (IsPage(array2, num4))
					{
						long num5 = BitConverter.ToInt64(array2, num4 + 6);
						if (num3 <= 0 || num5 <= 0)
						{
							return false;
						}
						seconds = (double)num5 / (double)num3;
						unpacked = num5 * num2 * 2;
						return true;
					}
				}
			}
			return false;
		}

		private static bool ReadFully(Stream stream, byte[] buffer)
		{
			int num;
			for (int i = 0; i < buffer.Length; i += num)
			{
				num = stream.Read(buffer, i, buffer.Length - i);
				if (num <= 0)
				{
					return false;
				}
			}
			return true;
		}

		private static bool IsPage(byte[] b, int i)
		{
			if (b[i] == 79 && b[i + 1] == 103 && b[i + 2] == 103)
			{
				return b[i + 3] == 83;
			}
			return false;
		}
	}
}

BepInEx/patchers/BaconMemory.Preloader.dll

Decompiled 2 days ago
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.Text;
using System.Text.RegularExpressions;
using System.Threading;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mono.Cecil;
using UnityEngine;
using UnityEngine.SceneManagement;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("BaconMemory.Preloader")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0")]
[assembly: AssemblyProduct("BaconMemory.Preloader")]
[assembly: AssemblyTitle("BaconMemory.Preloader")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "Code generated with Claude Code, Anthropic's AI coding agent")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 BaconMemory.Preloader
{
	internal sealed class BundleFile : IDisposable
	{
		internal struct Block
		{
			public uint Size;

			public uint StoredSize;

			public ushort Flags;

			public long Offset;

			public long FileOffset;

			public int Compression => Flags & 0x3F;
		}

		internal struct Node
		{
			public long Offset;

			public long Size;

			public uint Flags;

			public string Name;
		}

		private const uint CompressionMask = 63u;

		private const uint InfoAtEnd = 128u;

		private const uint InfoCombined = 64u;

		private const uint NewPadding = 512u;

		private const uint NewEncryption = 1024u;

		private const uint OldEncryption = 512u;

		private const uint OldWebPlugin = 256u;

		private const int CopyBlockSize = 131072;

		private const int MaxPackedBlock = 16777216;

		public readonly uint Version;

		public readonly string UnityVersion;

		public readonly string UnityRevision;

		public readonly uint Flags;

		public readonly bool Padding;

		public readonly Block[] Blocks;

		public readonly Node[] Nodes;

		public readonly long DataLength;

		public readonly bool HasLzma;

		private readonly FileStream _file;

		private int _cached = -1;

		private byte[] _unpacked;

		private byte[] _stored;

		private IntPtr _prefix;

		private long _prefixLength;

		private byte[] _pending = new byte[0];

		private int _pendingStart;

		private int _pendingCount;

		private long _producedTo;

		private int _produceBlock;

		private Lzma _lzma;

		private Action<long> _progress;

		public long SerializedEnd
		{
			get
			{
				long num = 0L;
				Node[] nodes = Nodes;
				for (int i = 0; i < nodes.Length; i++)
				{
					Node node = nodes[i];
					if ((node.Flags & 4) != 0)
					{
						num = Math.Max(num, node.Offset + node.Size);
					}
				}
				return num;
			}
		}

		public BundleFile(string path)
		{
			_file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 65536);
			try
			{
				byte[] array = new byte[Math.Min(_file.Length, 1024L)];
				ReadFile(0L, array, 0, array.Length);
				int p = 0;
				if (CString(array, ref p) != "UnityFS")
				{
					throw new InvalidDataException("not a UnityFS bundle");
				}
				Version = U32(array, ref p);
				UnityVersion = CString(array, ref p);
				UnityRevision = CString(array, ref p);
				I64(array, ref p);
				uint num = U32(array, ref p);
				uint num2 = U32(array, ref p);
				Flags = U32(array, ref p);
				bool flag = UsesNewFlags(UnityRevision);
				if ((Flags & 0x100) != 0 || (Flags & (uint)(flag ? 1024 : 512)) != 0)
				{
					throw new InvalidDataException("encrypted bundle");
				}
				Padding = flag && (Flags & 0x200) != 0;
				long num3 = p;
				if (Version >= 7)
				{
					num3 = Align16(num3);
				}
				long num4;
				long num5;
				if ((Flags & 0x80) != 0)
				{
					num4 = _file.Length - num;
					num5 = num3;
				}
				else
				{
					num4 = num3;
					num5 = num3 + num;
				}
				if (Padding)
				{
					num5 = Align16(num5);
				}
				if (num > 16777216 || num2 > 16777216 || num4 < 0 || num4 + num > _file.Length)
				{
					throw new InvalidDataException("bad header");
				}
				byte[] array2 = new byte[num];
				ReadFile(num4, array2, 0, array2.Length);
				byte[] b = Unpack(array2, (int)num, (int)(Flags & 0x3F), (int)num2);
				int p2 = 16;
				int num6 = I32(b, ref p2);
				if (num6 < 0 || num6 > 1048576)
				{
					throw new InvalidDataException("bad block count");
				}
				Blocks = new Block[num6];
				long num7 = 0L;
				long num8 = num5;
				for (int i = 0; i < num6; i++)
				{
					Block block = new Block
					{
						Size = U32(b, ref p2),
						StoredSize = U32(b, ref p2),
						Flags = U16(b, ref p2),
						Offset = num7,
						FileOffset = num8
					};
					if (block.Compression > 3 || (block.Compression >= 2 && block.Size > 16777216) || (block.Compression == 0 && block.Size != block.StoredSize))
					{
						throw new InvalidDataException("bad block");
					}
					if (block.Compression == 1)
					{
						HasLzma = true;
					}
					Blocks[i] = block;
					num7 += block.Size;
					num8 += block.StoredSize;
				}
				DataLength = num7;
				if (num8 > (((Flags & 0x80) != 0) ? num4 : _file.Length))
				{
					throw new InvalidDataException("blocks run past the end of the file");
				}
				int num9 = I32(b, ref p2);
				if (num9 < 0 || num9 > 65536)
				{
					throw new InvalidDataException("bad file count");
				}
				Nodes = new Node[num9];
				for (int j = 0; j < num9; j++)
				{
					Node node = new Node
					{
						Offset = I64(b, ref p2),
						Size = I64(b, ref p2),
						Flags = U32(b, ref p2),
						Name = CString(b, ref p2)
					};
					if (node.Offset < 0 || node.Size < 0 || node.Offset + node.Size > DataLength)
					{
						throw new InvalidDataException("bad file entry");
					}
					Nodes[j] = node;
				}
			}
			catch
			{
				_file.Dispose();
				throw;
			}
		}

		public void Dispose()
		{
			_file.Dispose();
			if (_prefix != IntPtr.Zero)
			{
				Marshal.FreeHGlobal(_prefix);
			}
			_prefix = IntPtr.Zero;
		}

		public void UnpackPrefix(long upTo, Action<long> progress)
		{
			if (!HasLzma || _prefix != IntPtr.Zero)
			{
				throw new InvalidOperationException("only once, for an LZMA bundle");
			}
			_progress = progress;
			_prefixLength = Math.Min(upTo, DataLength);
			_prefix = Marshal.AllocHGlobal((IntPtr)Math.Max(1L, _prefixLength));
			Produce(_prefixLength);
		}

		private void Produce(long upTo)
		{
			byte[] array = null;
			while (_producedTo < upTo && _produceBlock < Blocks.Length)
			{
				Block block = Blocks[_produceBlock];
				if (block.Compression == 1)
				{
					if (_lzma == null)
					{
						_file.Position = block.FileOffset;
						_lzma = new Lzma(_file, block.StoredSize, Accept, block.Size, _progress);
					}
					long num = Math.Min(block.Size, upTo - block.Offset);
					_lzma.DecodeTo(num);
					if (_lzma.Done < num)
					{
						throw new InvalidDataException("LZMA data ended early");
					}
					if (_lzma.Done < block.Size)
					{
						break;
					}
					_lzma = null;
				}
				else
				{
					array = array ?? new byte[131072];
					int num3;
					for (long num2 = _producedTo - block.Offset; num2 < block.Size; num2 += num3)
					{
						num3 = (int)Math.Min(array.Length, block.Size - num2);
						ReadBlocks(block.Offset + num2, array, 0, num3);
						Accept(array, 0, num3);
						_progress?.Invoke(num3);
					}
				}
				_produceBlock++;
			}
		}

		private void Accept(byte[] data, int index, int count)
		{
			if (_producedTo < _prefixLength)
			{
				int num = (int)Math.Min(count, _prefixLength - _producedTo);
				Marshal.Copy(data, index, (IntPtr)(_prefix.ToInt64() + _producedTo), num);
				_producedTo += num;
				index += num;
				count -= num;
			}
			if (count == 0)
			{
				return;
			}
			if (_pendingStart + _pendingCount + count > _pending.Length)
			{
				if (_pendingCount + count > _pending.Length)
				{
					Array.Resize(ref _pending, Math.Max(_pendingCount + count, 1048576));
				}
				Buffer.BlockCopy(_pending, _pendingStart, _pending, 0, _pendingCount);
				_pendingStart = 0;
			}
			Buffer.BlockCopy(data, index, _pending, _pendingStart + _pendingCount, count);
			_pendingCount += count;
			_producedTo += count;
		}

		public void Read(long offset, byte[] buffer, int index, int count)
		{
			if (offset < 0 || count < 0 || offset + count > DataLength)
			{
				throw new InvalidDataException("read past the end of the data");
			}
			if (!HasLzma)
			{
				ReadBlocks(offset, buffer, index, count);
				return;
			}
			if (offset + count > _prefixLength)
			{
				throw new InvalidOperationException("read past the unpacked part");
			}
			Marshal.Copy((IntPtr)(_prefix.ToInt64() + offset), buffer, index, count);
		}

		private void Next(long offset, byte[] buffer, int count)
		{
			if (!HasLzma)
			{
				ReadBlocks(offset, buffer, 0, count);
				return;
			}
			int dstOffset = 0;
			if (offset < _prefixLength)
			{
				int num = (int)Math.Min(count, _prefixLength - offset);
				Marshal.Copy((IntPtr)(_prefix.ToInt64() + offset), buffer, 0, num);
				dstOffset = num;
				offset += num;
				count -= num;
			}
			if (count != 0)
			{
				if (offset != _producedTo - _pendingCount)
				{
					throw new InvalidOperationException("the data stream has to be read in order");
				}
				if (_pendingCount < count)
				{
					Produce(Math.Min(DataLength, offset + Math.Max(count, 1048576)));
				}
				if (_pendingCount < count)
				{
					throw new InvalidDataException("data ended early");
				}
				Buffer.BlockCopy(_pending, _pendingStart, buffer, dstOffset, count);
				_pendingStart += count;
				_pendingCount -= count;
			}
		}

		private void ReadBlocks(long offset, byte[] buffer, int index, int count)
		{
			while (count > 0)
			{
				int num = FindBlock(offset);
				Block block = Blocks[num];
				long num2 = offset - block.Offset;
				int num3 = (int)Math.Min(count, block.Size - num2);
				if (block.Compression == 0)
				{
					ReadFile(block.FileOffset + num2, buffer, index, num3);
				}
				else
				{
					Buffer.BlockCopy(Unpacked(num), (int)num2, buffer, index, num3);
				}
				offset += num3;
				index += num3;
				count -= num3;
			}
		}

		public int FindBlock(long offset)
		{
			int num = 0;
			int num2 = Blocks.Length - 1;
			while (num <= num2)
			{
				int num3 = num + num2 >> 1;
				if (offset < Blocks[num3].Offset)
				{
					num2 = num3 - 1;
					continue;
				}
				if (offset >= Blocks[num3].Offset + Blocks[num3].Size)
				{
					num = num3 + 1;
					continue;
				}
				return num3;
			}
			throw new InvalidDataException("offset outside the data");
		}

		private byte[] Unpacked(int i)
		{
			if (_cached == i)
			{
				return _unpacked;
			}
			Block block = Blocks[i];
			if (block.Compression == 1)
			{
				throw new InvalidOperationException("LZMA blocks are unpacked in order");
			}
			if (_stored == null || _stored.Length < block.StoredSize)
			{
				_stored = new byte[Math.Max(block.StoredSize, 131072u)];
			}
			if (_unpacked == null || _unpacked.Length < block.Size)
			{
				_unpacked = new byte[Math.Max(block.Size, 131072u)];
			}
			_cached = -1;
			ReadFile(block.FileOffset, _stored, 0, (int)block.StoredSize);
			if (Lz4.Decode(_stored, (int)block.StoredSize, _unpacked, (int)block.Size) != block.Size)
			{
				throw new InvalidDataException("block unpacked to the wrong size");
			}
			_cached = i;
			return _unpacked;
		}

		public ulong[] WriteCopy(string path, SortedDictionary<long, byte> changes, Action<long> progress, Action<long> wrote)
		{
			_progress = progress;
			int num = (int)((DataLength + 131072 - 1) / 131072);
			Block[] array = new Block[num];
			ulong[] array2 = new ulong[num];
			int v = BlocksInfo(array).Length;
			using FileStream fileStream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, 1048576);
			MemoryStream memoryStream = new MemoryStream();
			PutCString(memoryStream, "UnityFS");
			PutU32(memoryStream, Version);
			PutCString(memoryStream, UnityVersion);
			PutCString(memoryStream, UnityRevision);
			long position = memoryStream.Position;
			PutI64(memoryStream, 0L);
			PutU32(memoryStream, (uint)v);
			PutU32(memoryStream, (uint)v);
			PutU32(memoryStream, 192u);
			fileStream.Write(memoryStream.GetBuffer(), 0, (int)memoryStream.Length);
			if (Version >= 7)
			{
				PadTo16(fileStream);
			}
			byte[] array3 = new byte[131072];
			byte[] array4 = new byte[Lz4.MaxPackedSize(131072)];
			using (SortedDictionary<long, byte>.Enumerator enumerator = changes.GetEnumerator())
			{
				bool flag = enumerator.MoveNext();
				for (int i = 0; i < num; i++)
				{
					long num2 = (long)i * 131072L;
					int num3 = (int)Math.Min(131072L, DataLength - num2);
					Next(num2, array3, num3);
					while (flag && enumerator.Current.Key < num2 + num3)
					{
						if (enumerator.Current.Key >= num2)
						{
							array3[enumerator.Current.Key - num2] = enumerator.Current.Value;
						}
						flag = enumerator.MoveNext();
					}
					array2[i] = Fingerprint(array3, num3);
					int num4 = Lz4.Encode(array3, num3, array4);
					Block block = new Block
					{
						Size = (uint)num3,
						Offset = num2
					};
					if (num4 < num3)
					{
						block.StoredSize = (uint)num4;
						block.Flags = 2;
						fileStream.Write(array4, 0, num4);
					}
					else
					{
						block.StoredSize = (uint)num3;
						fileStream.Write(array3, 0, num3);
					}
					array[i] = block;
					progress?.Invoke(num3);
					wrote?.Invoke(block.StoredSize);
				}
			}
			byte[] array5 = BlocksInfo(array);
			fileStream.Write(array5, 0, array5.Length);
			long position2 = fileStream.Position;
			fileStream.Position = position;
			MemoryStream memoryStream2 = new MemoryStream();
			PutI64(memoryStream2, position2);
			fileStream.Write(memoryStream2.GetBuffer(), 0, 8);
			fileStream.Flush(flushToDisk: true);
			return array2;
		}

		public void Verify(string path, ulong[] fingerprints, Action<long> progress)
		{
			using BundleFile bundleFile = new BundleFile(path);
			if (bundleFile.DataLength != DataLength || bundleFile.Nodes.Length != Nodes.Length || bundleFile.Blocks.Length != fingerprints.Length)
			{
				throw new InvalidDataException("the copy's layout differs");
			}
			for (int i = 0; i < Nodes.Length; i++)
			{
				Node node = Nodes[i];
				Node node2 = bundleFile.Nodes[i];
				if (node.Offset != node2.Offset || node.Size != node2.Size || node.Flags != node2.Flags || node.Name != node2.Name)
				{
					throw new InvalidDataException("the copy's file list differs");
				}
			}
			byte[] array = new byte[131072];
			for (int j = 0; j < fingerprints.Length; j++)
			{
				Block block = bundleFile.Blocks[j];
				if (block.Offset != (long)j * 131072L)
				{
					throw new InvalidDataException("the copy's blocks differ");
				}
				bundleFile.Read(block.Offset, array, 0, (int)block.Size);
				if (Fingerprint(array, (int)block.Size) != fingerprints[j])
				{
					throw new InvalidDataException("a block of the copy differs");
				}
				progress?.Invoke(block.Size);
			}
		}

		private static ulong Fingerprint(byte[] data, int count)
		{
			ulong num = (ulong)(-7046029254386353131L ^ count);
			int i;
			for (i = 0; i + 8 <= count; i += 8)
			{
				num ^= BitConverter.ToUInt64(data, i);
				num *= 18397679294719823053uL;
				num ^= num >> 32;
			}
			for (; i < count; i++)
			{
				num ^= data[i];
				num *= 1099511628211L;
			}
			return num;
		}

		private byte[] BlocksInfo(Block[] blocks)
		{
			MemoryStream memoryStream = new MemoryStream();
			memoryStream.Write(new byte[16], 0, 16);
			PutI32(memoryStream, blocks.Length);
			for (int i = 0; i < blocks.Length; i++)
			{
				Block block = blocks[i];
				PutU32(memoryStream, block.Size);
				PutU32(memoryStream, block.StoredSize);
				memoryStream.WriteByte((byte)(block.Flags >> 8));
				memoryStream.WriteByte((byte)block.Flags);
			}
			PutI32(memoryStream, Nodes.Length);
			Node[] nodes = Nodes;
			for (int i = 0; i < nodes.Length; i++)
			{
				Node node = nodes[i];
				PutI64(memoryStream, node.Offset);
				PutI64(memoryStream, node.Size);
				PutU32(memoryStream, node.Flags);
				PutCString(memoryStream, node.Name);
			}
			return memoryStream.ToArray();
		}

		private static byte[] Unpack(byte[] data, int length, int compression, int size)
		{
			switch (compression)
			{
			case 0:
				if (length != size)
				{
					throw new InvalidDataException("bad blocks info size");
				}
				return data;
			case 1:
				return Lzma.DecodeArray(data, length, size);
			case 2:
			case 3:
			{
				byte[] array = new byte[size];
				if (Lz4.Decode(data, length, array, size) != size)
				{
					throw new InvalidDataException("blocks info unpacked to the wrong size");
				}
				return array;
			}
			default:
				throw new InvalidDataException($"compression {compression}");
			}
		}

		private static bool UsesNewFlags(string revision)
		{
			string[] array = revision.Split('.');
			if (array.Length < 3 || !int.TryParse(array[0], out var result) || !int.TryParse(array[1], out var result2))
			{
				throw new InvalidDataException("unknown Unity version " + revision);
			}
			int i;
			for (i = 0; i < array[2].Length && char.IsDigit(array[2][i]); i++)
			{
			}
			int item = ((i > 0) ? int.Parse(array[2].Substring(0, i)) : 0);
			(int, int, int) tuple = (result, result2, item);
			if (result < 2020)
			{
				return false;
			}
			return result switch
			{
				2020 => tuple.CompareTo((2020, 3, 34)) >= 0, 
				2021 => tuple.CompareTo((2021, 3, 2)) >= 0, 
				2022 => tuple.CompareTo((2022, 1, 1)) >= 0, 
				_ => true, 
			};
		}

		private void ReadFile(long at, byte[] buffer, int index, int count)
		{
			_file.Position = at;
			while (count > 0)
			{
				int num = _file.Read(buffer, index, count);
				if (num <= 0)
				{
					throw new EndOfStreamException();
				}
				index += num;
				count -= num;
			}
		}

		private static long Align16(long value)
		{
			return (value + 15) & -16;
		}

		private static void PadTo16(Stream output)
		{
			while (output.Position % 16 != 0L)
			{
				output.WriteByte(0);
			}
		}

		private static void Need(byte[] b, int p, int n)
		{
			if (p < 0 || p + n > b.Length)
			{
				throw new InvalidDataException("header cut short");
			}
		}

		private static ushort U16(byte[] b, ref int p)
		{
			Need(b, p, 2);
			ushort result = (ushort)((b[p] << 8) | b[p + 1]);
			p += 2;
			return result;
		}

		private static uint U32(byte[] b, ref int p)
		{
			Need(b, p, 4);
			int result = (b[p] << 24) | (b[p + 1] << 16) | (b[p + 2] << 8) | b[p + 3];
			p += 4;
			return (uint)result;
		}

		private static int I32(byte[] b, ref int p)
		{
			return (int)U32(b, ref p);
		}

		private static long I64(byte[] b, ref int p)
		{
			return (long)(((ulong)U32(b, ref p) << 32) | U32(b, ref p));
		}

		private static string CString(byte[] b, ref int p)
		{
			int i;
			for (i = p; i < b.Length && b[i] != 0; i++)
			{
			}
			if (i >= b.Length)
			{
				throw new InvalidDataException("unterminated string");
			}
			string result = Encoding.UTF8.GetString(b, p, i - p);
			p = i + 1;
			return result;
		}

		private static void PutU32(Stream s, uint v)
		{
			s.WriteByte((byte)(v >> 24));
			s.WriteByte((byte)(v >> 16));
			s.WriteByte((byte)(v >> 8));
			s.WriteByte((byte)v);
		}

		private static void PutI32(Stream s, int v)
		{
			PutU32(s, (uint)v);
		}

		private static void PutI64(Stream s, long v)
		{
			PutU32(s, (uint)((ulong)v >> 32));
			PutU32(s, (uint)v);
		}

		private static void PutCString(Stream s, string v)
		{
			byte[] bytes = Encoding.UTF8.GetBytes(v);
			s.Write(bytes, 0, bytes.Length);
			s.WriteByte(0);
		}
	}
	internal static class Lz4
	{
		private const int MinMatch = 4;

		private const int LastLiterals = 5;

		private const int MatchLimit = 12;

		private const int HashBits = 16;

		private const int MaxDistance = 65535;

		[ThreadStatic]
		private static int[] _table;

		public static int MaxPackedSize(int length)
		{
			return length + length / 255 + 16;
		}

		public static int Encode(byte[] src, int length, byte[] dst)
		{
			int[] array = _table ?? (_table = new int[65536]);
			Array.Clear(array, 0, array.Length);
			int num = 0;
			int num2 = 0;
			int d = 0;
			int num3 = length - 12;
			while (num < num3)
			{
				uint num4 = U32(src, num);
				int num5 = (int)num4 * -1640531535 >>> 16;
				int num6 = array[num5] - 1;
				array[num5] = num + 1;
				if (num6 < 0 || num - num6 > 65535 || U32(src, num6) != num4)
				{
					num++;
					continue;
				}
				int i = 4;
				for (int num7 = length - 5 - num; i < num7 && src[num6 + i] == src[num + i]; i++)
				{
				}
				d = Sequence(src, num2, num - num2, dst, d, num - num6, i);
				num += i;
				num2 = num;
			}
			return Sequence(src, num2, length - num2, dst, d, 0, 0);
		}

		private static int Sequence(byte[] src, int from, int literals, byte[] dst, int d, int distance, int matchLength)
		{
			int num = matchLength - 4;
			dst[d++] = (byte)((Math.Min(literals, 15) << 4) | ((matchLength != 0) ? Math.Min(num, 15) : 0));
			if (literals >= 15)
			{
				d = Length(dst, d, literals - 15);
			}
			Buffer.BlockCopy(src, from, dst, d, literals);
			d += literals;
			if (matchLength == 0)
			{
				return d;
			}
			dst[d++] = (byte)distance;
			dst[d++] = (byte)(distance >> 8);
			if (num >= 15)
			{
				d = Length(dst, d, num - 15);
			}
			return d;
		}

		private static int Length(byte[] dst, int d, int value)
		{
			while (value >= 255)
			{
				dst[d++] = byte.MaxValue;
				value -= 255;
			}
			dst[d++] = (byte)value;
			return d;
		}

		private static uint U32(byte[] b, int i)
		{
			return (uint)(b[i] | (b[i + 1] << 8) | (b[i + 2] << 16) | (b[i + 3] << 24));
		}

		public static int Decode(byte[] src, int srcLength, byte[] dst, int dstLength)
		{
			int s = 0;
			int num = 0;
			while (s < srcLength)
			{
				int num2 = src[s++];
				int num3 = num2 >> 4;
				if (num3 == 15)
				{
					num3 += ReadLength(src, srcLength, ref s);
				}
				if (num3 > srcLength - s || num3 > dstLength - num)
				{
					throw new InvalidDataException("bad LZ4 literals");
				}
				Buffer.BlockCopy(src, s, dst, num, num3);
				s += num3;
				num += num3;
				if (s == srcLength)
				{
					break;
				}
				if (srcLength - s < 2)
				{
					throw new InvalidDataException("bad LZ4 match");
				}
				int num4 = src[s] | (src[s + 1] << 8);
				s += 2;
				if (num4 == 0 || num4 > num)
				{
					throw new InvalidDataException("bad LZ4 match");
				}
				int num5 = num2 & 0xF;
				if (num5 == 15)
				{
					num5 += ReadLength(src, srcLength, ref s);
				}
				num5 += 4;
				if (num5 > dstLength - num)
				{
					throw new InvalidDataException("bad LZ4 match");
				}
				int num6 = num - num4;
				if (num4 >= num5)
				{
					Buffer.BlockCopy(dst, num6, dst, num, num5);
					num += num5;
					continue;
				}
				for (int i = 0; i < num5; i++)
				{
					dst[num++] = dst[num6 + i];
				}
			}
			return num;
		}

		private static int ReadLength(byte[] src, int srcLength, ref int s)
		{
			int num = 0;
			int num2;
			do
			{
				if (s >= srcLength)
				{
					throw new InvalidDataException("bad LZ4 length");
				}
				num2 = src[s++];
				num += num2;
			}
			while (num2 == 255);
			return num;
		}
	}
	internal static class GameSoundList
	{
		private const string SoundsKey = "BaconMemory.GameSounds";

		internal static void Start(ManualLogSource log, ConfigFile config)
		{
			if (config.Bind<bool>("Sounds", "Free vanilla sounds until they play", true, (ConfigDescription)null).Value)
			{
				Thread thread = new Thread((ThreadStart)delegate
				{
					Scan(log);
				});
				thread.IsBackground = true;
				thread.Priority = ThreadPriority.BelowNormal;
				thread.Name = "BaconMemory game sounds";
				thread.Start();
			}
		}

		private static void Scan(ManualLogSource log)
		{
			try
			{
				Stopwatch stopwatch = Stopwatch.StartNew();
				Dictionary<string, List<long>> dictionary = new Dictionary<string, List<long>>();
				long num = 0L;
				int num2 = 0;
				string[] files = Directory.GetFiles(Path.GetDirectoryName(Paths.ManagedPath));
				foreach (string text in files)
				{
					string fileName = Path.GetFileName(text);
					if ((!fileName.StartsWith("level", StringComparison.OrdinalIgnoreCase) && !fileName.StartsWith("sharedassets", StringComparison.OrdinalIgnoreCase) && !fileName.StartsWith("resources", StringComparison.OrdinalIgnoreCase) && !fileName.StartsWith("globalgamemanagers", StringComparison.OrdinalIgnoreCase)) || fileName.EndsWith(".resS", StringComparison.OrdinalIgnoreCase) || fileName.EndsWith(".resource", StringComparison.OrdinalIgnoreCase))
					{
						continue;
					}
					try
					{
						foreach (Sound item in SoundScan.FindSounds(text))
						{
							if (!dictionary.TryGetValue(item.Name, out var value))
							{
								value = (dictionary[item.Name] = new List<long>());
							}
							if (!value.Contains(item.Samples))
							{
								value.Add(item.Samples);
							}
							num += item.Unpacked;
							num2++;
						}
					}
					catch (Exception ex) when (ex is InvalidDataException || ex is EndOfStreamException || ex is IOException)
					{
						log.LogWarning((object)("Game sounds: couldn't read " + fileName + ": " + ex.Message));
					}
				}
				AppDomain.CurrentDomain.SetData("BaconMemory.GameSounds", dictionary);
				log.LogInfo((object)$"Game sounds: the game's files hold {num2} sounds that unpack into RAM, {num >> 20} MB unpacked (listed in {stopwatch.ElapsedMilliseconds} ms)");
			}
			catch (Exception ex2)
			{
				log.LogWarning((object)("Game sounds: couldn't list the game's sounds, so none are freed: " + ex2.Message));
			}
		}
	}
	internal sealed class Lzma
	{
		private sealed class Len
		{
			private readonly ushort[] _choice = new ushort[2];

			private readonly ushort[] _low = new ushort[128];

			private readonly ushort[] _mid = new ushort[128];

			private readonly ushort[] _high = new ushort[256];

			public void Reset()
			{
				Lzma.Reset(_choice);
				Lzma.Reset(_low);
				Lzma.Reset(_mid);
				Lzma.Reset(_high);
			}

			public uint Decode(Lzma d, int posState)
			{
				if (d.Bit(_choice, 0) == 0)
				{
					return d.BitTree(_low, posState << 3, 3);
				}
				if (d.Bit(_choice, 1) == 0)
				{
					return 8 + d.BitTree(_mid, posState << 3, 3);
				}
				return 16 + d.BitTree(_high, 0, 8);
			}
		}

		private const int States = 12;

		private const int PosStatesMax = 16;

		private const int LenToPosStates = 4;

		private const int AlignBits = 4;

		private const int StartPosModel = 4;

		private const int EndPosModel = 14;

		private const int FullDistances = 128;

		private const int MatchMinLen = 2;

		private const uint TopValue = 16777216u;

		private readonly ushort[] _isMatch = new ushort[192];

		private readonly ushort[] _isRep = new ushort[12];

		private readonly ushort[] _isRepG0 = new ushort[12];

		private readonly ushort[] _isRepG1 = new ushort[12];

		private readonly ushort[] _isRepG2 = new ushort[12];

		private readonly ushort[] _isRep0Long = new ushort[192];

		private readonly ushort[] _posSlot = new ushort[256];

		private readonly ushort[] _posDecoders = new ushort[114];

		private readonly ushort[] _posAlign = new ushort[16];

		private readonly Len _len = new Len();

		private readonly Len _repLen = new Len();

		private readonly ushort[] _literals;

		private readonly int _lc;

		private readonly int _lpMask;

		private readonly int _pbMask;

		private readonly Stream _in;

		private long _inAt;

		private long _inLeft;

		private readonly byte[] _inBuffer = new byte[65536];

		private int _inPos;

		private int _inEnd;

		private uint _range;

		private uint _code;

		private readonly byte[] _window;

		private int _pos;

		private int _flushed;

		private readonly Action<byte[], int, int> _sink;

		private readonly long _size;

		private readonly Action<long> _progress;

		private int _state;

		private uint _rep0;

		private uint _rep1;

		private uint _rep2;

		private uint _rep3;

		private byte _previous;

		public long Done { get; private set; }

		public Lzma(Stream input, long inputLength, Action<byte[], int, int> sink, long size, Action<long> progress)
		{
			_in = input;
			_inAt = input.Position;
			_inLeft = inputLength;
			_sink = sink;
			_size = size;
			_progress = progress;
			byte[] array = new byte[5];
			for (int i = 0; i < 5; i++)
			{
				array[i] = NextByte();
			}
			int num = array[0];
			if (num >= 225)
			{
				throw new InvalidDataException("bad LZMA properties");
			}
			_lc = num % 9;
			num /= 9;
			int num2 = num % 5;
			int num3 = num / 5;
			if (num3 > 4)
			{
				throw new InvalidDataException("bad LZMA properties");
			}
			uint num4 = BitConverter.ToUInt32(array, 1);
			if (num4 > 67108864)
			{
				throw new InvalidDataException("LZMA dictionary too big");
			}
			_lpMask = (1 << num2) - 1;
			_pbMask = (1 << num3) - 1;
			_window = new byte[Math.Max(4096, (int)Math.Min(num4, Math.Max(size, 1L)))];
			_literals = new ushort[768 << _lc + num2];
			ushort[][] array2 = new ushort[10][] { _isMatch, _isRep, _isRepG0, _isRepG1, _isRepG2, _isRep0Long, _posSlot, _posDecoders, _posAlign, _literals };
			for (int j = 0; j < array2.Length; j++)
			{
				Reset(array2[j]);
			}
			_len.Reset();
			_repLen.Reset();
			_range = uint.MaxValue;
			for (int k = 0; k < 5; k++)
			{
				_code = (_code << 8) | NextByte();
			}
		}

		public static byte[] DecodeArray(byte[] data, int length, int size)
		{
			MemoryStream memoryStream = new MemoryStream(size);
			new Lzma(new MemoryStream(data, 0, length), length, memoryStream.Write, size, null).DecodeTo(size);
			if (memoryStream.Length != size)
			{
				throw new InvalidDataException("LZMA data unpacked to the wrong size");
			}
			return memoryStream.ToArray();
		}

		public void DecodeTo(long target)
		{
			target = Math.Min(target, _size);
			long num = Done;
			int num2 = _state;
			uint num3 = _rep0;
			uint num4 = _rep1;
			uint num5 = _rep2;
			uint num6 = _rep3;
			byte b = _previous;
			while (num < target)
			{
				int num7 = (int)num & _pbMask;
				if (Bit(_isMatch, (num2 << 4) + num7) == 0)
				{
					int offset = 768 * ((((int)num & _lpMask) << _lc) + (b >> 8 - _lc));
					b = ((num2 < 7) ? Literal(offset) : MatchedLiteral(offset, Get(num3)));
					Put(b);
					num2 = ((num2 >= 4) ? ((num2 < 10) ? (num2 - 3) : (num2 - 6)) : 0);
					num++;
					continue;
				}
				uint num9;
				if (Bit(_isRep, num2) == 1)
				{
					if (num == 0L)
					{
						throw new InvalidDataException("bad LZMA data");
					}
					if (Bit(_isRepG0, num2) == 0)
					{
						if (Bit(_isRep0Long, (num2 << 4) + num7) == 0)
						{
							num2 = ((num2 < 7) ? 9 : 11);
							b = Get(num3);
							Put(b);
							num++;
							continue;
						}
					}
					else
					{
						uint num8;
						if (Bit(_isRepG1, num2) == 0)
						{
							num8 = num4;
						}
						else
						{
							if (Bit(_isRepG2, num2) == 0)
							{
								num8 = num5;
							}
							else
							{
								num8 = num6;
								num6 = num5;
							}
							num5 = num4;
						}
						num4 = num3;
						num3 = num8;
					}
					num9 = _repLen.Decode(this, num7) + 2;
					num2 = ((num2 < 7) ? 8 : 11);
				}
				else
				{
					num6 = num5;
					num5 = num4;
					num4 = num3;
					num9 = 2 + _len.Decode(this, num7);
					num2 = ((num2 < 7) ? 7 : 10);
					int num10 = (int)BitTree(_posSlot, (int)(Math.Min(num9 - 2, 3u) << 6), 6);
					if (num10 >= 4)
					{
						int num11 = (num10 >> 1) - 1;
						num3 = (uint)((2 | (num10 & 1)) << num11);
						if (num10 < 14)
						{
							num3 += ReverseBitTree(_posDecoders, (int)num3 - num10 - 1, num11);
						}
						else
						{
							num3 += DirectBits(num11 - 4) << 4;
							num3 += ReverseBitTree(_posAlign, 0, 4);
						}
					}
					else
					{
						num3 = (uint)num10;
					}
				}
				if (num3 >= num || num3 >= _window.Length)
				{
					if (num3 == uint.MaxValue)
					{
						target = num;
						break;
					}
					throw new InvalidDataException("bad LZMA distance");
				}
				long num12 = Math.Min(num9, _size - num);
				for (long num13 = 0L; num13 < num12; num13++)
				{
					Put(Get(num3));
				}
				num += num12;
				b = Get(0u);
			}
			Done = num;
			_state = num2;
			_rep0 = num3;
			_rep1 = num4;
			_rep2 = num5;
			_rep3 = num6;
			_previous = b;
			Flush();
			if (target == _size && num != _size)
			{
				throw new InvalidDataException("LZMA data ended early");
			}
		}

		private static void Reset(ushort[] probs)
		{
			for (int i = 0; i < probs.Length; i++)
			{
				probs[i] = 1024;
			}
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		private byte NextByte()
		{
			if (_inPos == _inEnd)
			{
				Refill();
			}
			return _inBuffer[_inPos++];
		}

		private void Refill()
		{
			if (_inLeft <= 0)
			{
				throw new EndOfStreamException("LZMA data cut short");
			}
			_in.Position = _inAt;
			_inEnd = _in.Read(_inBuffer, 0, (int)Math.Min(_inBuffer.Length, _inLeft));
			if (_inEnd <= 0)
			{
				throw new EndOfStreamException("LZMA data cut short");
			}
			_inAt += _inEnd;
			_inLeft -= _inEnd;
			_inPos = 0;
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		private uint Bit(ushort[] probs, int index)
		{
			uint num = probs[index];
			uint num2 = (_range >> 11) * num;
			uint result;
			if (_code < num2)
			{
				_range = num2;
				probs[index] = (ushort)(num + (2048 - num >> 5));
				result = 0u;
			}
			else
			{
				_range -= num2;
				_code -= num2;
				probs[index] = (ushort)(num - (num >> 5));
				result = 1u;
			}
			if (_range < 16777216)
			{
				_code = (_code << 8) | NextByte();
				_range <<= 8;
			}
			return result;
		}

		private uint DirectBits(int count)
		{
			uint num = 0u;
			for (int num2 = count; num2 > 0; num2--)
			{
				_range >>= 1;
				uint num3 = _code - _range >> 31;
				_code -= _range & (num3 - 1);
				num = (num << 1) | (1 - num3);
				if (_range < 16777216)
				{
					_code = (_code << 8) | NextByte();
					_range <<= 8;
				}
			}
			return num;
		}

		private uint BitTree(ushort[] probs, int offset, int bits)
		{
			uint num = 1u;
			for (int i = 0; i < bits; i++)
			{
				num = (num << 1) + Bit(probs, offset + (int)num);
			}
			return num - (uint)(1 << bits);
		}

		private uint ReverseBitTree(ushort[] probs, int offset, int bits)
		{
			uint num = 1u;
			uint num2 = 0u;
			for (int i = 0; i < bits; i++)
			{
				uint num3 = Bit(probs, offset + (int)num);
				num = (num << 1) + num3;
				num2 |= num3 << i;
			}
			return num2;
		}

		private byte Literal(int offset)
		{
			uint num;
			for (num = 1u; num < 256; num = (num << 1) | Bit(_literals, offset + (int)num))
			{
			}
			return (byte)num;
		}

		private byte MatchedLiteral(int offset, byte match)
		{
			uint num = 1u;
			do
			{
				uint num2 = (uint)((match >> 7) & 1);
				match <<= 1;
				uint num3 = Bit(_literals, offset + (int)((1 + num2 << 8) + num));
				num = (num << 1) | num3;
				if (num2 != num3)
				{
					while (num < 256)
					{
						num = (num << 1) | Bit(_literals, offset + (int)num);
					}
					break;
				}
			}
			while (num < 256);
			return (byte)num;
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		private byte Get(uint distance)
		{
			int num = _pos - (int)distance - 1;
			if (num < 0)
			{
				num += _window.Length;
			}
			return _window[num];
		}

		[MethodImpl(MethodImplOptions.AggressiveInlining)]
		private void Put(byte b)
		{
			_window[_pos++] = b;
			if (_pos == _window.Length)
			{
				Flush();
			}
		}

		private void Flush()
		{
			int num = _pos - _flushed;
			if (num > 0)
			{
				_sink(_window, _flushed, num);
				_progress?.Invoke(num);
			}
			if (_pos == _window.Length)
			{
				_pos = 0;
			}
			_flushed = _pos;
		}
	}
	public static class Preloader
	{
		private const int ShaderChunks = 2;

		public static IEnumerable<string> TargetDLLs { get; } = Array.Empty<string>();

		public static void Patch(AssemblyDefinition assembly)
		{
		}

		public static void Finish()
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Expected O, but got Unknown
			new Harmony("baconmemory.preloader").Patch((MethodBase)AccessTools.Method(typeof(Chainloader), "Initialize", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Preloader), "ChainloaderInitialized", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void ChainloaderInitialized()
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Expected O, but got Unknown
			ManualLogSource val = Logger.CreateLogSource("BaconMemory");
			ConfigFile config = new ConfigFile(Path.Combine(Paths.ConfigPath, "baconmemory.cfg"), false)
			{
				SaveOnConfigSet = false
			};
			try
			{
				UnpackShadersAsNeeded(val, config);
			}
			catch (Exception arg)
			{
				val.LogError((object)$"Shaders: the chunk limit failed, shaders unpack as they always do: {arg}");
			}
			try
			{
				SoundBundles.Start(val, config);
			}
			catch (Exception arg2)
			{
				val.LogError((object)$"Sound bundles failed to start, mod sounds stay as they are: {arg2}");
			}
			try
			{
				GameSoundList.Start(val, config);
			}
			catch (Exception arg3)
			{
				val.LogError((object)$"Game sounds: listing failed to start, the game's sounds stay as they are: {arg3}");
			}
		}

		private static void UnpackShadersAsNeeded(ManualLogSource log, ConfigFile config)
		{
			if (config.Bind<bool>("Shaders", "Unpack shaders as needed", true, (ConfigDescription)null).Value)
			{
				Shader.maximumChunksOverride = 2;
				log.LogInfo((object)$"Shaders: mods' shaders keep at most {2} chunks of variants unpacked at once");
			}
		}
	}
	internal static class SoundBundles
	{
		private sealed class Entry
		{
			public long Size;

			public long Time;

			public string Copy;

			public int Sounds;

			public long Saving;

			public int Short;

			public long ShortSaving;

			public string Name = "";

			public string Error;

			public string Changed = "";
		}

		private sealed class Job
		{
			public string Key;

			public string Path;

			public long Size;

			public long Time;

			public int Tries;
		}

		private static class MemoryBudget
		{
			private const long Limit = 1073741824L;

			private static readonly object Gate = new object();

			private static long _held;

			public static void Take(long bytes)
			{
				lock (Gate)
				{
					while (_held > 0 && _held + bytes > 1073741824)
					{
						Monitor.Wait(Gate);
					}
					_held += bytes;
				}
			}

			public static void Give(long bytes)
			{
				lock (Gate)
				{
					_held -= bytes;
					Monitor.PulseAll(Gate);
				}
			}
		}

		private sealed class Progress
		{
			private readonly Stopwatch _watch = Stopwatch.StartNew();

			private long _total;

			private long _done;

			[ThreadStatic]
			private static long _jobPlanned;

			[ThreadStatic]
			private static long _jobDone;

			public double Seconds => _watch.Elapsed.TotalSeconds;

			public Progress(long fileBytes)
			{
				_total = fileBytes * 3;
			}

			public void Begin(long fileSize, long dataLength, bool lzma)
			{
				_jobPlanned = dataLength * (lzma ? 3 : 2);
				_jobDone = 0L;
				Interlocked.Add(ref _total, _jobPlanned - fileSize * 3);
			}

			public void Add(long n)
			{
				_jobDone += n;
				Interlocked.Add(ref _done, n);
			}

			public void Finished()
			{
				long num = _jobPlanned - _jobDone;
				if (num > 0)
				{
					Interlocked.Add(ref _done, num);
				}
				_jobPlanned = (_jobDone = 0L);
			}

			public long Permille()
			{
				return Math.Min(1000L, Interlocked.Read(in _done) * 1000 / Math.Max(1L, Interlocked.Read(in _total)));
			}

			public long SecondsLeft()
			{
				long num = Interlocked.Read(in _done);
				long num2 = Math.Max(1L, Interlocked.Read(in _total));
				double num3 = Math.Min(1.0, (double)num / (double)num2);
				double seconds = Seconds;
				if (!(seconds > 5.0) || !(num3 > 0.02))
				{
					return 0L;
				}
				return (long)Math.Ceiling(seconds / num3 - seconds);
			}
		}

		private const string Section = "Sounds";

		private const string KeepCompressedKey = "Keep long mod sounds compressed";

		private const string ShortOnPlayKey = "Load short mod sounds when they first play";

		private const string ShortestKey = "Shortest long sound (seconds)";

		private const string LeaveAloneKey = "Bundles to leave alone";

		private const string IndexVersion = "BaconMemory sound bundles 4";

		private const long ShortOnlyMinimum = 1048576L;

		private static readonly int MenuWorkers = Math.Max(2, Math.Min(Environment.ProcessorCount - 2, 8));

		private const long MenuWriteBytesPerSecond = 83886080L;

		private const long GameWriteBytesPerSecond = 26214400L;

		private static readonly HashSet<string> NotBundles = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
		{
			".dll", ".pdb", ".mdb", ".xml", ".json", ".md", ".txt", ".png", ".jpg", ".jpeg",
			".cfg", ".ogg", ".wav", ".mp3", ".yml", ".yaml", ".zip", ".ttf", ".otf", ".ico",
			".csv", ".lua", ".cs", ".tmp"
		};

		private static readonly object Lock = new object();

		private static readonly Dictionary<string, Entry> Entries = new Dictionary<string, Entry>(StringComparer.OrdinalIgnoreCase);

		private static readonly List<Job> Jobs = new List<Job>();

		private static readonly HashSet<string> LoadedThisSession = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static ManualLogSource _log;

		private static string _dir;

		private static string _indexPath;

		private static int _shortest;

		private static bool _keepLong;

		private static bool _shortOnPlay;

		private static HashSet<string> _leaveAlone;

		private static readonly HashSet<string> UnloadedEarly = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

		private static string _unloadedEarlyPath;

		private static readonly Dictionary<string, List<string>> LateSoundBundles = new Dictionary<string, List<string>>();

		private static bool _menuReached;

		private static volatile bool _moonLoaded;

		private static volatile bool _inMenu;

		private static Timer _startTimer;

		private static Timer _clock;

		private const string StatsKey = "BaconMemory.SoundBundles";

		private const int LoadedCopies = 0;

		private const int LoadedSounds = 1;

		private const int LoadedSaving = 2;

		private const int JobsTotal = 3;

		private const int JobsDone = 4;

		private const int State = 5;

		private const int SecondsLeft = 6;

		private const int MadeCopies = 7;

		private const int MadeSaving = 8;

		private const int Permille = 9;

		private const int LoadedShort = 10;

		private const int LoadedShortSaving = 11;

		private static readonly long[] Stats = new long[12];

		private const string SoundsKey = "BaconMemory.ChangedSounds";

		private static readonly Dictionary<string, List<long[]>> LoadedSoundSizes = new Dictionary<string, List<long[]>>();

		private static readonly object ThrottleGate = new object();

		private static readonly Stopwatch ThrottleClock = Stopwatch.StartNew();

		private static double _throttleDue;

		private static readonly object SaveGate = new object();

		private static string Header => string.Format("{0}, shortest {1} s, long sounds compressed {2}, short sounds load when they play {3}", "BaconMemory sound bundles 4", _shortest, _keepLong, _shortOnPlay);

		internal static void Start(ManualLogSource log, ConfigFile config)
		{
			//IL_01d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_01db: Expected O, but got Unknown
			_log = log;
			_dir = Path.Combine(Paths.CachePath, "BaconMemory", "SoundBundles");
			_keepLong = config.Bind<bool>("Sounds", "Keep long mod sounds compressed", true, (ConfigDescription)null).Value;
			_shortOnPlay = config.Bind<bool>("Sounds", "Load short mod sounds when they first play", true, (ConfigDescription)null).Value;
			if (!_keepLong && !_shortOnPlay)
			{
				DeleteCopies();
				return;
			}
			AppDomain.CurrentDomain.SetData("BaconMemory.SoundBundles", Stats);
			AppDomain.CurrentDomain.SetData("BaconMemory.ChangedSounds", LoadedSoundSizes);
			_shortest = Math.Max(3, Math.Min(600, config.Bind<int>("Sounds", "Shortest long sound (seconds)", 10, (ConfigDescription)null).Value));
			_leaveAlone = new HashSet<string>(from s in config.Bind<string>("Sounds", "Bundles to leave alone", "", (ConfigDescription)null).Value.Split(new char[2] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries)
				select s.Trim() into s
				where s.Length > 0
				select s, StringComparer.OrdinalIgnoreCase);
			Directory.CreateDirectory(_dir);
			string path = Path.Combine(_dir, "temp");
			if (Directory.Exists(path))
			{
				Directory.Delete(path, recursive: true);
			}
			string[] files = Directory.GetFiles(_dir, "*.tmp", SearchOption.AllDirectories);
			for (int num = 0; num < files.Length; num++)
			{
				TryDelete(files[num]);
			}
			_indexPath = Path.Combine(_dir, "bundles.txt");
			_unloadedEarlyPath = Path.Combine(_dir, "unloaded early.txt");
			LoadIndex();
			LoadUnloadedEarly();
			FindJobs();
			Harmony harmony = new Harmony("baconmemory.preloader");
			files = new string[2] { "LoadFromFile", "LoadFromFileAsync" };
			foreach (string name in files)
			{
				Hook(harmony, name, new Type[1] { typeof(string) }, "UseCopy1");
				Hook(harmony, name, new Type[2]
				{
					typeof(string),
					typeof(uint)
				}, "UseCopy2");
				Hook(harmony, name, new Type[3]
				{
					typeof(string),
					typeof(uint),
					typeof(ulong)
				}, "UseCopy3");
			}
			if (_shortOnPlay)
			{
				WatchUnloads(harmony);
			}
			SceneManager.sceneLoaded += OnSceneLoaded;
			SceneManager.sceneUnloaded += delegate(Scene scene)
			{
				_moonLoaded = SceneManager.sceneCount > 1;
				if (((Scene)(ref scene)).name == "MainMenu")
				{
					_inMenu = false;
				}
			};
			Stats[3] = Jobs.Count;
			Stats[5] = ((Jobs.Count > 0) ? 1 : 0);
			if (Jobs.Count > 0)
			{
				_log.LogInfo((object)$"Sound bundles: {Jobs.Count} new or changed bundles ({Jobs.Sum((Job j) => j.Size) >> 20} MB) get checked in the background once the main menu is up");
			}
		}

		private static void FindJobs()
		{
			HashSet<string> present = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (string item in Directory.EnumerateFiles(Paths.PluginPath, "*", SearchOption.AllDirectories))
			{
				try
				{
					FileInfo fileInfo = new FileInfo(item);
					if (fileInfo.Length >= 1024 && !NotBundles.Contains(fileInfo.Extension))
					{
						string text = Key(fileInfo.FullName);
						long ticks = fileInfo.LastWriteTimeUtc.Ticks;
						if (Entries.TryGetValue(text, out var value) && value.Size == fileInfo.Length && value.Time == ticks && (value.Copy == null || File.Exists(Path.Combine(_dir, value.Copy))) && (value.Short <= 0 || !UnloadedEarly.Contains(text)))
						{
							present.Add(text);
						}
						else if (!_leaveAlone.Contains(fileInfo.Name) && IsBundle(fileInfo.FullName))
						{
							present.Add(text);
							Jobs.Add(new Job
							{
								Key = text,
								Path = fileInfo.FullName,
								Size = fileInfo.Length,
								Time = ticks
							});
						}
					}
				}
				catch (Exception ex)
				{
					_log.LogWarning((object)("Sound bundles: couldn't check " + item + ": " + ex.Message));
				}
			}
			foreach (string item2 in Entries.Keys.Where((string k) => !present.Contains(k)).ToList())
			{
				if (Entries[item2].Copy != null)
				{
					TryDelete(Path.Combine(_dir, Entries[item2].Copy));
				}
				Entries.Remove(item2);
			}
			DeleteUnusedFiles();
			SaveIndex();
		}

		private static bool IsBundle(string path)
		{
			using FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
			byte[] array = new byte[8];
			return fileStream.Read(array, 0, 8) == 8 && array[0] == 85 && array[1] == 110 && array[2] == 105 && array[3] == 116 && array[4] == 121 && array[5] == 70 && array[6] == 83 && array[7] == 0;
		}

		private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			_moonLoaded = SceneManager.sceneCount > 1;
			if (((Scene)(ref scene)).name == "MainMenu")
			{
				_inMenu = true;
			}
			else if ((int)mode == 0)
			{
				_inMenu = false;
			}
			if (_menuReached || ((Scene)(ref scene)).name != "MainMenu")
			{
				return;
			}
			_menuReached = true;
			lock (Lock)
			{
				_log.LogInfo((object)$"Sound bundles: {Stats[0]} bundles loaded from copies: {Stats[1]} long sounds kept compressed, {Stats[2] >> 20} MB less RAM once they have all loaded, and {Stats[10]} short sounds that load when they first play, {Stats[11] >> 20} MB less until they do");
			}
			if (Jobs.Count > 0)
			{
				_startTimer = new Timer(delegate
				{
					StartWorkers();
				}, null, 5000, -1);
			}
		}

		private static void StartWorkers()
		{
			List<Job> list;
			lock (Lock)
			{
				list = (from j in Jobs
					orderby LoadedThisSession.Contains(j.Key) ? 1 : 0, j.Size descending
					select j).ToList();
			}
			ConcurrentQueue<Job> queue = new ConcurrentQueue<Job>(list);
			Progress progress = new Progress(list.Sum((Job j) => j.Size));
			Stats[5] = 2L;
			_clock = new Timer(delegate
			{
				Stats[6] = progress.SecondsLeft();
				Stats[9] = progress.Permille();
			}, null, 1000, 1000);
			int running = MenuWorkers;
			for (int num = 0; num < MenuWorkers; num++)
			{
				int index = num;
				Thread thread = new Thread((ThreadStart)delegate
				{
					while (!queue.IsEmpty)
					{
						if (_moonLoaded || (index > 0 && !_inMenu))
						{
							if (_moonLoaded)
							{
								Stats[5] = 3L;
							}
							Thread.Sleep(500);
						}
						else
						{
							if (!queue.TryDequeue(out var result))
							{
								break;
							}
							Stats[5] = 2L;
							Entry entry = Process(result, progress);
							progress.Finished();
							if (entry == null && ++result.Tries < 3)
							{
								queue.Enqueue(result);
								Thread.Sleep(3000);
							}
							else
							{
								Record(result, entry);
								Interlocked.Increment(ref Stats[4]);
							}
						}
					}
					if (Interlocked.Decrement(ref running) == 0)
					{
						Finish(progress);
					}
				});
				thread.IsBackground = true;
				thread.Priority = ThreadPriority.BelowNormal;
				thread.Name = "BaconMemory sound bundles";
				thread.Start();
			}
		}

		private static void Finish(Progress progress)
		{
			_clock?.Dispose();
			Stats[6] = 0L;
			Stats[9] = 1000L;
			Stats[5] = 4L;
			_log.LogInfo((object)$"Sound bundles: checked {Stats[3]} bundles in the background in {progress.Seconds:F0} s; copies of {Stats[7]} change their sounds ({Stats[8] >> 20} MB less than unpacked, counted over all of them, though a game only loads some at a time). Moons use them when they load, the rest from the next start");
		}

		private static void Record(Job job, Entry result)
		{
			if (result == null)
			{
				return;
			}
			string text = null;
			lock (Lock)
			{
				if (Entries.TryGetValue(job.Key, out var value) && value.Copy != null && value.Copy != result.Copy)
				{
					text = value.Copy;
				}
				Entries[job.Key] = result;
				if (result.Copy != null)
				{
					Stats[7]++;
					Stats[8] += result.Saving + result.ShortSaving;
				}
			}
			if (text != null)
			{
				TryDelete(Path.Combine(_dir, text));
			}
			SaveIndex();
		}

		private static Entry Process(Job job, Progress progress)
		{
			string text = null;
			long num = 0L;
			try
			{
				Entry entry = new Entry
				{
					Size = job.Size,
					Time = job.Time
				};
				using BundleFile bundleFile = new BundleFile(job.Path);
				progress.Begin(job.Size, bundleFile.DataLength, bundleFile.HasLzma);
				if (bundleFile.HasLzma)
				{
					num = bundleFile.SerializedEnd;
					MemoryBudget.Take(num);
					bundleFile.UnpackPrefix(num, progress.Add);
				}
				string bundleName;
				List<Sound> list = SoundScan.FindSounds(bundleFile, out bundleName);
				entry.Name = bundleName ?? "";
				bool flag = _shortOnPlay && CanLoadLate(job, entry.Name);
				SortedDictionary<long, byte> sortedDictionary = new SortedDictionary<long, byte>();
				List<Sound> list2 = new List<Sound>();
				foreach (Sound item in list)
				{
					if (item.Seconds >= (float)_shortest)
					{
						if (!_keepLong)
						{
							continue;
						}
						sortedDictionary[item.LoadTypeAt] = 1;
						entry.Sounds++;
						entry.Saving += Math.Max(0L, item.Unpacked - item.Compressed);
					}
					else
					{
						if (!flag || !item.Preload)
						{
							continue;
						}
						sortedDictionary[item.PreloadAt] = 0;
						sortedDictionary[item.BackgroundAt] = 1;
						entry.Short++;
						entry.ShortSaving += item.Unpacked;
					}
					list2.Add(item);
				}
				if (entry.Sounds == 0 && entry.ShortSaving < 1048576)
				{
					return new Entry
					{
						Size = job.Size,
						Time = job.Time
					};
				}
				if (!HasRoomFor(bundleFile.DataLength))
				{
					throw new IOException("not enough free disk space");
				}
				entry.Copy = Path.Combine(Id(job.Key), Path.GetFileName(job.Path));
				entry.Changed = string.Join("|", list2.Select((Sound s) => $"{Clean(s.Name)}~{s.Samples}~{s.Unpacked}~{s.Compressed}"));
				string text2 = Path.Combine(_dir, entry.Copy);
				Directory.CreateDirectory(Path.GetDirectoryName(text2));
				text = text2 + ".tmp";
				ulong[] fingerprints = bundleFile.WriteCopy(text, sortedDictionary, progress.Add, Throttle);
				bundleFile.Verify(text, fingerprints, progress.Add);
				if (File.Exists(text2))
				{
					File.Delete(text2);
				}
				File.Move(text, text2);
				text = null;
				return entry;
			}
			catch (Exception ex)
			{
				bool flag2 = ex is InvalidDataException || ex is EndOfStreamException;
				if (flag2 || job.Tries >= 2)
				{
					_log.LogWarning((object)("Sound bundles: " + Path.GetFileName(job.Path) + " keeps its sounds as they are" + (flag2 ? "" : " for now") + ": " + ex.Message));
				}
				return flag2 ? new Entry
				{
					Size = job.Size,
					Time = job.Time,
					Error = ex.Message
				} : null;
			}
			finally
			{
				if (text != null)
				{
					TryDelete(text);
				}
				if (num > 0)
				{
					MemoryBudget.Give(num);
				}
			}
		}

		private static bool CanLoadLate(Job job, string name)
		{
			if (!job.Path.EndsWith(".lethalbundle", StringComparison.OrdinalIgnoreCase) || name.Length == 0 || Clean(name) != name)
			{
				return false;
			}
			lock (Lock)
			{
				return !UnloadedEarly.Contains(job.Key);
			}
		}

		private static void Throttle(long bytes)
		{
			double num2;
			lock (ThrottleGate)
			{
				double totalSeconds = ThrottleClock.Elapsed.TotalSeconds;
				long num = (_inMenu ? 83886080 : 26214400);
				_throttleDue = Math.Max(_throttleDue, totalSeconds - 1.0) + (double)bytes / (double)num;
				num2 = _throttleDue - totalSeconds;
			}
			if (num2 > 0.0)
			{
				Thread.Sleep((int)Math.Min(num2 * 1000.0, 2000.0));
			}
		}

		private static void Hook(Harmony harmony, string name, Type[] args, string prefix)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Expected O, but got Unknown
			MethodInfo methodInfo = AccessTools.Method(typeof(AssetBundle), name, args, (Type[])null);
			if (methodInfo == null)
			{
				_log.LogWarning((object)$"Sound bundles: AssetBundle.{name}({args.Length} arguments) not found");
				return;
			}
			harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(SoundBundles), prefix, (Type[])null)
			{
				priority = 800
			}, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
		}

		private static void UseCopy1(ref string path)
		{
			UseCopy(ref path, 0u, 0uL);
		}

		private static void UseCopy2(ref string path, uint crc)
		{
			UseCopy(ref path, crc, 0uL);
		}

		private static void UseCopy3(ref string path, uint crc, ulong offset)
		{
			UseCopy(ref path, crc, offset);
		}

		private static void UseCopy(ref string path, uint crc, ulong offset)
		{
			if (string.IsNullOrEmpty(path))
			{
				return;
			}
			try
			{
				string fullPath = Path.GetFullPath(path);
				string text = Key(fullPath);
				Entry value;
				lock (Lock)
				{
					LoadedThisSession.Add(text);
					if (crc != 0 || offset != 0L || !Entries.TryGetValue(text, out value) || value.Copy == null || (value.Short > 0 && UnloadedEarly.Contains(text)))
					{
						return;
					}
				}
				FileInfo fileInfo = new FileInfo(fullPath);
				if (!fileInfo.Exists || value.Size != fileInfo.Length || value.Time != fileInfo.LastWriteTimeUtc.Ticks)
				{
					return;
				}
				string text2 = Path.Combine(_dir, value.Copy);
				if (!File.Exists(text2))
				{
					return;
				}
				path = text2;
				lock (Lock)
				{
					Stats[0]++;
					Stats[1] += value.Sounds;
					Stats[2] += value.Saving;
					Stats[10] += value.Short;
					Stats[11] += value.ShortSaving;
					if (value.Short > 0)
					{
						if (!LateSoundBundles.TryGetValue(value.Name, out var value2))
						{
							value2 = (LateSoundBundles[value.Name] = new List<string>());
						}
						if (!value2.Contains(text))
						{
							value2.Add(text);
						}
					}
					string[] array = value.Changed.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
					foreach (string obj in array)
					{
						string[] array2 = obj.Split('~');
						if (array2.Length == 4 && long.TryParse(array2[1], out var samples) && long.TryParse(array2[2], out var result) && long.TryParse(array2[3], out var result2))
						{
							if (!LoadedSoundSizes.TryGetValue(array2[0], out var value3))
							{
								value3 = (LoadedSoundSizes[array2[0]] = new List<long[]>());
							}
							if (!value3.Exists((long[] s) => s[0] == samples))
							{
								value3.Add(new long[3] { samples, result, result2 });
							}
						}
					}
				}
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Sound bundles: " + Path.GetFileName(path) + ": " + ex.Message));
			}
		}

		private static void WatchUnloads(Harmony harmony)
		{
			//IL_007e: Unknown result type (might be due to invalid IL or missing references)
			//IL_008c: Expected O, but got Unknown
			//IL_00eb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Expected O, but got Unknown
			string[] array = new string[2] { "Unload", "UnloadAsync" };
			foreach (string text in array)
			{
				MethodInfo methodInfo = AccessTools.Method(typeof(AssetBundle), text, new Type[1] { typeof(bool) }, (Type[])null);
				if (methodInfo == null)
				{
					_log.LogWarning((object)("Sound bundles: AssetBundle." + text + " not found"));
				}
				else
				{
					harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(SoundBundles), "Unloading", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				}
			}
			MethodInfo methodInfo2 = AccessTools.Method(typeof(AssetBundle), "UnloadAllAssetBundles", new Type[1] { typeof(bool) }, (Type[])null);
			if (methodInfo2 == null)
			{
				_log.LogWarning((object)"Sound bundles: AssetBundle.UnloadAllAssetBundles not found");
			}
			else
			{
				harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(SoundBundles), "UnloadingAll", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			}
		}

		private static void Unloading(AssetBundle __instance, bool unloadAllLoadedObjects)
		{
			if (unloadAllLoadedObjects || (Object)(object)__instance == (Object)null)
			{
				return;
			}
			try
			{
				string name = ((Object)__instance).name;
				List<string> value;
				lock (Lock)
				{
					if (!LateSoundBundles.TryGetValue(name, out value))
					{
						return;
					}
				}
				NoteUnloadedEarly(value);
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Sound bundles: checking a bundle unload failed: " + ex.Message));
			}
		}

		private static void UnloadingAll(bool unloadAllObjects)
		{
			if (unloadAllObjects)
			{
				return;
			}
			List<string> list;
			lock (Lock)
			{
				list = LateSoundBundles.Values.SelectMany((List<string> k) => k).ToList();
			}
			if (list.Count > 0)
			{
				NoteUnloadedEarly(list);
			}
		}

		private static void NoteUnloadedEarly(List<string> keys)
		{
			List<string> list;
			List<string> contents;
			lock (Lock)
			{
				list = keys.Where(UnloadedEarly.Add).ToList();
				contents = UnloadedEarly.ToList();
			}
			if (list.Count == 0)
			{
				return;
			}
			_log.LogWarning((object)("Sound bundles: a mod unloaded " + string.Join(", ", list.Select(Path.GetFileName)) + " but kept what it loaded from it, so its short sounds that haven't played yet can't load. From the next start it loads as it is, until its copy is made again with its short sounds loaded up front"));
			try
			{
				File.WriteAllLines(_unloadedEarlyPath, contents);
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Sound bundles: couldn't save " + _unloadedEarlyPath + ": " + ex.Message));
			}
		}

		private static void LoadUnloadedEarly()
		{
			try
			{
				if (!File.Exists(_unloadedEarlyPath))
				{
					return;
				}
				string[] array = File.ReadAllLines(_unloadedEarlyPath);
				foreach (string text in array)
				{
					if (text.Trim().Length > 0)
					{
						UnloadedEarly.Add(text.Trim());
					}
				}
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Sound bundles: couldn't read " + _unloadedEarlyPath + ": " + ex.Message));
			}
		}

		private static void DeleteUnusedFiles()
		{
			try
			{
				HashSet<string> hashSet = new HashSet<string>(from e in Entries.Values
					where e.Copy != null
					select Path.GetFullPath(Path.Combine(_dir, e.Copy)), StringComparer.OrdinalIgnoreCase);
				string[] directories = Directory.GetDirectories(_dir);
				foreach (string path in directories)
				{
					string[] files = Directory.GetFiles(path);
					foreach (string path2 in files)
					{
						if (!hashSet.Contains(Path.GetFullPath(path2)))
						{
							TryDelete(path2);
						}
					}
					if (Directory.GetFileSystemEntries(path).Length == 0)
					{
						Directory.Delete(path);
					}
				}
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Sound bundles: tidying the copies failed: " + ex.Message));
			}
		}

		private static void LoadIndex()
		{
			if (!File.Exists(_indexPath))
			{
				return;
			}
			try
			{
				string[] array = File.ReadAllLines(_indexPath);
				if (array.Length == 0 || array[0] != Header)
				{
					_log.LogInfo((object)"Sound bundles: the sound settings or the copy format changed, so the copies are made again");
					return;
				}
				foreach (string item in array.Skip(1))
				{
					string[] array2 = item.Split('\t');
					if (array2.Length >= 10 && long.TryParse(array2[1], out var result) && long.TryParse(array2[2], out var result2))
					{
						int.TryParse(array2[4], out var result3);
						long.TryParse(array2[5], out var result4);
						int.TryParse(array2[6], out var result5);
						long.TryParse(array2[7], out var result6);
						Entries[array2[0]] = new Entry
						{
							Size = result,
							Time = result2,
							Copy = ((array2[3] == "-") ? null : array2[3]),
							Sounds = result3,
							Saving = result4,
							Short = result5,
							ShortSaving = result6,
							Name = array2[8],
							Error = ((array2[9].Length > 0) ? array2[9] : null),
							Changed = ((array2.Length > 10) ? array2[10] : "")
						};
					}
				}
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Sound bundles: couldn't read " + _indexPath + ": " + ex.Message));
			}
		}

		private static void SaveIndex()
		{
			lock (SaveGate)
			{
				try
				{
					List<string> list = new List<string> { Header };
					lock (Lock)
					{
						foreach (KeyValuePair<string, Entry> entry in Entries)
						{
							Entry value = entry.Value;
							string text = ((value.Error == null) ? "" : value.Error.Replace('\t', ' ').Replace('\r', ' ').Replace('\n', ' '));
							list.Add(string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}\t{6}\t{7}\t{8}\t{9}\t{10}", entry.Key, value.Size, value.Time, value.Copy ?? "-", value.Sounds, value.Saving, value.Short, value.ShortSaving, Clean(value.Name), text, value.Changed));
						}
					}
					string text2 = _indexPath + ".tmp";
					File.WriteAllLines(text2, list);
					if (File.Exists(_indexPath))
					{
						File.Delete(_indexPath);
					}
					File.Move(text2, _indexPath);
				}
				catch (Exception ex)
				{
					_log.LogWarning((object)("Sound bundles: couldn't save " + _indexPath + ": " + ex.Message));
				}
			}
		}

		private static void DeleteCopies()
		{
			if (!Directory.Exists(_dir))
			{
				return;
			}
			try
			{
				long num = new DirectoryInfo(_dir).GetFiles("*", SearchOption.AllDirectories).Sum((FileInfo f) => f.Length);
				Directory.Delete(_dir, recursive: true);
				_log.LogInfo((object)$"Sound bundles: off, deleted the copies ({num >> 20} MB)");
			}
			catch (Exception ex)
			{
				_log.LogWarning((object)("Sound bundles: couldn't delete the copies in " + _dir + ": " + ex.Message));
			}
		}

		private static string Key(string full)
		{
			string text = Path.GetFullPath(Paths.BepInExRootPath).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
			if (!full.StartsWith(text, StringComparison.OrdinalIgnoreCase))
			{
				return full;
			}
			return full.Substring(text.Length);
		}

		private static string Clean(string name)
		{
			return Regex.Replace(name, "[\\t\\r\\n|~]", " ");
		}

		private static string Id(string key)
		{
			ulong num = 14695981039346656037uL;
			string text = key.ToLowerInvariant();
			foreach (char c in text)
			{
				num ^= c;
				num *= 1099511628211L;
			}
			return num.ToString("x16");
		}

		private static bool HasRoomFor(long bytes)
		{
			try
			{
				return new DriveInfo(Path.GetPathRoot(Path.GetFullPath(_dir))).AvailableFreeSpace > bytes + 2147483648u;
			}
			catch (Exception)
			{
				return true;
			}
		}

		private static void TryDelete(string path)
		{
			try
			{
				if (File.Exists(path))
				{
					File.Delete(path);
				}
			}
			catch (Exception)
			{
			}
		}
	}
	internal struct Sound
	{
		public long LoadTypeAt;

		public long PreloadAt;

		public long BackgroundAt;

		public bool Preload;

		public string Name;

		public float Seconds;

		public long Samples;

		public long Unpacked;

		public long Compressed;
	}
	internal static class SoundScan
	{
		private sealed class ClipReader
		{
			private readonly byte[] _b;

			private readonly long _start;

			private readonly bool _big;

			public int At;

			public ClipReader(byte[] b, long start, bool bigEndian)
			{
				_b = b;
				_start = start;
				_big = bigEndian;
			}

			private int Take(int n)
			{
				if (At + n > _b.Length)
				{
					throw new InvalidDataException("clip cut short");
				}
				int at = At;
				At += n;
				return at;
			}

			public void Skip(int n)
			{
				Take(n);
			}

			public void Align4()
			{
				At = (int)(((_start + At + 3) & -4) - _start);
			}

			public int Byte()
			{
				return _b[Take(1)];
			}

			public int Int32()
			{
				int num = Take(4);
				if (!_big)
				{
					return _b[num] | (_b[num + 1] << 8) | (_b[num + 2] << 16) | (_b[num + 3] << 24);
				}
				return (_b[num] << 24) | (_b[num + 1] << 16) | (_b[num + 2] << 8) | _b[num + 3];
			}

			public long Int64()
			{
				uint num = (uint)Int32();
				uint num2 = (uint)Int32();
				if (!_big)
				{
					return (long)(((ulong)num2 << 32) | num);
				}
				return (long)(((ulong)num << 32) | num2);
			}

			public float Single()
			{
				return BitConverter.ToSingle(BitConverter.GetBytes(Int32()), 0);
			}
		}

		private sealed class Cursor
		{
			private readonly Action<long, byte[], int, int> _read;

			private readonly long _start;

			private readonly long _length;

			private readonly byte[] _window = new byte[65536];

			private long _windowAt = -1L;

			private int _windowLength;

			public long Position;

			public bool BigEndian;

			public Cursor(Action<long, byte[], int, int> read, long start, long length)
			{
				_read = read;
				_start = start;
				_length = length;
			}

			private int Take(int n)
			{
				if (Position < 0 || Position + n > _length)
				{
					throw new InvalidDataException("read past the end of a serialized file");
				}
				if (_windowAt < 0 || Position < _windowAt || Position + n > _windowAt + _windowLength)
				{
					_windowAt = Position;
					_windowLength = (int)Math.Min(_window.Length, _length - Position);
					_read(_start + Position, _window, 0, _windowLength);
				}
				int result = (int)(Position - _windowAt);
				Position += n;
				return result;
			}

			public byte Byte()
			{
				return _window[Take(1)];
			}

			public short Int16()
			{
				int num = Take(2);
				return (short)(BigEndian ? ((_window[num] << 8) | _window[num + 1]) : (_window[num] | (_window[num + 1] << 8)));
			}

			public int Int32()
			{
				int num = Take(4);
				if (!BigEndian)
				{
					return _window[num] | (_window[num + 1] << 8) | (_window[num + 2] << 16) | (_window[num + 3] << 24);
				}
				return (_window[num] << 24) | (_window[num + 1] << 16) | (_window[num + 2] << 8) | _window[num + 3];
			}

			public uint UInt32()
			{
				return (uint)Int32();
			}

			public long Int64()
			{
				uint num = UInt32();
				uint num2 = UInt32();
				if (!BigEndian)
				{
					return (long)(((ulong)num2 << 32) | num);
				}
				return (long)(((ulong)num << 32) | num2);
			}

			public void Skip(long n)
			{
				if (n < 0 || Position + n > _length)
				{
					throw new InvalidDataException("skip past the end of a serialized file");
				}
				Position += n;
			}

			public void Align4()
			{
				Position = (Position + 3) & -4;
			}

			public string CString()
			{
				StringBuilder stringBuilder = new StringBuilder();
				for (int i = 0; i < 256; i++)
				{
					byte b = Byte();
					if (b == 0)
					{
						return stringBuilder.ToString();
					}
					stringBuilder.Append((char)b);
				}
				throw new InvalidDataException("unterminated string");
			}

			public byte[] Bytes(int n)
			{
				byte[] array = new byte[n];
				int srcOffset = Take(n);
				Buffer.BlockCopy(_window, srcOffset, array, 0, n);
				return array;
			}
		}

		private const int AudioClipClass = 83;

		private const int MonoBehaviourClass = 114;

		private const int AssetBundleClass = 142;

		public static List<Sound> FindSounds(BundleFile bundle, out string bundleName)
		{
			List<Sound> list = new List<Sound>();
			bundleName = null;
			BundleFile.Node[] nodes = bundle.Nodes;
			for (int i = 0; i < nodes.Length; i++)
			{
				BundleFile.Node node = nodes[i];
				if ((node.Flags & 4) != 0)
				{
					ScanSerializedFile(bundle.Read, node.Offset, node.Size, list, ref bundleName);
				}
			}
			return list;
		}

		public static List<Sound> FindSounds(string serializedFile)
		{
			List<Sound> list = new List<Sound>();
			string bundleName = null;
			FileStream file = new FileStream(serializedFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete, 65536);
			try
			{
				ScanSerializedFile(Read, 0L, file.Length, list, ref bundleName);
				return list;
			}
			finally
			{
				if (file != null)
				{
					((IDisposable)file).Dispose();
				}
			}
			void Read(long at, byte[] buffer, int index, int count)
			{
				file.Position = at;
				while (count > 0)
				{
					int num = file.Read(buffer, index, count);
					if (num <= 0)
					{
						throw new EndOfStreamException();
					}
					index += num;
					count -= num;
				}
			}
		}

		private static void ScanSerializedFile(Action<long, byte[], int, int> read, long start, long size, List<Sound> found, ref string bundleName)
		{
			Cursor cursor = new Cursor(read, start, size)
			{
				BigEndian = true
			};
			cursor.UInt32();
			cursor.UInt32();
			uint num = cursor.UInt32();
			long num2 = cursor.UInt32();
			if (num < 17 || num > 22)
			{
				return;
			}
			bool bigEndian = cursor.Byte() != 0;
			cursor.Skip(3L);
			if (num >= 22)
			{
				cursor.UInt32();
				cursor.Int64();
				num2 = cursor.Int64();
				cursor.Int64();
			}
			cursor.BigEndian = bigEndian;
			cursor.CString();
			cursor.Int32();
			bool flag = cursor.Byte() != 0;
			int num3 = cursor.Int32();
			if (num3 < 0 || num3 > 1048576)
			{
				throw new InvalidDataException("bad type count");
			}
			int[] array = new int[num3];
			for (int i = 0; i < num3; i++)
			{
				int num4 = cursor.Int32();
				cursor.Byte();
				cursor.Int16();
				if (num4 == 114)
				{
					cursor.Skip(16L);
				}
				cursor.Skip(16L);
				if (flag)
				{
					int num5 = cursor.Int32();
					int num6 = cursor.Int32();
					if (num5 < 0 || num6 < 0)
					{
						throw new InvalidDataException("bad type tree");
					}
					cursor.Skip((long)num5 * (long)((num >= 19) ? 32 : 24) + num6);
					if (num >= 21)
					{
						int num7 = cursor.Int32();
						if (num7 < 0)
						{
							throw new InvalidDataException("bad type tree");
						}
						cursor.Skip(4L * (long)num7);
					}
				}
				array[i] = num4;
			}
			int num8 = cursor.Int32();
			if (num8 < 0 || num8 > 16777216)
			{
				throw new InvalidDataException("bad object count");
			}
			List<(long, uint)> list = new List<(long, uint)>();
			long num9 = -1L;
			for (int j = 0; j < num8; j++)
			{
				cursor.Align4();
				cursor.Int64();
				long num10 = ((num >= 22) ? cursor.Int64() : cursor.UInt32());
				uint item = cursor.UInt32();
				int num11 = cursor.Int32();
				if (num11 < 0 || num11 >= num3)
				{
					throw new InvalidDataException("bad type index");
				}
				if (array[num11] == 83)
				{
					list.Add((num2 + num10, item));
				}
				else if (array[num11] == 142 && num9 < 0)
				{
					num9 = num2 + num10;
				}
			}
			foreach (var (start2, size2) in list)
			{
				if (TryReadClip(cursor, start2, size2, bigEndian, out var sound))
				{
					sound.LoadTypeAt += start;
					sound.PreloadAt += start;
					sound.BackgroundAt += start;
					found.Add(sound);
				}
			}
			if (num9 >= 0 && bundleName == null)
			{
				cursor.Position = num9;
				int num12 = cursor.Int32();
				if (num12 >= 0 && num12 <= 1024)
				{
					bundleName = Encoding.UTF8.GetString(cursor.Bytes(num12));
				}
			}
		}

		private static bool TryReadClip(Cursor c, long start, uint size, bool bigEndian, out Sound sound)
		{
			sound = default(Sound);
			if (size < 48 || size > 4096)
			{
				return false;
			}
			c.Position = start;
			byte[] array = c.Bytes((int)size);
			ClipReader clipReader = new ClipReader(array, start, bigEndian);
			try
			{
				int num = clipReader.Int32();
				if (num < 0 || num > array.Length)
				{
					return false;
				}
				string name = Encoding.UTF8.GetString(array, clipReader.At, num);
				clipReader.Skip(num);
				clipReader.Align4();
				int at = clipReader.At;
				int num2 = clipReader.Int32();
				int num3 = clipReader.Int32();
				int num4 = clipReader.Int32();
				clipReader.Int32();
				float num5 = clipReader.Single();
				int num6 = clipReader.Byte();
				int num7 = clipReader.Byte();
				clipReader.Align4();
				int num8 = clipReader.Int32();
				int at2 = clipReader.At;
				int num9 = clipReader.Byte();
				int num10 = clipReader.Byte();
				int num11 = clipReader.Byte();
				clipReader.Align4();
				int num12 = clipReader.Int32();
				if (num12 < 0 || num12 > array.Length)
				{
					return false;
				}
				clipReader.Skip(num12);
				clipReader.Align4();
				long num13 = clipReader.Int64();
				long num14 = clipReader.Int64();
				int num15 = clipReader.Int32();
				if (clipReader.At != array.Length)
				{
					return false;
				}
				if (num2 != 0 || num15 < 1 || num15 > 3 || num6 != 0)
				{
					return false;
				}
				if (!(num5 > 0f) || !(num5 < 86400f) || num3 < 1 || num3 > 8 || num4 < 1000 || num4 > 400000)
				{
					return false;
				}
				if (num7 > 1 || num9 > 1 || num10 > 1 || num11 > 1 || num8 < 0 || num13 < 0 || num14 <= 0)
				{
					return false;
				}
				sound = new Sound
				{
					LoadTypeAt = start + at + (bigEndian ? 3 : 0),
					PreloadAt = start + at2,
					BackgroundAt = start + at2 + 1,
					Preload = (num9 == 1),
					Name = name,
					Seconds = num5,
					Samples = (long)Math.Round(num5 * (float)num4),
					Unpacked = (long)Math.Round(num5 * (float)num4) * num3 * 2,
					Compressed = num14
				};
				return true;
			}
			catch (InvalidDataException)
			{
				return false;
			}
		}
	}
}