Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of RiskOfFrames v0.0.27
plugins/RiskOfFrames.dll
Decompiled 13 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HG.Reflection; using HarmonyLib; using Microsoft.CodeAnalysis; using RoR2; using RoR2.Audio; using RoR2.ConVar; using RoR2.Networking; using RoR2.Projectile; using UnityEngine; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.Rendering.PostProcessing; [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("RiskOfFrames")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("RiskOfFrames")] [assembly: AssemblyTitle("RiskOfFrames")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace RiskOfFrames { internal sealed class CorpseOptimizer : Feature { private sealed class Tracked { public Corpse corpse; public float bornAt; public bool done; public Rigidbody[] bones; public Collider[] colliders; } private ConfigEntry<bool> enforceLimit; private ConfigEntry<float> freezeDelay; private static CorpseOptimizer instance; private static readonly FieldRef<List<Corpse>> instancesList = AccessTools.StaticFieldRefAccess<List<Corpse>>(AccessTools.Field(typeof(Corpse), "instancesList")); private static readonly FieldRef<int> maxCorpses = AccessTools.StaticFieldRefAccess<int>(AccessTools.Field(typeof(Corpse), "maxCorpses")); private static readonly FieldRef<DisposalMode> disposalMode = AccessTools.StaticFieldRefAccess<DisposalMode>(AccessTools.Field(typeof(Corpse), "disposalMode")); private static readonly Action<Corpse> grabDitherController = AccessTools.MethodDelegate<Action<Corpse>>(AccessTools.Method(typeof(Corpse), "GrabDitherController", (Type[])null, (Type[])null), (object)null, true); private readonly List<Tracked> frozenRagdolls = new List<Tracked>(); private readonly List<Tracked> tracked = new List<Tracked>(); private readonly List<Corpse> dissolving = new List<Corpse>(); private readonly List<Animator> frozenByUs = new List<Animator>(); private int scanIndex; public override string Name => "Corpse Cleanup"; public override string Description => "Freeze corpses whose death animation has finished (looks identical), and when there are more corpses than the game's corpse limit, fade out the oldest ones even if they are on screen (vanilla only removes off-screen ones, so big fights pile up corpses forever)."; protected override void BindExtra(ConfigFile config) { //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown enforceLimit = config.Bind<bool>("Corpse Cleanup", "Enforce Limit On Screen", true, "Fade out the oldest corpses once there are more than the game's corpse limit (Settings > Gameplay), even when they are visible."); freezeDelay = config.Bind<float>("Corpse Cleanup", "Freeze After Seconds", 3f, new ConfigDescription("How long after death to check whether the death animation is finished.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(1f, 30f), Array.Empty<object>())); } public override void Install(Harmony harmony) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown instance = this; harmony.Patch((MethodBase)AccessTools.Method(typeof(Corpse), "OnEnable", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(CorpseOptimizer), "CorpseEnabled", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void CorpseEnabled(Corpse __instance) { if (instance != null && instance.Enabled && Object.op_Implicit((Object)(object)__instance)) { instance.tracked.Add(new Tracked { corpse = __instance, bornAt = Time.time }); } } public override void Update() { FreezeFinishedDeathAnimations(); EnforceLimit(); } private void FreezeFinishedDeathAnimations() { //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_011e: Unknown result type (might be due to invalid IL or missing references) if (Time.frameCount % 300 == 0) { frozenRagdolls.RemoveAll((Tracked f) => !Object.op_Implicit((Object)(object)f.corpse)); } int num = Math.Min(8, this.tracked.Count); for (int num2 = 0; num2 < num && this.tracked.Count > 0; num2++) { if (scanIndex >= this.tracked.Count) { scanIndex = 0; } Tracked tracked = this.tracked[scanIndex]; if (!Object.op_Implicit((Object)(object)tracked.corpse) || tracked.done) { this.tracked.RemoveAt(scanIndex); continue; } scanIndex++; float num3 = Time.time - tracked.bornAt; if (num3 < freezeDelay.Value) { continue; } Animator component = ((Component)tracked.corpse).GetComponent<Animator>(); if (!Object.op_Implicit((Object)(object)component) || !((Behaviour)component).enabled || !((Behaviour)component).isActiveAndEnabled) { tracked.done = SettleRagdoll(tracked, num3); continue; } if (!component.IsInTransition(0)) { AnimatorStateInfo currentAnimatorStateInfo = component.GetCurrentAnimatorStateInfo(0); if (!((AnimatorStateInfo)(ref currentAnimatorStateInfo)).loop && ((AnimatorStateInfo)(ref currentAnimatorStateInfo)).normalizedTime >= 1f) { ((Behaviour)component).enabled = false; frozenByUs.Add(component); tracked.done = true; continue; } } if (num3 > freezeDelay.Value + 15f) { tracked.done = true; } } } private bool SettleRagdoll(Tracked t, float age) { //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) if (t.bones == null) { RagdollController component = ((Component)t.corpse).GetComponent<RagdollController>(); if (!Object.op_Implicit((Object)(object)component) || component.bones == null) { return true; } List<Rigidbody> list = new List<Rigidbody>(); List<Collider> list2 = new List<Collider>(); Transform[] bones = component.bones; foreach (Transform val in bones) { if (Object.op_Implicit((Object)(object)val)) { Rigidbody component2 = ((Component)val).GetComponent<Rigidbody>(); if (Object.op_Implicit((Object)(object)component2)) { list.Add(component2); } Collider component3 = ((Component)val).GetComponent<Collider>(); if (Object.op_Implicit((Object)(object)component3)) { list2.Add(component3); } } } t.bones = list.ToArray(); t.colliders = list2.ToArray(); } float num = age - freezeDelay.Value; float num2 = ((num > 8f) ? 9f : ((num > 5f) ? 2.25f : 0.09f)); float num3 = ((num > 8f) ? float.MaxValue : ((num > 5f) ? 4f : 0.25f)); Rigidbody[] bones2 = t.bones; foreach (Rigidbody val2 in bones2) { if (!Object.op_Implicit((Object)(object)val2) || val2.isKinematic || val2.IsSleeping()) { continue; } Vector3 val3 = val2.velocity; if (!(((Vector3)(ref val3)).sqrMagnitude > num2)) { val3 = val2.angularVelocity; if (!(((Vector3)(ref val3)).sqrMagnitude > num3)) { continue; } } return age > freezeDelay.Value + 20f; } bones2 = t.bones; foreach (Rigidbody val4 in bones2) { if (Object.op_Implicit((Object)(object)val4)) { val4.interpolation = (RigidbodyInterpolation)0; val4.isKinematic = true; } } Collider[] colliders = t.colliders; foreach (Collider val5 in colliders) { if (Object.op_Implicit((Object)(object)val5)) { val5.enabled = false; } } frozenRagdolls.Add(t); RofStats.RagdollsSettled++; return true; } private void EnforceLimit() { for (int num = dissolving.Count - 1; num >= 0; num--) { Corpse val = dissolving[num]; if (!Object.op_Implicit((Object)(object)val)) { dissolving.RemoveAt(num); } else { val.UpdateDissolve(); } } if (!enforceLimit.Value || (int)disposalMode.Invoke() != 1) { return; } List<Corpse> list = instancesList.Invoke(); int num2 = maxCorpses.Invoke(); if (num2 < 0 || list == null) { return; } int num3 = list.Count - dissolving.Count - num2; for (int i = 0; i < list.Count; i++) { if (num3 <= 0) { break; } Corpse val2 = list[i]; if (Object.op_Implicit((Object)(object)val2) && !val2.dissolve) { grabDitherController(val2); val2.dissolve = true; val2.lifeTime = Corpse.dissolveTime; dissolving.Add(val2); num3--; } } } protected override void OnDisabled() { foreach (Animator frozenByU in frozenByUs) { if (Object.op_Implicit((Object)(object)frozenByU)) { ((Behaviour)frozenByU).enabled = true; } } frozenByUs.Clear(); foreach (Tracked frozenRagdoll in frozenRagdolls) { if (!Object.op_Implicit((Object)(object)frozenRagdoll.corpse)) { continue; } Collider[] colliders = frozenRagdoll.colliders; foreach (Collider val in colliders) { if (Object.op_Implicit((Object)(object)val)) { val.enabled = true; } } Rigidbody[] bones = frozenRagdoll.bones; foreach (Rigidbody val2 in bones) { if (Object.op_Implicit((Object)(object)val2)) { val2.isKinematic = false; val2.interpolation = (RigidbodyInterpolation)1; } } } frozenRagdolls.Clear(); tracked.Clear(); foreach (Corpse item in dissolving) { if (Object.op_Implicit((Object)(object)item)) { item.lifeTime = 0f; item.UpdateDissolve(); } } dissolving.Clear(); } } internal sealed class DamageNumberBatching : Feature { private struct Pending { public DamageNumberManager manager; public float amount; public Vector3 position; public bool crit; public TeamIndex teamIndex; public DamageColorIndex colorIndex; } private static DamageNumberBatching instance; private static readonly FieldRef<DamageNumberManager, ParticleSystem> psRef = AccessTools.FieldRefAccess<DamageNumberManager, ParticleSystem>("ps"); private readonly List<Pending> pending = new List<Pending>(256); private readonly List<Vector4> customData = new List<Vector4>(1024); public override string Name => "Batch Damage Numbers"; public override string Description => "Spawn all of a frame's damage numbers in one pass instead of re-copying every number on screen for each new one. Looks identical."; public override void Install(Harmony harmony) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown instance = this; harmony.Patch((MethodBase)AccessTools.Method(typeof(DamageNumberManager), "SpawnDamageNumber", (Type[])null, (Type[])null), new HarmonyMethod(typeof(DamageNumberBatching), "SpawnPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool SpawnPrefix(DamageNumberManager __instance, float amount, Vector3 position, bool crit, TeamIndex teamIndex, DamageColorIndex damageColorIndex) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0053: 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_005c: Unknown result type (might be due to invalid IL or missing references) if (instance == null || !instance.Enabled || !Object.op_Implicit((Object)(object)__instance)) { return true; } instance.pending.Add(new Pending { manager = __instance, amount = amount, position = position, crit = crit, teamIndex = teamIndex, colorIndex = damageColorIndex }); return false; } public override void LateUpdate() { Flush(); } protected override void OnDisabled() { Flush(); } private void Flush() { int count = pending.Count; if (count == 0) { return; } int num = 0; while (num < count) { DamageNumberManager manager = pending[num].manager; int i; for (i = num; i < count && (Object)(object)pending[i].manager == (Object)(object)manager; i++) { } FlushBatch(manager, num, i); num = i; } pending.Clear(); } private void FlushBatch(DamageNumberManager manager, int start, int end) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: 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_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Invalid comparison between Unknown and I4 //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Invalid comparison between Unknown and I4 //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) ParticleSystem val = (Object.op_Implicit((Object)(object)manager) ? psRef.Invoke(manager) : null); if (!Object.op_Implicit((Object)(object)val)) { return; } int particleCount = val.particleCount; for (int i = start; i < end; i++) { Pending pending = this.pending[i]; Color val2 = DamageColor.FindColor(pending.colorIndex); Color val3 = Color.white; TeamIndex teamIndex = pending.teamIndex; if ((int)teamIndex != -1) { if ((int)teamIndex == 2) { ((Color)(ref val3))..ctor(0.5568628f, 0.29411766f, 0.6039216f); } } else { val3 = Color.gray; } EmitParams val4 = default(EmitParams); ((EmitParams)(ref val4)).position = pending.position; ((EmitParams)(ref val4)).startColor = Color32.op_Implicit(val2 * val3); ((EmitParams)(ref val4)).applyShapeToPosition = true; val.Emit(val4, 1); } int num = val.particleCount - particleCount; if (num > 0) { val.GetCustomParticleData(customData, (ParticleSystemCustomData)0); int num2 = customData.Count - num; for (int j = 0; j < num && num2 + j >= 0; j++) { Pending pending2 = this.pending[start + j]; customData[num2 + j] = new Vector4(1f, 0f, pending2.amount, pending2.crit ? 1f : 0f); } val.SetCustomParticleData(customData, (ParticleSystemCustomData)0); } } } internal sealed class MergeDuplicateEffects : Feature { private readonly struct Key : IEquatable<Key> { private readonly int effect; private readonly int x; private readonly int y; private readonly int z; private readonly int sx; private readonly int sy; private readonly int sz; private readonly int root; private readonly uint generic; private readonly bool send; public Key(EffectIndex index, EffectData data, bool send) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Expected I4, but got Unknown //IL_0010: 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_003c: Unknown result type (might be due to invalid IL or missing references) this.send = send; effect = (int)index; x = Cell(data.origin.x); y = Cell(data.origin.y); z = Cell(data.origin.z); sx = Cell(data.start.x); sy = Cell(data.start.y); sz = Cell(data.start.z); root = (Object.op_Implicit((Object)(object)data.rootObject) ? ((Object)data.rootObject).GetInstanceID() : 0); generic = data.genericUInt; } private static int Cell(float v) { return Mathf.FloorToInt(v * 2f); } public bool Equals(Key o) { if (send == o.send && effect == o.effect && x == o.x && y == o.y && z == o.z && sx == o.sx && sy == o.sy && sz == o.sz && root == o.root) { return generic == o.generic; } return false; } public override bool Equals(object obj) { if (obj is Key o) { return Equals(o); } return false; } public override int GetHashCode() { return (int)(((uint)(((((((((((((((effect * 397) ^ x) * 397) ^ y) * 397) ^ z) * 397) ^ sx) * 397) ^ sy) * 397) ^ sz) * 397) ^ root) * 397) ^ generic) * 2) + (send ? 1 : 0); } } private static MergeDuplicateEffects instance; private static int frame = -1; private static readonly HashSet<Key> seen = new HashSet<Key>(); public override string Name => "Merge Duplicate Effects"; public override string Description => "When the exact same visual effect is spawned at the same spot several times in one frame, spawn it once. Effects only, never damage or gameplay."; public override void Install(Harmony harmony) { //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Expected O, but got Unknown instance = this; harmony.Patch((MethodBase)AccessTools.Method(typeof(EffectManager), "SpawnEffect", new Type[3] { typeof(EffectIndex), typeof(EffectData), typeof(bool) }, (Type[])null), new HarmonyMethod(typeof(MergeDuplicateEffects), "SpawnEffectPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool SpawnEffectPrefix(EffectIndex effectIndex, EffectData effectData, bool transmit) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (instance == null || !instance.Enabled || effectData == null || (int)effectIndex == -1) { return true; } if (frame != Time.frameCount) { frame = Time.frameCount; seen.Clear(); } bool flag = transmit && NetworkServer.active; if (seen.Add(new Key(effectIndex, effectData, flag))) { return true; } if (flag) { RofStats.EffectSendsMerged++; } else { RofStats.EffectsMerged++; } return false; } } internal sealed class EmotesApiFixes : Feature { private sealed class MapperCache { public Component ik; public bool ikChecked; public bool enableA1; } private static EmotesApiFixes instance; private Type constraintType; private FieldInfo constraintActive; private Type mapperType; private FieldRef<object, Animator> a1; private FieldRef<object, Animator> a2; private FieldRef<object, SkinnedMeshRenderer> smr2; private FieldRef<object, bool> twopart; private FieldRef<object, bool> jank; private FieldRef<object, bool> ragdolling; private FieldRef<object, bool> oneFrameAnimatorLeeWay; private FieldRef<object, object> currentClip; private readonly HashSet<Behaviour> disabledByUs = new HashSet<Behaviour>(); private readonly Dictionary<int, MapperCache> mapperCache = new Dictionary<int, MapperCache>(); public override string Name => "CustomEmotesAPI Idle Cost"; public override string Description => "If CustomEmotesAPI is installed: stop its per-bone constraints and per-character cleanup from running every frame while nobody is emoting. No visible change."; public override void Install(Harmony harmony) { //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_01df: Expected O, but got Unknown //IL_01f3: Unknown result type (might be due to invalid IL or missing references) //IL_0200: Expected O, but got Unknown //IL_0214: Unknown result type (might be due to invalid IL or missing references) //IL_0221: Expected O, but got Unknown //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0242: Expected O, but got Unknown instance = this; constraintType = AccessTools.TypeByName("EmoteConstraint"); mapperType = AccessTools.TypeByName("BoneMapper"); if (constraintType == null || mapperType == null) { Disable("CustomEmotesAPI not installed"); return; } constraintActive = AccessTools.Field(constraintType, "constraintActive"); MethodInfo methodInfo = AccessTools.Method(constraintType, "ActivateConstraints", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(constraintType, "DeactivateConstraints", (Type[])null, (Type[])null); MethodInfo methodInfo3 = AccessTools.Method(constraintType, "AddSource", (Type[])null, (Type[])null); MethodInfo methodInfo4 = AccessTools.Method(mapperType, "TwoPartThing", (Type[])null, (Type[])null); if (constraintActive == null || methodInfo == null || methodInfo2 == null || methodInfo3 == null || methodInfo4 == null) { Disable("unrecognised CustomEmotesAPI version"); return; } try { a1 = AccessTools.FieldRefAccess<Animator>(mapperType, "a1"); a2 = AccessTools.FieldRefAccess<Animator>(mapperType, "a2"); smr2 = AccessTools.FieldRefAccess<SkinnedMeshRenderer>(mapperType, "smr2"); twopart = AccessTools.FieldRefAccess<bool>(mapperType, "twopart"); jank = AccessTools.FieldRefAccess<bool>(mapperType, "jank"); ragdolling = AccessTools.FieldRefAccess<bool>(mapperType, "ragdolling"); oneFrameAnimatorLeeWay = AccessTools.FieldRefAccess<bool>(mapperType, "oneFrameAnimatorLeeWay"); currentClip = AccessTools.FieldRefAccess<object>(mapperType, "currentClip"); } catch (Exception ex) { Disable("unrecognised CustomEmotesAPI version: " + ex.Message); return; } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(EmotesApiFixes), "ActivatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(typeof(EmotesApiFixes), "DeactivatePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo3, (HarmonyMethod)null, new HarmonyMethod(typeof(EmotesApiFixes), "AddSourcePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo4, new HarmonyMethod(typeof(EmotesApiFixes), "TwoPartThingPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void AddSourcePostfix(Behaviour __instance) { instance?.DisableIfIdle(__instance); } private static void DeactivatePostfix(Behaviour __instance) { instance?.DisableIfIdle(__instance); } private static void ActivatePostfix(Behaviour __instance) { if (instance != null && Object.op_Implicit((Object)(object)__instance) && instance.disabledByUs.Remove(__instance)) { __instance.enabled = true; } } private void DisableIfIdle(Behaviour constraint) { if (base.Enabled && Object.op_Implicit((Object)(object)constraint) && constraint.enabled && !(bool)constraintActive.GetValue(constraint)) { constraint.enabled = false; disabledByUs.Add(constraint); } } protected override void OnEnabled() { Object[] array = Object.FindObjectsOfType(constraintType); foreach (Object val in array) { DisableIfIdle((Behaviour)(object)((val is Behaviour) ? val : null)); } } protected override void OnDisabled() { foreach (Behaviour disabledByU in disabledByUs) { if (Object.op_Implicit((Object)(object)disabledByU)) { disabledByU.enabled = true; } } disabledByUs.Clear(); mapperCache.Clear(); } public override void Update() { if (Time.frameCount % 600 == 0) { disabledByUs.RemoveWhere((Behaviour b) => !Object.op_Implicit((Object)(object)b)); if (mapperCache.Count > 512) { mapperCache.Clear(); } } } private static bool TwoPartThingPrefix(MonoBehaviour __instance) { if (instance == null || !instance.Enabled || !Object.op_Implicit((Object)(object)__instance)) { return true; } try { return !instance.TrySteadyState(__instance); } catch { return true; } } private bool TrySteadyState(MonoBehaviour mapper) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) Animator val = a2.Invoke((object)mapper); if (currentClip.Invoke((object)mapper) != null || !twopart.Invoke((object)mapper) || !Object.op_Implicit((Object)(object)val) || ((Behaviour)val).enabled) { return false; } AnimatorStateInfo currentAnimatorStateInfo = val.GetCurrentAnimatorStateInfo(0); if (!((AnimatorStateInfo)(ref currentAnimatorStateInfo)).IsName("none")) { return false; } int instanceID = ((Object)mapper).GetInstanceID(); if (!mapperCache.TryGetValue(instanceID, out var value)) { SkinnedMeshRenderer val2 = smr2.Invoke((object)mapper); string text = ((Object.op_Implicit((Object)(object)val2) && Object.op_Implicit((Object)(object)((Component)val2).transform.parent)) ? ((Object)((Component)((Component)val2).transform.parent).gameObject).name : ""); string text2 = (Object.op_Implicit((Object)(object)((Component)val).transform.parent) ? ((Object)((Component)val).transform.parent).name : ""); value = new MapperCache { enableA1 = (text == "mdlCHEF" || text2 == "Bastian" || (jank.Invoke((object)mapper) && text2 != "mdlAssassin")) }; mapperCache[instanceID] = value; } if (!ragdolling.Invoke((object)mapper)) { if (value.enableA1) { Animator val3 = a1.Invoke((object)mapper); if (Object.op_Implicit((Object)(object)val3)) { ((Behaviour)val3).enabled = true; } } oneFrameAnimatorLeeWay.Invoke((object)mapper) = true; } ((Behaviour)val).enabled = false; if (!value.ikChecked) { value.ikChecked = true; value.ik = (Object.op_Implicit((Object)(object)((Component)val).transform.parent) ? ((Component)((Component)val).transform.parent).GetComponent("InverseKinematics") : null); if (((Object)val).name == "loader") { value.ik = null; } } Component ik = value.ik; Behaviour val4 = (Behaviour)(object)((ik is Behaviour) ? ik : null); if (val4 != null && Object.op_Implicit((Object)(object)val4)) { val4.enabled = true; } return true; } } internal sealed class FasterTargetSearch : Feature { private sealed class MissileState { public float nextSearch; } private sealed class SearchAccess : BullseyeSearch { private sealed class LazyResults : IEnumerable<HurtBox>, IEnumerable { private readonly List<CandidateInfo> candidates; private readonly bool lineOfSight; private readonly Vector3 origin; private readonly QueryTriggerInteraction triggers; private readonly CharacterBody viewer; private readonly bool hasViewer; private readonly List<HurtBox> accepted = new List<HurtBox>(); private int nextCandidate; public LazyResults(List<CandidateInfo> candidates, bool lineOfSight, Vector3 origin, QueryTriggerInteraction triggers, CharacterBody viewer) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) this.candidates = candidates; this.lineOfSight = lineOfSight; this.origin = origin; this.triggers = triggers; this.viewer = viewer; hasViewer = Object.op_Implicit((Object)(object)viewer); } private bool Produce(int index) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Invalid comparison between Unknown and I4 RaycastHit val5 = default(RaycastHit); while (accepted.Count <= index) { if (nextCandidate >= candidates.Count) { return false; } CandidateInfo val = candidates[nextCandidate++]; if (lineOfSight) { Vector3 val2 = val.position - origin; Vector3 val3 = origin; Vector3 val4 = val2; float magnitude = ((Vector3)(ref val2)).magnitude; LayerIndex world = LayerIndex.world; if (Physics.Raycast(val3, val4, ref val5, magnitude, LayerMask.op_Implicit(((LayerIndex)(ref world)).mask), triggers)) { continue; } } if (hasViewer) { CharacterBody component = ((Component)val.hurtBox.healthComponent).gameObject.GetComponent<CharacterBody>(); if (Object.op_Implicit((Object)(object)component) && (int)component.GetVisibilityLevel(viewer) < 4) { continue; } } accepted.Add(val.hurtBox); } return true; } public IEnumerator<HurtBox> GetEnumerator() { for (int i = 0; Produce(i); i++) { yield return accepted[i]; } } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } private static readonly FieldInfo candidatesField = AccessTools.Field(typeof(BullseyeSearch), "candidatesEnumerable"); private static readonly FieldInfo positionField = AccessTools.Field(typeof(CandidateInfo), "position"); private static readonly FieldInfo hurtBoxField = AccessTools.Field(typeof(CandidateInfo), "hurtBox"); public static bool Ready { get { if (candidatesField != null && positionField != null) { return hurtBoxField != null; } return false; } } public static IEnumerable<HurtBox> Lazy(BullseyeSearch search) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (!(candidatesField.GetValue(search) is List<CandidateInfo> candidates)) { return null; } return new LazyResults(candidates, search.filterByLoS, search.searchOrigin, search.queryTriggerInteraction, search.viewer); } } private static FasterTargetSearch instance; private const float MissileRetrySeconds = 0.1f; private static readonly ConditionalWeakTable<MissileController, MissileState> missileStates = new ConditionalWeakTable<MissileController, MissileState>(); public override string Name => "Faster Target Searches"; public override string Description => "Target searches (AI, homing missiles, orbs, items) check line of sight only until the caller has its answer (same results as vanilla). Homing missiles with nothing to target retry every 0.1 s instead of every physics step."; public override void Install(Harmony harmony) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Expected O, but got Unknown //IL_009d: Expected O, but got Unknown instance = this; if (!SearchAccess.Ready) { Disable("BullseyeSearch layout changed"); return; } harmony.Patch((MethodBase)AccessTools.Method(typeof(BullseyeSearch), "GetResults", new Type[0], (Type[])null), new HarmonyMethod(typeof(FasterTargetSearch), "GetResultsPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(MissileController), "FindTarget", (Type[])null, (Type[])null), new HarmonyMethod(typeof(FasterTargetSearch), "FindTargetPrefix", (Type[])null), new HarmonyMethod(typeof(FasterTargetSearch), "FindTargetPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool GetResultsPrefix(BullseyeSearch __instance, ref IEnumerable<HurtBox> __result) { if (instance == null || !instance.Enabled) { return true; } IEnumerable<HurtBox> enumerable = SearchAccess.Lazy(__instance); if (enumerable == null) { return true; } __result = enumerable; return false; } private static bool FindTargetPrefix(MissileController __instance, ref Transform __result) { if (instance == null || !instance.Enabled) { return true; } if (missileStates.TryGetValue(__instance, out var value) && Time.fixedTime < value.nextSearch) { __result = null; RofStats.MissileSearchesSkipped++; return false; } return true; } private static void FindTargetPostfix(MissileController __instance, Transform __result) { if (instance != null && instance.Enabled) { missileStates.GetOrCreateValue(__instance).nextSearch = (Object.op_Implicit((Object)(object)__result) ? 0f : (Time.fixedTime + 0.1f)); } } } internal sealed class FastPostProcessingInit : Feature { private const string CacheVersion = "pp1"; private static FastPostProcessingInit instance; private static string cachePath; private static string pendingKey; public override string Name => "Fast Post-Processing Init"; public override string Description => "When post-processing starts up, only look for effect classes in assemblies that can contain them, and remember the result while those mods are unchanged, instead of listing every type of every mod (~1 minute with large packs). Same effects found."; public override void Install(Harmony harmony) { //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Expected O, but got Unknown //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown instance = this; cachePath = Path.Combine(Paths.CachePath, "RiskOfFrames-ppcache.txt"); MethodInfo methodInfo = AccessTools.Method(typeof(RuntimeUtilities), "GetAllAssemblyTypes", (Type[])null, (Type[])null); if (methodInfo == null) { Disable("post-processing library layout changed"); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(FastPostProcessingInit), "Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(PostProcessManager), "ReloadBaseTypes", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(FastPostProcessingInit), "ReloadPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void ReloadPostfix(PostProcessManager __instance) { IOrderedEnumerable<string> values = __instance.settingsTypes.Keys.Select((Type t) => t.FullName).OrderBy<string, string>((string n) => n, StringComparer.Ordinal); Plugin.Log.LogInfo((object)string.Format("Post-processing effect types ({0}): {1}", __instance.settingsTypes.Count, string.Join(", ", values))); if (pendingKey != null) { SaveCache(pendingKey, __instance.settingsTypes.Keys); pendingKey = null; } } private static List<Assembly> Candidates(out string key) { string library = typeof(PostProcessEffectSettings).Assembly.GetName().Name; List<Assembly> list = new List<Assembly>(); List<string> list2 = new List<string>(); Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { if (!assembly.IsDynamic) { string name = assembly.GetName().Name; if (!name.StartsWith("MMHOOK_", StringComparison.Ordinal) && !name.StartsWith("MMHOOK.", StringComparison.Ordinal) && (!(name != library) || assembly.GetReferencedAssemblies().Any((AssemblyName r) => r.Name == library))) { list.Add(assembly); list2.Add(assembly.FullName + "|" + assembly.ManifestModule.ModuleVersionId); } } } catch { } } list2.Sort(StringComparer.Ordinal); using SHA1 sHA = SHA1.Create(); byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes("pp1\n" + string.Join("\n", list2))); key = BitConverter.ToString(array).Replace("-", ""); return list; } private static List<Type> LoadCache(string key, List<Assembly> candidates) { try { if (!File.Exists(cachePath)) { return null; } string[] array = File.ReadAllLines(cachePath); if (array.Length == 0 || array[0] != "pp1 " + key) { return null; } Dictionary<string, Assembly> dictionary = new Dictionary<string, Assembly>(); foreach (Assembly candidate in candidates) { dictionary[candidate.GetName().Name] = candidate; } List<Type> list = new List<Type>(); for (int i = 1; i < array.Length; i++) { int num = array[i].IndexOf('|'); if (num > 0) { if (!dictionary.TryGetValue(array[i].Substring(0, num), out var value)) { return null; } Type type = value.GetType(array[i].Substring(num + 1), throwOnError: false); if (type == null) { return null; } list.Add(type); } } return list; } catch (Exception ex) { Plugin.Log.LogWarning((object)("post-processing cache unreadable, listing types: " + ex.Message)); return null; } } private static void SaveCache(string key, IEnumerable<Type> effectTypes) { try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("pp1").Append(' ').Append(key) .Append('\n'); foreach (Type effectType in effectTypes) { stringBuilder.Append(effectType.Assembly.GetName().Name).Append('|').Append(effectType.FullName) .Append('\n'); } Directory.CreateDirectory(Path.GetDirectoryName(cachePath)); File.WriteAllText(cachePath, stringBuilder.ToString()); } catch (Exception ex) { Plugin.Log.LogWarning((object)("could not write post-processing cache: " + ex.Message)); } } private static bool Prefix(ref IEnumerable<Type> __result) { if (instance == null || !instance.Enabled) { return true; } Stopwatch stopwatch = Stopwatch.StartNew(); string key; List<Assembly> list = Candidates(out key); List<Type> list2 = LoadCache(key, list); if (list2 != null) { __result = list2; Plugin.Log.LogInfo((object)$"{instance.Name}: {list2.Count} effect classes from cache ({list.Count} assemblies unchanged) in {stopwatch.Elapsed.TotalSeconds:0.00}s"); return false; } List<Type> list3 = new List<Type>(); foreach (Assembly item in list) { try { list3.AddRange(item.GetTypes()); } catch (ReflectionTypeLoadException ex) { list3.AddRange(ex.Types.Where((Type t) => t != null)); } catch { } } __result = list3; pendingKey = key; Plugin.Log.LogInfo((object)string.Format("{0}: listed {1} types from {2} assemblies ({3}) in {4:0.00}s", instance.Name, list3.Count, list.Count, string.Join(", ", list.Select((Assembly a) => a.GetName().Name)), stopwatch.Elapsed.TotalSeconds)); return false; } } internal sealed class IdleSoundEmitters : Feature { private static IdleSoundEmitters instance; public override string Name => "Idle Sound Emitters"; public override string Description => "Switch off pooled one-shot sound emitters while they're idle (vanilla keeps every one of them updating every frame forever). Sounds are unchanged."; public override void Install(Harmony harmony) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Expected O, but got Unknown instance = this; harmony.Patch((MethodBase)AccessTools.Method(typeof(PointSoundManager), "FreeEmitter", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(IdleSoundEmitters), "FreeEmitterPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(PointSoundManager), "RequestEmitter", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(IdleSoundEmitters), "RequestEmitterPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } protected override void OnEnabled() { SetPooled(enabled: false); } protected override void OnDisabled() { SetPooled(enabled: true); } private static void SetPooled(bool enabled) { if (!(AccessTools.Field(typeof(PointSoundManager), "emitterPool")?.GetValue(null) is Stack<AkGameObj> stack)) { return; } foreach (AkGameObj item in stack) { if (Object.op_Implicit((Object)(object)item)) { ((Behaviour)item).enabled = enabled; } } } private static void FreeEmitterPostfix(AkGameObj emitter) { if (instance != null && instance.Enabled && Object.op_Implicit((Object)(object)emitter)) { ((Behaviour)emitter).enabled = false; RofStats.SoundEmittersParked++; } } private static void RequestEmitterPostfix(AkGameObj __result) { if (Object.op_Implicit((Object)(object)__result) && !((Behaviour)__result).enabled) { ((Behaviour)__result).enabled = true; } } } internal sealed class LeanerInventoryUpdates : Feature { private sealed class Info { public bool player; public int phase; } private const int Interval = 4; private static LeanerInventoryUpdates instance; private static Inventory inStep; private static readonly ConditionalWeakTable<Inventory, Info> infos = new ConditionalWeakTable<Inventory, Info>(); public override string Name => "Leaner Inventory Updates"; public override string Description => "Host: non-player inventories refresh equipment state every 4th physics step instead of every step (recharges may finish up to 50 ms later). Players unchanged."; public override void Install(Harmony harmony) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Expected O, but got Unknown //IL_004f: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown instance = this; harmony.Patch((MethodBase)AccessTools.Method(typeof(Inventory), "MyFixedUpdate", (Type[])null, (Type[])null), new HarmonyMethod(typeof(LeanerInventoryUpdates), "StepPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, new HarmonyMethod(typeof(LeanerInventoryUpdates), "StepFinalizer", (Type[])null), (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(Inventory), "RefreshInventoryStateServer", (Type[])null, (Type[])null), new HarmonyMethod(typeof(LeanerInventoryUpdates), "RefreshPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void StepPrefix(Inventory __instance) { inStep = __instance; } private static void StepFinalizer() { inStep = null; } private static bool RefreshPrefix(Inventory __instance) { if (instance == null || !instance.Enabled || inStep != __instance || !NetworkServer.active) { return true; } Info value = infos.GetValue(__instance, Create); if (value.player) { return true; } if ((Mathf.RoundToInt(Time.fixedTime / Time.fixedDeltaTime) + value.phase) % 4 == 0) { return true; } RofStats.InventoryRefreshesSkipped++; return false; } private static Info Create(Inventory inventory) { CharacterMaster component = ((Component)inventory).GetComponent<CharacterMaster>(); return new Info { player = (!Object.op_Implicit((Object)(object)component) || Object.op_Implicit((Object)(object)component.playerCharacterMasterController)), phase = (((Object)inventory).GetInstanceID() & 0x7FFFFFFF) % 4 }; } } internal sealed class LeanProjectilePhysics : Feature { private static LeanProjectilePhysics instance; public override string Name => "Lean Projectile Physics"; public override string Description => "Switch off rigidbody interpolation for projectiles that are only drawn through their ghost (the interpolated positions are never visible)."; public override void Install(Harmony harmony) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown instance = this; harmony.Patch((MethodBase)AccessTools.Method(typeof(ProjectileController), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(LeanProjectilePhysics), "StartPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void StartPostfix(ProjectileController __instance) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) Rigidbody val = default(Rigidbody); if (instance != null && instance.Enabled && Object.op_Implicit((Object)(object)__instance) && ((Component)__instance).TryGetComponent<Rigidbody>(ref val) && (int)val.interpolation != 0 && !((Object)(object)((Component)__instance).GetComponentInChildren<Renderer>(true) != (Object)null)) { val.interpolation = (RigidbodyInterpolation)0; RofStats.ProjectileInterpolationsOff++; } } } internal sealed class MergeDuplicateSounds : Feature { private static MergeDuplicateSounds instance; private static int frame = -1; private static readonly HashSet<(uint, int, int, int)> seen = new HashSet<(uint, int, int, int)>(); public override string Name => "Merge Duplicate Sounds"; public override string Description => "Play the same sound only once per frame within 2 m (identical overlapping copies just add loudness and cost)."; public override void Install(Harmony harmony) { //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Expected O, but got Unknown instance = this; MethodInfo methodInfo = AccessTools.Method(typeof(PointSoundManager), "EmitSoundLocal", new Type[2] { typeof(AkEventIdArg), typeof(Vector3) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(PointSoundManager), "EmitSoundLocal", new Type[2] { typeof(string), typeof(Vector3) }, (Type[])null); if (methodInfo == null || methodInfo2 == null) { Disable("PointSoundManager layout changed"); return; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(MergeDuplicateSounds), "ByIdPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(MergeDuplicateSounds), "ByNamePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool ByIdPrefix(AkEventIdArg akEventId, Vector3 position, ref uint __result) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) return Allow(akEventId.id, position, ref __result); } private static bool ByNamePrefix(string soundEventName, Vector3 position, ref uint __result) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) if (soundEventName == null) { return true; } return Allow(AkSoundEngine.GetIDFromString(soundEventName), position, ref __result); } private static bool Allow(uint id, Vector3 position, ref uint __result) { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (instance == null || !instance.Enabled || id == 0) { return true; } if (frame != Time.frameCount) { frame = Time.frameCount; seen.Clear(); } if (seen.Add((id, Mathf.FloorToInt(position.x * 0.5f), Mathf.FloorToInt(position.y * 0.5f), Mathf.FloorToInt(position.z * 0.5f)))) { return true; } RofStats.SoundsMerged++; __result = 0u; return false; } } internal static class NativeLib { private const string DllName = "rof_native"; private const uint ExpectedVersion = 1u; public static bool Available { get; private set; } [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] private static extern IntPtr LoadLibraryW(string path); [DllImport("rof_native", CallingConvention = CallingConvention.Cdecl)] private static extern uint rof_native_version(); [DllImport("rof_native", CallingConvention = CallingConvention.Cdecl)] private unsafe static extern float rof_native_sum(float* values, UIntPtr count); public unsafe static void TryLoad(string pluginDirectory) { try { string path = Path.Combine(pluginDirectory, "rof_native.dll"); if (!File.Exists(path)) { Plugin.Log.LogInfo((object)"Native helpers: rof_native.dll not found; using managed code paths"); return; } if (LoadLibraryW(path) == IntPtr.Zero) { Plugin.Log.LogWarning((object)$"Native helpers: could not load rof_native.dll (error {Marshal.GetLastWin32Error()}); using managed code paths"); return; } uint num = rof_native_version(); float num2 = rof_native_sum(stackalloc float[3] { 1f, 2f, 3.5f }, (UIntPtr)3uL); if (num != 1 || Math.Abs(num2 - 6.5f) > 0.0001f) { Plugin.Log.LogWarning((object)$"Native helpers: rof_native.dll self-test failed (version {num}, sum {num2}); using managed code paths"); return; } Available = true; Plugin.Log.LogInfo((object)$"Native helpers: rof_native.dll v{num} loaded"); } catch (Exception ex) { Plugin.Log.LogWarning((object)("Native helpers unavailable: " + ex.Message)); } } } internal sealed class OffscreenCulling : Feature { private static OffscreenCulling instance; private ConfigEntry<bool> animators; private ConfigEntry<bool> particles; private static readonly Dictionary<Component, Renderer[]> ikRenderers = new Dictionary<Component, Renderer[]>(); private static readonly List<ParticleSystem> pausedTemplates = new List<ParticleSystem>(); private static bool templatesDone; public override string Name => "Offscreen Culling"; public override string Description => "Let Unity skip pose updates of off-screen characters (their animations still run) and pause off-screen effect particles. Never your own character."; private static bool On { get { if (instance != null) { return instance.Enabled; } return false; } } protected override void BindExtra(ConfigFile config) { animators = config.Bind<bool>("Offscreen Culling", "Animators", true, "Skip writing off-screen characters' bone poses (animation logic still runs)."); particles = config.Bind<bool>("Offscreen Culling", "Particles", true, "Pause effect and projectile particle systems while they're off-screen (set once on their templates)."); } public override void Install(Harmony harmony) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Expected O, but got Unknown //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Expected O, but got Unknown //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0132: Expected O, but got Unknown instance = this; harmony.Patch((MethodBase)AccessTools.Method(typeof(CharacterModel), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(OffscreenCulling), "ModelStart", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); Type type = AccessTools.Inner(typeof(CharacterModel), "ParentedPrefabDisplay"); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "ApplyInternal", (Type[])null, (Type[])null)); if (methodInfo != null) { harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(typeof(OffscreenCulling), "DisplayCreated", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else { Plugin.Log.LogWarning((object)(Name + ": item display hook not found; item displays are left alone")); } RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, (Action)delegate { if (On) { PauseTemplates(); } }); Type type2 = AccessTools.TypeByName("Generics.Dynamics.InverseKinematics"); MethodInfo methodInfo2 = ((type2 == null) ? null : AccessTools.Method(type2, "LateUpdate", (Type[])null, (Type[])null)); if (methodInfo2 != null) { harmony.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(OffscreenCulling), "IkPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } protected override void OnEnabled() { if (RoR2Application.loadFinished) { PauseTemplates(); } } protected override void OnDisabled() { RestoreTemplates(); } private static void ModelStart(CharacterModel __instance) { //IL_008f: Unknown result type (might be due to invalid IL or missing references) if (!On || !instance.animators.Value || !Object.op_Implicit((Object)(object)__instance)) { return; } CharacterBody body = __instance.body; if (!Object.op_Implicit((Object)(object)body) || !Object.op_Implicit((Object)(object)body.master) || !Object.op_Implicit((Object)(object)body.master.playerCharacterMasterController) || !Object.op_Implicit((Object)(object)body.master.playerCharacterMasterController.networkUser) || !((NetworkBehaviour)body.master.playerCharacterMasterController.networkUser).isLocalPlayer) { Animator component = ((Component)__instance).GetComponent<Animator>(); if (Object.op_Implicit((Object)(object)component) && (int)component.cullingMode == 0) { component.cullingMode = (AnimatorCullingMode)1; RofStats.AnimatorsCulled++; } } } private static void DisplayCreated(Transform parent) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Invalid comparison between Unknown and I4 //IL_0096: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Invalid comparison between Unknown and I4 if (!On || !Object.op_Implicit((Object)(object)parent) || parent.childCount == 0) { return; } GameObject gameObject = ((Component)parent.GetChild(parent.childCount - 1)).gameObject; if (instance.animators.Value) { Animator[] componentsInChildren = gameObject.GetComponentsInChildren<Animator>(true); foreach (Animator val in componentsInChildren) { if ((int)val.cullingMode != 2) { val.cullingMode = (AnimatorCullingMode)2; RofStats.ItemDisplayAnimatorsCulled++; } } } if (!instance.particles.Value) { return; } ParticleSystem[] componentsInChildren2 = gameObject.GetComponentsInChildren<ParticleSystem>(true); for (int i = 0; i < componentsInChildren2.Length; i++) { MainModule main = componentsInChildren2[i].main; if ((int)((MainModule)(ref main)).cullingMode == 3) { ((MainModule)(ref main)).cullingMode = (ParticleSystemCullingMode)2; RofStats.ItemDisplayParticlesCulled++; } } } private static bool IkPrefix(Component __instance) { if (!On || !instance.animators.Value || !Object.op_Implicit((Object)(object)__instance)) { return true; } Renderer[] componentsInChildren; if (!ikRenderers.TryGetValue(__instance, out var value)) { CharacterModel componentInParent = __instance.GetComponentInParent<CharacterModel>(); object array; if (!Object.op_Implicit((Object)(object)componentInParent)) { array = new Renderer[0]; } else { componentsInChildren = (Renderer[])(object)((Component)componentInParent).GetComponentsInChildren<SkinnedMeshRenderer>(true); array = componentsInChildren; } value = (Renderer[])array; if (ikRenderers.Count > 4096) { ikRenderers.Clear(); } ikRenderers[__instance] = value; } if (value.Length == 0) { return true; } componentsInChildren = value; foreach (Renderer val in componentsInChildren) { if (Object.op_Implicit((Object)(object)val) && val.isVisible) { return true; } } RofStats.IkSolvesSkipped++; return false; } private static void PauseTemplates() { if (!templatesDone && instance.particles.Value) { templatesDone = true; HashSet<GameObject> hashSet = new HashSet<GameObject>(); for (int i = 0; i < EffectCatalog.effectCount; i++) { EffectDef effectDef = EffectCatalog.GetEffectDef((EffectIndex)i); PauseTemplate((effectDef != null) ? effectDef.prefab : null, hashSet); } for (int j = 0; j < ProjectileCatalog.projectilePrefabCount; j++) { ProjectileController projectilePrefabProjectileControllerComponent = ProjectileCatalog.GetProjectilePrefabProjectileControllerComponent(j); PauseTemplate(Object.op_Implicit((Object)(object)projectilePrefabProjectileControllerComponent) ? projectilePrefabProjectileControllerComponent.ghostPrefab : null, hashSet); } Plugin.Log.LogInfo((object)$"Offscreen Culling: {pausedTemplates.Count} particle systems in {hashSet.Count} effect/projectile templates pause off-screen"); } } private static void PauseTemplate(GameObject template, HashSet<GameObject> seen) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Invalid comparison between Unknown and I4 if (!Object.op_Implicit((Object)(object)template) || !seen.Add(template)) { return; } ParticleSystem[] componentsInChildren = template.GetComponentsInChildren<ParticleSystem>(true); foreach (ParticleSystem val in componentsInChildren) { MainModule main = val.main; if ((int)((MainModule)(ref main)).cullingMode == 3) { ((MainModule)(ref main)).cullingMode = (ParticleSystemCullingMode)2; pausedTemplates.Add(val); RofStats.ParticleSystemsCulled++; } } } private static void RestoreTemplates() { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) foreach (ParticleSystem pausedTemplate in pausedTemplates) { if (Object.op_Implicit((Object)(object)pausedTemplate)) { MainModule main = pausedTemplate.main; ((MainModule)(ref main)).cullingMode = (ParticleSystemCullingMode)3; } } pausedTemplates.Clear(); templatesDone = false; } } internal sealed class OverloadMode : Feature { private const float NormalStep = 1f / 60f; private const float OverloadStep = 1f / 30f; private ConfigEntry<float> enterFps; private ConfigEntry<float> exitFps; private ConfigEntry<bool> whileHosting; private ConfigEntry<bool> asClient; private float smoothedMs = 16f; private float slowFor; private float fastFor; private bool overloaded; public override string Name => "Overload Mode (30 Hz When Overloaded)"; public override string Description => "When the game is overloaded (FPS below the threshold for a couple of seconds), simulate at 30 steps per second instead of 60 until it recovers. Applies in single player and when you join someone's game; when hosting others only if enabled below. Measured a third faster frames (host / single player) and a fifth faster (client) in huge late-game fights, but changes timing while active (very fast attacks and short hit windows are checked half as often)."; protected override void BindExtra(ConfigFile config) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Expected O, but got Unknown enterFps = config.Bind<float>("Overload Mode", "Enter Below FPS", 30f, new ConfigDescription("Switch to 30 Hz when FPS stays below this for 2 seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 60f), Array.Empty<object>())); exitFps = config.Bind<float>("Overload Mode", "Exit Above FPS", 45f, new ConfigDescription("Switch back to 60 Hz when FPS stays above this for 5 seconds.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(10f, 120f), Array.Empty<object>())); whileHosting = config.Bind<bool>("Overload Mode", "Also While Hosting Others", false, "Also switch to 30 Hz while other players are connected to your game (everyone's game then runs at the host's timing)."); asClient = config.Bind<bool>("Overload Mode", "Also When Joining Others", true, "Also switch your own game to 30 Hz when you're a client in someone else's game. Only your character's and your screen's simulation is affected, not the host's."); } public override void Install(Harmony harmony) { } public override void Update() { float unscaledDeltaTime = Time.unscaledDeltaTime; smoothedMs = Mathf.Lerp(smoothedMs, unscaledDeltaTime * 1000f, 0.1f); bool num; if (!NetworkServer.active) { if (!NetworkClient.active) { goto IL_006f; } num = asClient.Value; } else { if (whileHosting.Value) { goto IL_0063; } num = NetworkServer.connections.Count <= 1; } if (num) { goto IL_0063; } goto IL_006f; IL_0063: if (Time.timeScale != 0f) { if (!overloaded) { slowFor = ((smoothedMs > 1000f / enterFps.Value) ? (slowFor + unscaledDeltaTime) : 0f); if (slowFor >= 2f) { SetOverloaded(on: true); } } else { fastFor = ((smoothedMs < 1000f / exitFps.Value) ? (fastFor + unscaledDeltaTime) : 0f); if (fastFor >= 5f) { SetOverloaded(on: false); } } return; } goto IL_006f; IL_006f: SetOverloaded(on: false); } private void SetOverloaded(bool on) { slowFor = (fastFor = 0f); if (overloaded != on) { overloaded = on; Time.fixedDeltaTime = (on ? (1f / 30f) : (1f / 60f)); RofStats.OverloadSwitches++; Plugin.Log.LogInfo((object)(on ? "Overload Mode: simulating at 30 Hz" : "Overload Mode: back to 60 Hz")); } } protected override void OnDisabled() { SetOverloaded(on: false); } } internal sealed class EarlyFeature : Feature { private readonly string name; private readonly string description; private readonly bool defaultOn; public override string Name => name; public override string Description => description; public override bool DefaultOn => defaultOn; public EarlyFeature(string name, string description, bool defaultOn = true) { this.name = name; this.description = description; this.defaultOn = defaultOn; } public override void Install(Harmony harmony) { if (!AppDomain.CurrentDomain.GetAssemblies().Any((Assembly a) => a.GetName().Name == "RiskOfFrames.Early")) { Disable("RiskOfFrames.Early patcher not installed"); } } } internal sealed class PerformanceLog : Feature { private static PerformanceLog instance; private ConfigEntry<float> interval; private readonly List<float> frames = new List<float>(4096); private float windowStart = -1f; private long effectsDrawn; private string path; private static FieldInfo emitterPoolField; private static FieldInfo timeoutsField; public override string Name => "Performance Log"; public override string Description => "Every 30 s of a run, append frame rate (average, 1% low), stage, host/client, enemy/projectile counts and sound emitter counts to BepInEx/RiskOfFrames-perf.csv."; protected override void BindExtra(ConfigFile config) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown interval = config.Bind<float>("Performance Log", "Interval Seconds", 30f, new ConfigDescription("Seconds per logged line.", (AcceptableValueBase)(object)new AcceptableValueRange<float>(5f, 600f), Array.Empty<object>())); } public override void Install(Harmony harmony) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown instance = this; path = Path.Combine(Paths.BepInExRootPath, "RiskOfFrames-perf.csv"); emitterPoolField = AccessTools.Field(typeof(PointSoundManager), "emitterPool"); timeoutsField = AccessTools.Field(typeof(PointSoundManager), "timeouts"); harmony.Patch((MethodBase)AccessTools.Method(typeof(EffectManager), "SpawnEffect", new Type[3] { typeof(EffectIndex), typeof(EffectData), typeof(bool) }, (Type[])null), new HarmonyMethod(typeof(PerformanceLog), "CountEffect", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static void CountEffect(bool transmit) { if (instance != null && instance.Enabled && (!transmit || !NetworkServer.active)) { instance.effectsDrawn++; } } public override void Update() { if (!Object.op_Implicit((Object)(object)Run.instance)) { windowStart = -1f; frames.Clear(); return; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (windowStart < 0f) { windowStart = realtimeSinceStartup; effectsDrawn = 0L; frames.Clear(); return; } frames.Add(Time.unscaledDeltaTime); if (realtimeSinceStartup - windowStart >= interval.Value && frames.Count > 0) { try { Write(realtimeSinceStartup - windowStart); } catch (Exception ex) { Plugin.Log.LogDebug((object)("performance log: " + ex.Message)); } windowStart = realtimeSinceStartup; effectsDrawn = 0L; frames.Clear(); } } private void Write(float seconds) { frames.Sort(); int count = frames.Count; int num = Math.Max(1, count / 100); double num2 = 0.0; for (int i = count - num; i < count; i++) { num2 += (double)frames[i]; } float num3 = (float)count / seconds; float num4 = (float)((double)num / num2); float num5 = frames[count - 1] * 1000f; int num6 = (emitterPoolField?.GetValue(null) as ICollection)?.Count ?? (-1); int num7 = (timeoutsField?.GetValue(null) as ICollection)?.Count ?? (-1); bool flag = !File.Exists(path); using StreamWriter streamWriter = new StreamWriter(path, append: true); if (flag) { streamWriter.WriteLine("time,rofVersion,role,players,stage,stageNumber,runMinutes,avgFps,onePercentLowFps,worstFrameMs,enemies,projectiles,effectsDrawnPerSec,soundEmittersPlaying,soundEmittersPooled"); } Run val = Run.instance; streamWriter.WriteLine(string.Join(",", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), "0.0.27", NetworkServer.active ? "host" : "client", NetworkUser.readOnlyInstancesList.Count.ToString(), Object.op_Implicit((Object)(object)SceneCatalog.mostRecentSceneDef) ? SceneCatalog.mostRecentSceneDef.cachedName : "?", Object.op_Implicit((Object)(object)val) ? (val.stageClearCount + 1).ToString() : "", Object.op_Implicit((Object)(object)val) ? (val.GetRunStopwatch() / 60f).ToString("0.0", CultureInfo.InvariantCulture) : "", num3.ToString("0.0", CultureInfo.InvariantCulture), num4.ToString("0.0", CultureInfo.InvariantCulture), num5.ToString("0", CultureInfo.InvariantCulture), TeamComponent.GetTeamMembers((TeamIndex)2).Count.ToString(), InstanceTracker.GetInstancesList<ProjectileController>().Count.ToString(), ((float)effectsDrawn / seconds).ToString("0", CultureInfo.InvariantCulture), num7.ToString(), num6.ToString())); } } internal sealed class PhysicsStepCap : Feature { private const float VanillaMaxDelta = 1f / 3f; private ConfigEntry<int> maxSteps; private ConfigEntry<bool> soloOnly; public override string Name => "Limit Catch-Up Spikes"; public override string Description => "When a frame takes very long (over 133 ms), let the game catch up with at most 8 physics steps instead of 20, so one slow moment doesn't snowball into several freezes. During such a spike the game runs in slow motion for an instant; normal frames are unaffected. Not applied while you host other players."; protected override void BindExtra(ConfigFile config) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown maxSteps = config.Bind<int>("Limit Catch-Up Spikes", "Max Steps Per Frame", 8, new ConfigDescription("Physics steps allowed per frame (vanilla allows 20).", (AcceptableValueBase)(object)new AcceptableValueRange<int>(2, 20), Array.Empty<object>())); soloOnly = config.Bind<bool>("Limit Catch-Up Spikes", "Not While Hosting Others", true, "Don't limit while you host other players (the host's slow motion would also slow everyone else). Clients and single player are always limited."); } public override void Install(Harmony harmony) { } public override void Update() { bool flag = !NetworkServer.active || NetworkServer.connections.Count <= 1; float num = ((!soloOnly.Value || flag) ? ((float)maxSteps.Value * Time.fixedDeltaTime) : (1f / 3f)); if (!Mathf.Approximately(Time.maximumDeltaTime, num)) { Time.maximumDeltaTime = num; } } protected override void OnDisabled() { Time.maximumDeltaTime = 1f / 3f; } } [BepInPlugin("daz.RiskOfFrames", "RiskOfFrames", "0.0.27")] public sealed class Plugin : BaseUnityPlugin { public const string PluginGUID = "daz.RiskOfFrames"; public const string PluginName = "RiskOfFrames"; private static Action earlyTweaksCheck; private static Action earlyTweaksEnd; public const string PluginVersion = "0.0.27"; internal static ManualLogSource Log; internal static Harmony Harmony; internal static readonly List<Feature> Features = new List<Feature>(); internal static void FlushEarlyConfigSaves() { try { Type.GetType("RiskOfFrames.Early.ConfigSaveDeferral, RiskOfFrames.Early")?.GetMethod("Flush")?.Invoke(null, null); } catch (Exception ex) { Log.LogWarning((object)("config flush failed: " + ex.Message)); } } internal static void EarlyStartupTweaks(bool end) { try { if (earlyTweaksCheck == null) { Type type = Type.GetType("RiskOfFrames.Early.StartupTweaks, RiskOfFrames.Early"); earlyTweaksCheck = ((type == null) ? ((Action)delegate { }) : ((Action)Delegate.CreateDelegate(typeof(Action), type.GetMethod("Check", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)))); earlyTweaksEnd = ((type == null) ? ((Action)delegate { }) : ((Action)Delegate.CreateDelegate(typeof(Action), type.GetMethod("End", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)))); } (end ? earlyTweaksEnd : earlyTweaksCheck)(); } catch (Exception ex) { earlyTweaksCheck = (earlyTweaksEnd = delegate { }); Log.LogWarning((object)("startup tweaks: " + ex.Message)); } } private void Awake() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Expected O, but got Unknown //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_021d: Expected O, but got Unknown //IL_0408: Unknown result type (might be due to invalid IL or missing references) //IL_040d: Unknown result type (might be due to invalid IL or missing references) //IL_0413: Expected O, but got Unknown //IL_0413: Unknown result type (might be due to invalid IL or missing references) Log = ((BaseUnityPlugin)this).Logger; Harmony = new Harmony("daz.RiskOfFrames"); NativeLib.TryLoad(Path.GetDirectoryName(((BaseUnityPlugin)this).Info.Location)); Log.LogInfo((object)$"Rendering threading mode: {SystemInfo.renderingThreadingMode}"); Features.Add(new DamageNumberBatching()); Features.Add(new CorpseOptimizer()); Features.Add(new ProjectileTickDedupe()); Features.Add(new EmotesApiFixes()); Features.Add(new MergeDuplicateEffects()); Features.Add(new IdleSoundEmitters()); Features.Add(new MergeDuplicateSounds()); Features.Add(new FasterTargetSearch()); Features.Add(new LeanProjectilePhysics()); Features.Add(new OffscreenCulling()); Features.Add(new LeanerInventoryUpdates()); Features.Add(new PerformanceLog()); Features.Add(new PhysicsStepCap()); Features.Add(new OverloadMode()); Features.Add(new UncappedLoading()); Features.Add(new FastContentLoading()); Features.Add(new FastSystemInitializers()); Features.Add(new FasterLoadingUploads()); Features.Add(new LeanLoadingScreen()); Features.Add(new SkipSplashAndIntro()); Features.Add(new StartupScanCache()); Features.Add(new FastPostProcessingInit()); Features.Add(new EarlyFeature("Parallel Mod Startup Loads", "Start the startup asset loads of known slow mods (CharacterFixer) all at once instead of one after another. Same assets, same result. Takes effect at the next launch.")); Features.Add(new EarlyFeature("Defer Config Saving During Startup", "BepInEx rewrites a mod's whole config file for every config entry created; during startup each changed file is written once at the end instead. Takes effect at the next launch.")); Features.Add(new EarlyFeature("Pause Garbage Collection During Startup", "While the game starts up, don't run the garbage collector (it repeatedly scans the multi-GB heap of a big pack while almost nothing is garbage yet). Switched back on at the main menu, or immediately if memory use passes the limit below. Takes effect at the next launch.")); ((BaseUnityPlugin)this).Config.Bind<int>("Pause Garbage Collection During Startup", "Startup Memory Limit (MB)", 5120, new ConfigDescription("If memory use passes this during startup, garbage collection is switched back on right away.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1024, 32768), Array.Empty<object>())); Features.Add(new EarlyFeature("High Loading Priority During Startup", "Give Unity's background loading thread high priority from the moment mods start loading (the game itself only does this later). Back to normal at the main menu. Takes effect at the next launch.")); Features.Add(new EarlyFeature("Skip Log Stack Traces During Startup", "During startup, info and warning log messages don't record a stack trace (errors still do). Back to normal at the main menu. Takes effect at the next launch.")); Features.Add(new EarlyFeature("Prefetch Mod Asset Bundles", "Remember which asset bundle files mods open while they load, and at the next launch start opening them all in the background as soon as mods begin loading; each mod gets its bundle already open. Bundles nobody asks for are closed again. Takes effect at the next launch.")); Features.Add(new EarlyFeature("Graphics Jobs (Next Launch)", "Let Unity prepare drawing on worker threads (its 'graphics jobs' option). Unity only reads this from the game's boot.config file, so RiskOfFrames adds one line there (backup: boot.config.rof-backup) and removes it again when you turn this off. Applies from the NEXT launch, and to every profile while it's on. Off by default: measured anywhere from 14% faster to 9% slower in heavy fights. If you turn it on and later uninstall RiskOfFrames, turn it off and launch once first (or delete that line from boot.config).", defaultOn: false)); Features.Add(new EarlyFeature("Prefetch Embedded Mod Bundles", "Also prefetch asset bundles that mods load from inside their DLL (a copy is kept in BepInEx/cache/RiskOfFrames-bundles). Off by default: measured slower overall, because the extra background work delays other mods' loads. Takes effect at the next launch.", defaultOn: false)); Features.Add(new EarlyFeature("Prefetch Game Asset Bundles v2", "Remember which game asset bundles load during startup and start loading them all in the background at the next launch. Off by default: measured no gain, and the extra background loads delay mods' own loads. Takes effect at the next launch.", defaultOn: false)); foreach (Feature feature in Features) { try { feature.Bind(((BaseUnityPlugin)this).Config); feature.Install(Harmony); Log.LogInfo((object)(feature.Name + ": " + ((!feature.Available) ? ("not applicable (" + feature.UnavailableReason + ")") : (feature.Toggle.Value ? "on" : "off")))); } catch (Exception ex) { feature.Disable("failed to install: " + ex.Message); Log.LogError((object)$"{feature.Name} failed to install and is disabled: {ex}"); } } foreach (Feature feature2 in Features) { feature2.Refresh(); } Application.quitting += FlushEarlyConfigSaves; Application.quitting += delegate { EarlyStartupTweaks(end: true); }; GameObject val = new GameObject("RiskOfFrames.FrameDriver"); Object.DontDestroyOnLoad((Object)val); ((Object)val).hideFlags = (HideFlags)61; val.AddComponent<FrameDriver>(); } } internal abstract class Feature { public abstract string Name { get; } public abstract string Description { get; } public virtual bool DefaultOn => true; public ConfigEntry<bool> Toggle { get; private set; } public bool Available { get; private set; } = true; public string UnavailableReason { get; private set; } public bool Enabled { get; private set; } public void Bind(ConfigFile config) { Toggle = config.Bind<bool>("Features", Name, DefaultOn, Description); Toggle.SettingChanged += delegate { Refresh(); }; BindExtra(config); } protected virtual void BindExtra(ConfigFile config) { } public abstract void Install(Harmony harmony); public void Disable(string reason) { Available = false; UnavailableReason = reason; Refresh(); } public void Refresh() { bool flag = Available && Toggle != null && Toggle.Value; if (flag == Enabled) { return; } Enabled = flag; try { if (flag) { OnEnabled(); } else { OnDisabled(); } } catch (Exception arg) { Plugin.Log.LogError((object)$"{Name} toggle failed: {arg}"); } } protected virtual void OnEnabled() { } protected virtual void OnDisabled() { } public virtual void Update() { } public virtual void LateUpdate() { } } internal sealed class FrameDriver : MonoBehaviour { private int framesSinceLoaded = -1; private void Start() { foreach (Feature feature in Plugin.Features) { feature.Refresh(); } } private void Update() { if (framesSinceLoaded < 2) { if (RoR2Application.loadFinished && ++framesSinceLoaded == 2) { Plugin.FlushEarlyConfigSaves(); Plugin.EarlyStartupTweaks(end: true); } else { Plugin.EarlyStartupTweaks(end: false); } } foreach (Feature feature in Plugin.Features) { if (feature.Enabled) { try { feature.Update(); } catch (Exception arg) { Plugin.Log.LogError((object)$"{feature.Name}.Update: {arg}"); } } } } private void LateUpdate() { foreach (Feature feature in Plugin.Features) { if (feature.Enabled) { try { feature.LateUpdate(); } catch (Exception arg) { Plugin.Log.LogError((object)$"{feature.Name}.LateUpdate: {arg}"); } } } } } internal sealed class ProjectileTickDedupe : Feature { private static ProjectileTickDedupe instance; private static readonly Action<ProjectileGhostController> ghostFixedUpdate = AccessTools.MethodDelegate<Action<ProjectileGhostController>>(AccessTools.Method(typeof(ProjectileGhostController), "FixedUpdate", (Type[])null, (Type[])null), (object)null, true); private static readonly Action<ProjectileNetworkTransform, float> networkFixedUpdate = AccessTools.MethodDelegate<Action<ProjectileNetworkTransform, float>>(AccessTools.Method(typeof(ProjectileNetworkTransform), "MyFixedUpdate", (Type[])null, (Type[])null), (object)null, true); private readonly HashSet<ProjectileGhostController> pendingGhosts = new HashSet<ProjectileGhostController>(); private readonly List<ProjectileGhostController> ghostBuffer = new List<ProjectileGhostController>(); private readonly HashSet<ProjectileNetworkTransform> pendingNetwork = new HashSet<ProjectileNetworkTransform>(); private readonly List<ProjectileNetworkTransform> networkBuffer = new List<ProjectileNetworkTransform>(); private readonly HashSet<ProjectileNetworkTransform> pendingInterpolation = new HashSet<ProjectileNetworkTransform>(); private readonly List<ProjectileNetworkTransform> interpolationBuffer = new List<ProjectileNetworkTransform>(); private static readonly FieldRef<ProjectileNetworkTransform, bool> hasEffectiveAuthorityRef = AccessTools.FieldRefAccess<ProjectileNetworkTransform, bool>("hasEffectiveAuthority"); private static readonly FieldRef<ProjectileNetworkTransform, Rigidbody> rbRef = AccessTools.FieldRefAccess<ProjectileNetworkTransform, Rigidbody>("rb"); private static readonly FieldRef<ProjectileNetworkTransform, NetworkLerpedVector3> interpolatedPositionRef = AccessTools.FieldRefAccess<ProjectileNetworkTransform, NetworkLerpedVector3>("interpolatedPosition"); private static readonly FieldRef<ProjectileNetworkTransform, NetworkLerpedQuaternion> interpolatedRotationRef = AccessTools.FieldRefAccess<ProjectileNetworkTransform, NetworkLerpedQuaternion>("interpolatedRotation"); private bool runningGhosts; private bool runningNetwork; private bool skippedNetworkWhileAlone; public override string Name => "Projectile Per-Frame Updates"; public override string Description => "Update projectile visuals and the host's projectile network positions once per rendered frame instead of once per physics step (and skip the network part entirely in single player). Big win in projectile-heavy late game; looks identical."; public override void Install(Harmony harmony) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Expected O, but got Unknown instance = this; harmony.Patch((MethodBase)AccessTools.Method(typeof(ProjectileGhostController), "FixedUpdate", (Type[])null, (Type[])null), new HarmonyMethod(typeof(ProjectileTickDedupe), "GhostFixedUpdatePrefix", (Type[])null) { priority = 800 }, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(ProjectileNetworkTransform), "MyFixedUpdate", (Type[])null, (Type[])null), new HarmonyMethod(typeof(ProjectileTickDedupe), "NetworkFixedUpdatePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); NetworkManagerSystem.onServerConnectGlobal += OnClientConnected; } private static bool GhostFixedUpdatePrefix(ProjectileGhostController __instance) { if (instance == null || !instance.Enabled || instance.runningGhosts) { return true; } instance.pendingGhosts.Add(__instance); return false; } public override void LateUpdate() { RunDeferredInterpolation(); if (pendingGhosts.Count == 0) { return; } ghostBuffer.AddRange(pendingGhosts); pendingGhosts.Clear(); runningGhosts = true; try { foreach (ProjectileGhostController item in ghostBuffer) { if (Object.op_Implicit((Object)(object)item) && ((Behaviour)item).isActiveAndEnabled) { try { ghostFixedUpdate(item); } catch (Exception ex) { Plugin.Log.LogDebug((object)("ghost update failed: " + ex.Message)); } } } } finally { runningGhosts = false; ghostBuffer.Clear(); } } private static bool NetworkFixedUpdatePrefix(ProjectileNetworkTransform __instance) { if (instance == null || !instance.Enabled || instance.runningNetwork) { return true; } if (IsDisplayOnlyInterpolation(__instance)) { instance.pendingInterpolation.Add(__instance); RofStats.ClientInterpolationsSkipped++; return false; } if (__instance.checkForLocalPlayerAuthority || !((NetworkBehaviour)__instance).isServer) { return true; } if (!HasRemoteClients()) { instance.skippedNetworkWhileAlone = true; return false; } instance.pendingNetwork.Add(__instance); return false; } public override void Update() { if (pendingNetwork.Count == 0) { return; } networkBuffer.AddRange(pendingNetwork); pendingNetwork.Clear(); runningNetwork = true; try { float fixedDeltaTime = Time.fixedDeltaTime; foreach (ProjectileNetworkTransform item in networkBuffer) { if (Object.op_Implicit((Object)(object)item) && ((Behaviour)item).isActiveAndEnabled) { networkFixedUpdate(item, fixedDeltaTime); } } } finally { runningNetwork = false; networkBuffer.Clear(); } } private static bool IsDisplayOnlyInterpolation(ProjectileNetworkTransform pnt) { if (!(pnt.checkForLocalPlayerAuthority ? (!hasEffectiveAuthorityRef.Invoke(pnt)) : (!((NetworkBehaviour)pnt).isServer))) { return false; } Rigidbody val = rbRef.Invoke(pnt); if (Object.op_Implicit((Object)(object)val)) { return !val.detectCollisions; } return true; } private void RunDeferredInterpolation() { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) if (pendingInterpolation.Count == 0) { return; } interpolationBuffer.AddRange(pendingInterpolation); pendingInterpolation.Clear(); try { foreach (ProjectileNetworkTransform item in interpolationBuffer) { if (Object.op_Implicit((Object)(object)item) && ((Behaviour)item).isActiveAndEnabled) { Vector3 currentValue = ((NetworkLerpedVector3)(ref interpolatedPositionRef.Invoke(item))).GetCurrentValue(false); Quaternion currentValue2 = ((NetworkLerpedQuaternion)(ref interpolatedRotationRef.Invoke(item))).GetCurrentValue(false); ((Component)item).transform.SetPositionAndRotation(currentValue, currentValue2); Rigidbody val = rbRef.Invoke(item); if (Object.op_Implicit((Object)(object)val)) { val.position = currentValue; val.rotation = currentValue2; } } } } finally { interpolationBuffer.Clear(); } } private static bool HasRemoteClients() { ReadOnlyCollection<NetworkConnection> connections = NetworkServer.connections; for (int i = 0; i < connections.Count; i++) { NetworkConnection val = connections[i]; if (val != null && !Util.ConnectionIsLocal(val)) { return true; } } return false; } private void OnClientConnected(NetworkConnection conn) { if (skippedNetworkWhileAlone && NetworkServer.active) { skippedNetworkWhileAlone = false; ResyncAll(); } } private static void ResyncAll() { ProjectileNetworkTransform val = default(ProjectileNetworkTransform); foreach (ProjectileController instances in InstanceTracker.GetInstancesList<ProjectileController>()) { if (Object.op_Implicit((Object)(object)instances) && ((Component)instances).TryGetComponent<ProjectileNetworkTransform>(ref val) && !val.checkForLocalPlayerAuthority) { val.SetValuesFromTransform(); } } } protected override void OnDisabled() { LateUpdate(); Update(); if (skippedNetworkWhileAlone && NetworkServer.active) { skippedNetworkWhileAlone = false; ResyncAll(); } } } public static class RofStats { public static long EffectsMerged; public static long EffectSendsMerged; public static long SoundEmittersParked; public static long ClientInterpolationsSkipped; public static long MissileSearchesSkipped; public static long InventoryRefreshesSkipped; public static long AnimatorUpdatesSkipped; public static int AnimatorsThrottledNow; public static long RagdollsSettled; public static long GetTypesCacheHits; public static long SoundsMerged; public static long ProjectileInterpolationsOff; public static long AnimatorsCulled; public static long ParticleSystemsCulled; public static long ItemDisplayAnimatorsCulled; public static long ItemDisplayParticlesCulled; public static long IkSolvesSkipped; public static long OverloadSwitches; } internal sealed class StartupScanCache : Feature { private static class ConVarScan { private static FieldInfo allConVarsField; private static FieldInfo archiveConVarsField; private static FieldInfo volumeMasterField; private static MethodInfo registerConVar; public static void Install(Harmony harmony) { //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Expected O, but got Unknown Type type = AccessTools.TypeByName("RoR2BepInExPack.VanillaFixes.FixConVar"); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "LoadAllConVars", (Type[])null, (Type[])null)); allConVarsField = AccessTools.Field(typeof(Console), "allConVars"); archiveConVarsField = AccessTools.Field(typeof(Console), "archiveConVars"); registerConVar = AccessTools.Method(typeof(Console), "RegisterConVarInternal", (Type[])null, (Type[])null); volumeMasterField = AccessTools.Field(typeof(AudioManager), "cvVolumeMaster"); if (methodInfo == null || allConVarsField == null || archiveConVarsField == null || registerConVar == null || volumeMasterField == null) { Plugin.Log.LogInfo((object)"Startup Scan Cache: console-variable scan not cached (RoR2BepInExPack layout differs)"); newConVarTypes["-"] = new List<string>(); } else { harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(ConVarScan), "LoadAllConVarsPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static bool IsMonoFriendlyType(Type type) { if (type.GetFields((BindingFlags)(-1)).Any((FieldInfo fi) => fi.FieldType.Name == "MonoFNPtrFakeClass")) { Plugin.Log.LogDebug((object)$"Not scanning {type} for ConVars due to it containing delegate pointer field(s)"); return false; } return true; } private static bool Declares(Type type) { try { FieldInfo[] fields = type.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < fields.Length; i++) { if (fields[i].FieldType.IsSubclassOf(typeof(BaseConVar))) { return true; } } MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); for (int i = 0; i < methods.Length; i++) { if (((MemberInfo)methods[i]).GetCustomAttribute<ConVarProviderAttribute>() != null) { return true; } } return false; } catch { return true; } } private static bool LoadAllConVarsPrefix() { //IL_0425: Unknown result type (might be due to invalid IL or missing references) //IL_042c: Unknown result type (might be due to invalid IL or missing references) //IL_0228: Unknown result type (might be due to invalid IL or missing references) //IL_0232: Expected O, but got Unknown if (StartupScanCache.instance == null || !StartupScanCache.instance.Enabled) { return true; } Stopwatch stopwatch = Stopwatch.StartNew(); LoadCache(); Console instance = Console.instance; allConVarsField.SetValue(instance, new Dictionary<string, BaseConVar>(StringComparer.InvariantCultureIgnoreCase)); archiveConVarsField.SetValue(instance, new List<BaseConVar>()); Dictionary<string, BaseConVar> dictionary = (Dictionary<string, BaseConVar>)allConVarsField.GetValue(instance); Action<BaseConVar> action = (Action<BaseConVar>)Delegate.CreateDelegate(typeof(Action<BaseConVar>), instance, registerConVar); List<Type> list = new List<Type>(); int num = 0; int num2 = 0; Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { if (assembly.GetCustomAttribute<OptInAttribute>() == null) { continue; } if (!assembly.IsDynamic && cachedConVarTypes != null && cachedConVarTypes.TryGetValue(assembly.FullName, out var value) && TryResolve(assembly, value, out var types)) { list.AddRange(types); newConVarTypes[assembly.FullName] = value; num++; continue; } List<Type> list2 = assembly.GetTypes().Where(IsMonoFriendlyType).Where(Declares) .ToList(); list.AddRange(list2); if (!assembly.IsDynamic) { newConVarTypes[assembly.FullName] = list2.Select((Type t) => t.MetadataToken.ToString()).ToList(); } num2++; } catch (Exception ex) { Plugin.Log.LogDebug((object)ex); } } foreach (Type item in list) { try { FieldInfo[] fields = item.GetFields(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (FieldInfo fieldInfo in fields) { try { if (fieldInfo.FieldType.IsSubclassOf(typeof(BaseConVar))) { if (fieldInfo.IsStatic) { action((BaseConVar)fieldInfo.GetValue(null)); } else if (item.GetCustomAttribute<CompilerGeneratedAttribute>() == null) { Debug.LogError((object)("ConVar defined as " + item.Name + "." + fieldInfo.Name + " could not be registered. ConVars must be static fields.")); } } } catch (Exception ex2) { Plugin.Log.LogDebug((object)ex2); } } MethodInfo[] methods = item.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); foreach (MethodInfo methodInfo in methods) { try { if (((MemberInfo)methodInfo).GetCustomAttribute<ConVarProviderAttribute>() == null) { continue; } if (methodInfo.ReturnType != typeof(IEnumerable<BaseConVar>) || methodInfo.GetParameters().Length != 0) { Debug.LogError((object)"ConVar provider {type.Name}.{methodInfo.Name} does not match the signature \"static IEnumerable<ConVar.BaseConVar>()\"."); continue; } if (!methodInfo.IsStatic) { Debug.LogError((object)("ConVar provider " + item.Name + "." + methodInfo.Name + " could not be invoked. Methods marked with the ConVarProvider attribute must be static.")); continue; } foreach (BaseConVar item2 in (IEnumerable<BaseConVar>)methodInfo.Invoke(null, Array.Empty<object>())) { action(item2); } } catch (Exception ex3) { Plugin.Log.LogDebug((object)ex3); } } } catch (Exception ex4) { Plugin.Log.LogDebug((object)ex4); } } object? value2 = volumeMasterField.GetValue(null); BaseConVar val = (BaseConVar)((value2 is BaseConVar) ? value2 : null); if (val != null) { AccessTools.Field(((object)val).GetType(), "fallbackString")?.SetValue(val, val.GetString()); } foreach (BaseConVar value3 in dictionary.Values) { try { if ((value3.flags & 0x10) != 0) { value3.defaultValue = value3.GetString(); } else if (value3.defaultValue != null) { value3.AttemptSetString(value3.defaultValue); } } catch (Exception ex5) { Plugin.Log.LogError((object)ex5); } } Plugin.Log.LogInfo((object)$"Startup Scan Cache: console-variable scan {stopwatch.Elapsed.TotalSeconds:0.00}s ({num} assemblies from cache, {num2} scanned, {dictionary.Count} console variables)"); if (CacheComplete && num2 > 0) { SaveCache(); } return false; } private static bool TryResolve(Assembly asm, List<string> tokens, out List<Type> types) { types = new List<Type>(tokens.Count); try
patchers/RiskOfFrames.Early.dll
Decompiled 13 hours agousing System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text.RegularExpressions; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Mono.Cecil; using RoR2; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.Scripting; [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("RiskOfFrames.Early")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0")] [assembly: AssemblyProduct("RiskOfFrames.Early")] [assembly: AssemblyTitle("RiskOfFrames.Early")] [assembly: AssemblyVersion("0.1.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 RiskOfFrames.Early { internal static class BundlePrefetch { private static string listPath; private static string newListPath; private static string streamingAssets; private static readonly List<(string path, uint crc)> recorded = new List<(string, uint)>(); private static readonly HashSet<string> recordedSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly Dictionary<string, (object request, uint crc)> prefetched = new Dictionary<string, (object, uint)>(StringComparer.OrdinalIgnoreCase); private static bool started; private static bool recording = true; private static int served; private static FieldInfo transformedId; private static FieldInfo requestOperation; private static FieldInfo provideHandle; private static FieldInfo downloadedBytes; private static FieldInfo options; private static MethodInfo getLoadInfo; private static MethodInfo addCallback; private static MethodInfo localCompleted; private static PropertyInfo crcProperty; private static PropertyInfo loadFinished; private static string pluginsPath; private static bool engineLoaded; public static void Init() { pluginsPath = Path.GetFullPath(Paths.PluginPath); listPath = Path.Combine(Paths.CachePath, "RiskOfFrames-bundles.txt"); newListPath = listPath + ".new"; if (File.Exists(newListPath)) { File.Copy(newListPath, listPath, overwrite: true); File.Delete(newListPath); } } public static void OnAssemblyLoaded(Assembly asm) { if (started || asm.IsDynamic) { return; } string location; try { location = asm.Location; } catch { return; } if (string.IsNullOrEmpty(location) || !Path.GetFullPath(location).StartsWith(pluginsPath, StringComparison.OrdinalIgnoreCase)) { return; } if (!engineLoaded) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { if (assembly.GetName().Name == "UnityEngine.CoreModule") { engineLoaded = true; break; } } catch { } } if (!engineLoaded) { return; } } started = true; Start(); } private static void Start() { try { if (!PatchAddressables()) { return; } streamingAssets = Path.GetFullPath(Application.streamingAssetsPath); if (!File.Exists(listPath)) { return; } string[] array = File.ReadAllLines(listPath); foreach (string text in array) { int num = text.LastIndexOf('|'); if (num > 0) { string text2 = text.Substring(0, num); if (uint.TryParse(text.Substring(num + 1), out var result) && !prefetched.ContainsKey(text2) && File.Exists(text2) && Path.GetFullPath(text2).StartsWith(streamingAssets, StringComparison.OrdinalIgnoreCase)) { prefetched[text2] = (StartLoad(text2, result), result); } } } EarlyPatcher.Log.LogInfo((object)$"Bundle prefetch: started {prefetched.Count} game asset bundles in the background"); } catch (Exception ex) { EarlyPatcher.Log.LogWarning((object)("Bundle prefetch unavailable: " + ex.Message)); } } private static object StartLoad(string path, uint crc) { return AssetBundle.LoadFromFileAsync(path, crc); } private static bool PatchAddressables() { //IL_017e: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Expected O, but got Unknown //IL_01b6: Expected O, but got Unknown Type type = AccessTools.TypeByName("UnityEngine.ResourceManagement.ResourceProviders.AssetBundleResource"); if (type == null) { return false; } transformedId = AccessTools.Field(type, "m_TransformedInternalId"); requestOperation = AccessTools.Field(type, "m_RequestOperation"); provideHandle = AccessTools.Field(type, "m_ProvideHandle"); downloadedBytes = AccessTools.Field(type, "m_DownloadedBytes"); options = AccessTools.Field(type, "m_Options"); getLoadInfo = AccessTools.Method(type, "GetLoadInfo", (Type[])null, (Type[])null); addCallback = AccessTools.Method(type, "AddCallbackInvokeIfDone", (Type[])null, (Type[])null); localCompleted = AccessTools.Method(type, "LocalRequestOperationCompleted", (Type[])null, (Type[])null); MethodInfo methodInfo = AccessTools.Method(type, "BeginOperation", (Type[])null, (Type[])null); crcProperty = AccessTools.Property(AccessTools.TypeByName("UnityEngine.ResourceManagement.ResourceProviders.AssetBundleRequestOptions"), "Crc"); Type type2 = AccessTools.TypeByName("RoR2.RoR2Application"); loadFinished = ((type2 == null) ? null : AccessTools.Property(type2, "loadFinished")); if (transformedId == null || requestOperation == null || provideHandle == null || downloadedBytes == null || options == null || getLoadInfo == null || addCallback == null || localCompleted == null || methodInfo == null || crcProperty == null) { EarlyPatcher.Log.LogInfo((object)"Bundle prefetch: Addressables layout not recognised; disabled"); return false; } new Harmony("daz.RiskOfFrames.Early.BundlePrefetch").Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(BundlePrefetch), "BeginPrefix", (Type[])null), new HarmonyMethod(typeof(BundlePrefetch), "BeginPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } private static uint CrcOf(object resource) { object value = options.GetValue(resource); if (value != null) { return (uint)crcProperty.GetValue(value); } return 0u; } private static bool BeginPrefix(object __instance) { if (prefetched.Count == 0) { return true; } try { object[] array = new object[3] { provideHandle.GetValue(__instance), null, null }; getLoadInfo.Invoke(null, array); if (array[1]?.ToString() != "Local" || !(array[2] is string text)) { return true; } if (!prefetched.TryGetValue(text, out (object, uint) value) || value.Item2 != CrcOf(__instance)) { return true; } prefetched.Remove(text); downloadedBytes.SetValue(__instance, 0L); transformedId.SetValue(__instance, text); requestOperation.SetValue(__instance, value.Item1); Action<AsyncOperation> action = (Action<AsyncOperation>)Delegate.CreateDelegate(typeof(Action<AsyncOperation>), __instance, localCompleted); addCallback.Invoke(__instance, new object[2] { value.Item1, action }); served++; if (prefetched.Count == 0) { EarlyPatcher.Log.LogInfo((object)$"Bundle prefetch: all {served} prefetched bundles were used"); } return false; } catch (Exception ex) { EarlyPatcher.Log.LogWarning((object)("Bundle prefetch hand-over failed, loading normally: " + ex.Message)); return true; } } private static void BeginPostfix(object __instance) { if (!recording) { return; } try { if (loadFinished != null && (bool)loadFinished.GetValue(null)) { recording = false; EarlyPatcher.Log.LogInfo((object)$"Bundle prefetch: recorded {recorded.Count} startup bundles; {served} served from prefetch, {prefetched.Count} prefetched but unused"); return; } string text = transformedId.GetValue(__instance) as string; if (!string.IsNullOrEmpty(text) && !recordedSet.Contains(text) && streamingAssets != null && Path.GetFullPath(text).StartsWith(streamingAssets, StringComparison.OrdinalIgnoreCase)) { recordedSet.Add(text); uint item = CrcOf(__instance); recorded.Add((text, item)); File.AppendAllText(newListPath, text + "|" + item + "\n"); } } catch { } } } internal static class CharacterFixerLoads { private static List<string> deferred; private static bool applied; public static void Apply(Assembly asm) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Expected O, but got Unknown //IL_00ae: Expected O, but got Unknown //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Expected O, but got Unknown if (!applied) { applied = true; Type type = asm.GetType("CharacterFixer.CharacterFixer"); MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "Awake", (Type[])null, (Type[])null)); MethodInfo methodInfo2 = ((type == null) ? null : AccessTools.Method(type, "LoadThing", (Type[])null, (Type[])null)); if (methodInfo == null || methodInfo2 == null) { EarlyPatcher.Log.LogInfo((object)"CharacterFixer layout not recognised; left as is"); return; } Harmony val = new Harmony("daz.RiskOfFrames.Early.CharacterFixer"); val.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(CharacterFixerLoads), "AwakePrefix", (Type[])null), new HarmonyMethod(typeof(CharacterFixerLoads), "AwakePostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)methodInfo2, new HarmonyMethod(typeof(CharacterFixerLoads), "LoadThingPrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); EarlyPatcher.Log.LogInfo((object)"CharacterFixer: startup loads will run in parallel"); } } private static void AwakePrefix() { deferred = new List<string>(); } private static bool LoadThingPrefix(string thing) { if (deferred == null) { return true; } deferred.Add(thing); return false; } private static void AwakePostfix() { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0187: Unknown result type (might be due to invalid IL or missing references) //IL_018c: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Unknown result type (might be due to invalid IL or missing references) List<string> list = deferred; deferred = null; if (list == null || list.Count == 0) { return; } Stopwatch stopwatch = Stopwatch.StartNew(); List<AsyncOperationHandle<ItemDisplayRuleSet>> list2 = new List<AsyncOperationHandle<ItemDisplayRuleSet>>(list.Count); foreach (string item2 in list) { list2.Add(Addressables.LoadAssetAsync<ItemDisplayRuleSet>((object)item2)); } List<(ItemDisplayRuleSet, int, int, AsyncOperationHandle<GameObject>)> list3 = new List<(ItemDisplayRuleSet, int, int, AsyncOperationHandle<GameObject>)>(); foreach (AsyncOperationHandle<ItemDisplayRuleSet> item3 in list2) { ItemDisplayRuleSet val = item3.WaitForCompletion(); if ((Object)(object)val == (Object)null) { continue; } for (int i = 0; i < val.keyAssetRuleGroups.Length; i++) { ItemDisplayRule[] rules = val.keyAssetRuleGroups[i].displayRuleGroup.rules; for (int j = 0; j < rules.Length; j++) { if (!((Object)(object)rules[j].followerPrefab != (Object)null)) { AssetReferenceGameObject followerPrefabAddress = rules[j].followerPrefabAddress; if (followerPrefabAddress != null && ((AssetReference)followerPrefabAddress).RuntimeKeyIsValid()) { list3.Add((val, i, j, ((AssetReferenceT<GameObject>)(object)followerPrefabAddress).LoadAssetAsync())); } } } } } foreach (var item4 in list3) { ref ItemDisplayRule reference = ref item4.Item1.keyAssetRuleGroups[item4.Item2].displayRuleGroup.rules[item4.Item3]; AsyncOperationHandle<GameObject> item = item4.Item4; reference.followerPrefab = item.WaitForCompletion(); } EarlyPatcher.Log.LogInfo((object)$"CharacterFixer: {list.Count} rule sets and {list3.Count} display prefabs loaded in {stopwatch.Elapsed.TotalSeconds:0.00}s"); } } public static class ConfigSaveDeferral { private static readonly object gate = new object(); private static readonly HashSet<ConfigFile> dirty = new HashSet<ConfigFile>(); private static bool deferring; private static int deferredSaves; internal static void Init(Harmony harmony) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown harmony.Patch((MethodBase)AccessTools.Method(typeof(ConfigFile), "Save", (Type[])null, (Type[])null), new HarmonyMethod(typeof(ConfigSaveDeferral), "SavePrefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); deferring = true; AppDomain.CurrentDomain.ProcessExit += delegate { Flush(); }; } private static bool SavePrefix(ConfigFile __instance) { if (!deferring) { return true; } lock (gate) { if (!deferring) { return true; } dirty.Add(__instance); deferredSaves++; return false; } } public static void Flush() { ConfigFile[] array; int num; lock (gate) { if (!deferring) { return; } deferring = false; array = (ConfigFile[])(object)new ConfigFile[dirty.Count]; dirty.CopyTo(array); dirty.Clear(); num = deferredSaves; } Stopwatch stopwatch = Stopwatch.StartNew(); ConfigFile[] array2 = array; foreach (ConfigFile val in array2) { try { val.Save(); } catch (Exception ex) { ManualLogSource log = EarlyPatcher.Log; if (log != null) { log.LogWarning((object)("could not save " + val.ConfigFilePath + ": " + ex.Message)); } } } ManualLogSource log2 = EarlyPatcher.Log; if (log2 != null) { log2.LogInfo((object)$"Config saves: {num} startup saves collapsed into {array.Length} file writes ({stopwatch.Elapsed.TotalSeconds:0.00}s)"); } } } public static class EarlyPatcher { internal static ManualLogSource Log; private static bool parallelLoads = true; private static bool prefetchBundles = false; private static bool deferConfigSaves = true; private static bool tweaksStarted; private static bool graphicsJobs; private static string pluginsPath; public static IEnumerable<string> TargetDLLs { get; } = new string[0]; public static void Patch(AssemblyDefinition assembly) { } public static void Initialize() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown try { Log = Logger.CreateLogSource("RiskOfFrames.Early"); ReadConfig(); GraphicsJobs.Apply(graphicsJobs); if (deferConfigSaves) { ConfigSaveDeferral.Init(new Harmony("daz.RiskOfFrames.Early.Config")); } if (prefetchBundles) { BundlePrefetch.Init(); } if (ModBundlePrefetch.Enabled) { ModBundlePrefetch.Init(); } pluginsPath = Path.GetFullPath(Paths.PluginPath); AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad; } catch (Exception ex) { Console.WriteLine("RiskOfFrames.Early failed to start: " + ex); } } private static void ReadConfig() { string path = Path.Combine(Paths.ConfigPath, "daz.RiskOfFrames.cfg"); if (File.Exists(path)) { string input = File.ReadAllText(path); Match match = Regex.Match(input, "(?m)^Parallel Mod Startup Loads\\s*=\\s*(true|false)"); if (match.Success) { parallelLoads = match.Groups[1].Value == "true"; } match = Regex.Match(input, "(?m)^Prefetch Game Asset Bundles v2\\s*=\\s*(true|false)"); if (match.Success) { prefetchBundles = match.Groups[1].Value == "true"; } match = Regex.Match(input, "(?m)^Defer Config Saving During Startup\\s*=\\s*(true|false)"); if (match.Success) { deferConfigSaves = match.Groups[1].Value == "true"; } match = Regex.Match(input, "(?m)^Prefetch Mod Asset Bundles\\s*=\\s*(true|false)"); if (match.Success) { ModBundlePrefetch.Enabled = match.Groups[1].Value == "true"; } match = Regex.Match(input, "(?m)^Prefetch Embedded Mod Bundles\\s*=\\s*(true|false)"); if (match.Success) { ModBundlePrefetch.Embedded = match.Groups[1].Value == "true"; } match = Regex.Match(input, "(?m)^High Loading Priority During Startup\\s*=\\s*(true|false)"); if (match.Success) { StartupTweaks.HighLoadingPriority = match.Groups[1].Value == "true"; } match = Regex.Match(input, "(?m)^Pause Garbage Collection During Startup\\s*=\\s*(true|false)"); if (match.Success) { StartupTweaks.PauseGc = match.Groups[1].Value == "true"; } match = Regex.Match(input, "(?m)^Skip Log Stack Traces During Startup\\s*=\\s*(true|false)"); if (match.Success) { StartupTweaks.SkipStackTraces = match.Groups[1].Value == "true"; } match = Regex.Match(input, "(?m)^Startup Memory Limit \\(MB\\)\\s*=\\s*(\\d+)"); if (match.Success && long.TryParse(match.Groups[1].Value, out var result) && result >= 1024) { StartupTweaks.MemoryLimitBytes = result << 20; } match = Regex.Match(input, "(?m)^Graphics Jobs \\(Next Launch\\)\\s*=\\s*(true|false)"); if (match.Success) { graphicsJobs = match.Groups[1].Value == "true"; } } } private static bool EngineReady(Assembly asm) { if (asm.IsDynamic) { return false; } string location; try { location = asm.Location; } catch { return false; } if (string.IsNullOrEmpty(location) || !Path.GetFullPath(location).StartsWith(pluginsPath, StringComparison.OrdinalIgnoreCase)) { return false; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { try { if (assembly.GetName().Name == "UnityEngine.CoreModule") { return true; } } catch { } } return false; } private static void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args) { Assembly loadedAssembly = args.LoadedAssembly; string name; try { name = loadedAssembly.GetName().Name; } catch { return; } try { if (tweaksStarted) { StartupTweaks.Check(); } else if (EngineReady(loadedAssembly)) { tweaksStarted = true; StartupTweaks.Start(); if (ModBundlePrefetch.Enabled) { ModBundlePrefetch.Start(); } } } catch (Exception ex) { Log.LogWarning((object)("startup tweaks: " + ex.Message)); } if (prefetchBundles) { try { BundlePrefetch.OnAssemblyLoaded(loadedAssembly); } catch (Exception ex2) { Log.LogWarning((object)("bundle prefetch: " + ex2.Message)); } } try { if (name == "CharacterFixer" && parallelLoads) { CharacterFixerLoads.Apply(loadedAssembly); } } catch (Exception ex3) { Log.LogWarning((object)("could not prepare " + name + ": " + ex3.Message)); } } } internal static class GraphicsJobs { private const string Key = "gfx-enable-gfx-jobs"; private const string Line = "gfx-enable-gfx-jobs=1"; public static void Apply(bool enabled) { try { string text = Path.Combine(Path.Combine(Paths.GameRootPath, Paths.ProcessName + "_Data"), "boot.config"); string path = Path.Combine(Paths.CachePath, "RiskOfFrames-bootconfig.txt"); if (!File.Exists(text)) { return; } List<string> list = File.ReadAllLines(text).ToList(); int num = list.FindIndex((string l) => l.Trim().StartsWith("gfx-enable-gfx-jobs=", StringComparison.Ordinal)); if (enabled) { if (num < 0 || !(list[num].Trim() == "gfx-enable-gfx-jobs=1")) { string text2 = text + ".rof-backup"; if (!File.Exists(text2)) { File.Copy(text, text2); } string contents; if (num >= 0) { contents = "replaced:" + list[num]; list[num] = "gfx-enable-gfx-jobs=1"; } else { contents = "added"; list.Add("gfx-enable-gfx-jobs=1"); } File.WriteAllLines(text, list.ToArray()); Directory.CreateDirectory(Path.GetDirectoryName(path)); File.WriteAllText(path, contents); EarlyPatcher.Log.LogInfo((object)"Graphics jobs: enabled in boot.config; takes effect at the next launch"); } } else { if (!File.Exists(path)) { return; } string text3 = File.ReadAllText(path).Trim(); if (num >= 0 && list[num].Trim() == "gfx-enable-gfx-jobs=1") { if (text3.StartsWith("replaced:", StringComparison.Ordinal)) { list[num] = text3.Substring("replaced:".Length); } else { list.RemoveAt(num); } File.WriteAllLines(text, list.ToArray()); } File.Delete(path); EarlyPatcher.Log.LogInfo((object)"Graphics jobs: removed from boot.config; back to normal at the next launch"); } } catch (Exception ex) { EarlyPatcher.Log.LogWarning((object)("Graphics jobs: could not update boot.config: " + ex.Message)); } } } internal static class ModBundlePrefetch { internal static bool Enabled = true; internal static bool Embedded = false; private static string listPath; private static string newListPath; private static string pluginsPath; private static string memDir; private const string MemPrefix = "mem:"; [ThreadStatic] private static byte[] pendingBytes; [ThreadStatic] private static string pendingKey; private static readonly Dictionary<string, object> prefetched = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> recorded = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static bool recording = true; private static int served; private static int started; public static void Init() { pluginsPath = Path.GetFullPath(Paths.PluginPath); listPath = Path.Combine(Paths.CachePath, "RiskOfFrames-modbundles.txt"); newListPath = listPath + ".new"; memDir = Path.Combine(Paths.CachePath, "RiskOfFrames-bundles"); if (File.Exists(newListPath)) { File.Copy(newListPath, listPath, overwrite: true); File.Delete(newListPath); } } private static string Stamp(string path) { FileInfo fileInfo = new FileInfo(path); return fileInfo.Length + "|" + fileInfo.LastWriteTimeUtc.Ticks; } public static void Start() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Expected O, but got Unknown //IL_0068: Expected O, but got Unknown //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Expected O, but got Unknown //IL_00c8: Expected O, but got Unknown //IL_0113: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_0135: Expected O, but got Unknown //IL_0135: Expected O, but got Unknown //IL_01c7: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01e9: Expected O, but got Unknown //IL_01e9: Expected O, but got Unknown //IL_022a: Unknown result type (might be due to invalid IL or missing references) //IL_023f: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Expected O, but got Unknown //IL_024c: Expected O, but got Unknown try { Harmony val = new Harmony("daz.RiskOfFrames.Early.ModBundles"); Type typeFromHandle = typeof(AssetBundle); val.Patch((MethodBase)AccessTools.Method(typeFromHandle, "LoadFromFile", new Type[1] { typeof(string) }, (Type[])null), new HarmonyMethod(typeof(ModBundlePrefetch), "Prefix1", (Type[])null), new HarmonyMethod(typeof(ModBundlePrefetch), "Postfix1", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.Method(typeFromHandle, "LoadFromFile", new Type[2] { typeof(string), typeof(uint) }, (Type[])null), new HarmonyMethod(typeof(ModBundlePrefetch), "Prefix2", (Type[])null), new HarmonyMethod(typeof(ModBundlePrefetch), "Postfix2", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); val.Patch((MethodBase)AccessTools.Method(typeFromHandle, "LoadFromFile", new Type[3] { typeof(string), typeof(uint), typeof(ulong) }, (Type[])null), new HarmonyMethod(typeof(ModBundlePrefetch), "Prefix3", (Type[])null), new HarmonyMethod(typeof(ModBundlePrefetch), "Postfix3", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); foreach (MethodInfo item in Embedded ? AccessTools.GetDeclaredMethods(typeFromHandle) : new List<MethodInfo>()) { if (!item.IsStatic || item.ReturnType != typeFromHandle) { continue; } ParameterInfo[] parameters = item.GetParameters(); if (parameters.Length != 0) { if (item.Name == "LoadFromMemory" && parameters[0].ParameterType == typeof(byte[])) { val.Patch((MethodBase)item, new HarmonyMethod(typeof(ModBundlePrefetch), "MemoryPrefix", (Type[])null), new HarmonyMethod(typeof(ModBundlePrefetch), "MemoryPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } else if (item.Name == "LoadFromStream" && parameters[0].ParameterType == typeof(Stream)) { val.Patch((MethodBase)item, new HarmonyMethod(typeof(ModBundlePrefetch), "StreamPrefix", (Type[])null), new HarmonyMethod(typeof(ModBundlePrefetch), "MemoryPostfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } } if (!File.Exists(listPath)) { return; } string[] array = File.ReadAllLines(listPath); foreach (string text in array) { int num = text.IndexOf('|'); if (num <= 0) { continue; } string text2 = text.Substring(0, num); if (prefetched.ContainsKey(text2)) { continue; } if (text2.StartsWith("mem:", StringComparison.Ordinal)) { if (Embedded) { string text3 = Path.Combine(memDir, text2.Substring("mem:".Length) + ".bundle"); if (File.Exists(text3) && !(new FileInfo(text3).Length.ToString() != text.Substring(num + 1))) { prefetched[text2] = AssetBundle.LoadFromFileAsync(text3); started++; } } } else if (File.Exists(text2) && !(Stamp(text2) != text.Substring(num + 1))) { prefetched[text2] = AssetBundle.LoadFromFileAsync(text2); started++; } } EarlyPatcher.Log.LogInfo((object)$"Mod bundle prefetch: started opening {started} mod asset bundles in the background"); } catch (Exception ex) { EarlyPatcher.Log.LogWarning((object)("Mod bundle prefetch unavailable: " + ex.Message)); } } private static bool Serve(string path, uint crc, ulong offset, ref AssetBundle result) { if (prefetched.Count == 0 || crc != 0 || offset != 0L || string.IsNullOrEmpty(path)) { return true; } string fullPath; try { fullPath = Path.GetFullPath(path); } catch { return true; } if (prefetched.TryGetValue(fullPath, out var value)) { AssetBundleCreateRequest val = (AssetBundleCreateRequest)((value is AssetBundleCreateRequest) ? value : null); if (val != null) { prefetched.Remove(fullPath); AssetBundle assetBundle = val.assetBundle; if (!Object.op_Implicit((Object)(object)assetBundle)) { return true; } result = assetBundle; served++; return false; } } return true; } private static void Record(string path, uint crc, ulong offset, AssetBundle result) { if (!recording || !Object.op_Implicit((Object)(object)result) || crc != 0 || offset != 0L || string.IsNullOrEmpty(path)) { return; } try { string fullPath = Path.GetFullPath(path); if (fullPath.StartsWith(pluginsPath, StringComparison.OrdinalIgnoreCase) && recorded.Add(fullPath)) { File.AppendAllText(newListPath, fullPath + "|" + Stamp(fullPath) + "\n"); } } catch { } } private static string Key(byte[] bytes) { using SHA1 sHA = SHA1.Create(); return "mem:" + BitConverter.ToString(sHA.ComputeHash(bytes)).Replace("-", ""); } private static bool ServeMemory(byte[] bytes, uint crc, ref AssetBundle result) { pendingBytes = null; pendingKey = null; if (bytes == null || bytes.Length < 1024 || crc != 0) { return true; } string key; try { key = Key(bytes); } catch { return true; } pendingKey = key; pendingBytes = bytes; if (prefetched.TryGetValue(key, out var value)) { AssetBundleCreateRequest val = (AssetBundleCreateRequest)((value is AssetBundleCreateRequest) ? value : null); if (val != null) { prefetched.Remove(key); AssetBundle assetBundle = val.assetBundle; if (!Object.op_Implicit((Object)(object)assetBundle)) { return true; } result = assetBundle; served++; return false; } } return true; } private static bool MemoryPrefix(ref AssetBundle __result, object[] __args) { uint crc = ((__args.Length > 1 && __args[1] is uint num) ? num : 0u); return ServeMemory(__args[0] as byte[], crc, ref __result); } private static bool StreamPrefix(ref AssetBundle __result, object[] __args) { Stream stream = __args[0] as Stream; pendingBytes = null; pendingKey = null; uint num = ((__args.Length > 1 && __args[1] is uint num2) ? num2 : 0u); if (stream == null || !stream.CanSeek || !stream.CanRead || num != 0) { return true; } byte[] array; try { long position = stream.Position; long num3 = stream.Length - position; if (num3 < 1024 || num3 > int.MaxValue) { return true; } array = new byte[num3]; int i; int num4; for (i = 0; i < array.Length; i += num4) { num4 = stream.Read(array, i, array.Length - i); if (num4 <= 0) { break; } } stream.Position = position; if (i != array.Length) { return true; } } catch { return true; } return ServeMemory(array, 0u, ref __result); } private static void MemoryPostfix(AssetBundle __result) { byte[] array = pendingBytes; string text = pendingKey; pendingBytes = null; pendingKey = null; if (!recording || !Object.op_Implicit((Object)(object)__result) || array == null || text == null || !recorded.Add(text)) { return; } try { Directory.CreateDirectory(memDir); string text2 = Path.Combine(memDir, text.Substring("mem:".Length) + ".bundle"); if (!File.Exists(text2) || new FileInfo(text2).Length != array.Length) { File.WriteAllBytes(text2 + ".tmp", array); if (File.Exists(text2)) { File.Delete(text2); } File.Move(text2 + ".tmp", text2); } File.AppendAllText(newListPath, text + "|" + array.Length + "\n"); } catch { } } private static bool Prefix1(string path, ref AssetBundle __result) { return Serve(path, 0u, 0uL, ref __result); } private static bool Prefix2(string path, uint crc, ref AssetBundle __result) { return Serve(path, crc, 0uL, ref __result); } private static bool Prefix3(string path, uint crc, ulong offset, ref AssetBundle __result) { return Serve(path, crc, offset, ref __result); } private static void Postfix1(string path, AssetBundle __result) { Record(path, 0u, 0uL, __result); } private static void Postfix2(string path, uint crc, AssetBundle __result) { Record(path, crc, 0uL, __result); } private static void Postfix3(string path, uint crc, ulong offset, AssetBundle __result) { Record(path, crc, offset, __result); } public static void Finish() { //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (!recording) { return; } recording = false; int num = 0; foreach (object value in prefetched.Values) { try { AssetBundle assetBundle = ((AssetBundleCreateRequest)value).assetBundle; if (Object.op_Implicit((Object)(object)assetBundle)) { assetBundle.Unload(true); num++; } } catch { } } prefetched.Clear(); try { if (Directory.Exists(memDir)) { string[] files = Directory.GetFiles(memDir); foreach (string text in files) { string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(text); if (text.EndsWith(".tmp", StringComparison.Ordinal) || !recorded.Contains("mem:" + fileNameWithoutExtension)) { File.Delete(text); } } } } catch { } if (started > 0 || recorded.Count > 0) { EarlyPatcher.Log.LogInfo((object)$"Mod bundle prefetch: {served} of {started} prefetched mod bundles used ({num} unused, closed); {recorded.Count} recorded for next launch"); } } } internal static class StartupTweaks { internal static bool PauseGc = true; internal static bool SkipStackTraces = true; internal static long MemoryLimitBytes = 5368709120L; private static bool gcPaused; private static bool tracesOff; private static int savedLogTrace = -1; private static int savedWarningTrace = -1; internal static bool HighLoadingPriority = true; private static int savedLoadingPriority = -1; private static int collectionsAtStart; private static readonly Stopwatch clock = new Stopwatch(); public static void Start() { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected I4, but got Unknown //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected I4, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected I4, but got Unknown //IL_00c7: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Invalid comparison between Unknown and I4 clock.Start(); if (HighLoadingPriority) { try { savedLoadingPriority = (int)Application.backgroundLoadingPriority; Application.backgroundLoadingPriority = (ThreadPriority)4; } catch (Exception ex) { EarlyPatcher.Log.LogWarning((object)("loading priority: " + ex.Message)); } } if (SkipStackTraces) { try { savedLogTrace = (int)Application.GetStackTraceLogType((LogType)3); savedWarningTrace = (int)Application.GetStackTraceLogType((LogType)2); Application.SetStackTraceLogType((LogType)3, (StackTraceLogType)0); Application.SetStackTraceLogType((LogType)2, (StackTraceLogType)0); tracesOff = true; } catch (Exception ex2) { EarlyPatcher.Log.LogWarning((object)("log stack traces: " + ex2.Message)); } } if (!PauseGc) { return; } try { long num = (long)SystemInfo.systemMemorySize << 20; if (num > 0) { MemoryLimitBytes = Math.Min(MemoryLimitBytes, Math.Max(1073741824L, num / 3)); } if ((int)GarbageCollector.GCMode == 1) { collectionsAtStart = GC.CollectionCount(0); GarbageCollector.GCMode = (Mode)0; gcPaused = true; EarlyPatcher.Log.LogInfo((object)$"Garbage collection paused for startup (heap {GC.GetTotalMemory(forceFullCollection: false) >> 20} MB, limit {MemoryLimitBytes >> 20} MB)"); } } catch (Exception ex3) { EarlyPatcher.Log.LogWarning((object)("could not pause garbage collection: " + ex3.Message)); } } public static void Check() { if (gcPaused && GC.GetTotalMemory(forceFullCollection: false) > MemoryLimitBytes) { ResumeGc("memory limit reached", collectNow: false); } } private static void ResumeGc(string why, bool collectNow) { if (!gcPaused) { return; } gcPaused = false; long totalMemory = GC.GetTotalMemory(forceFullCollection: false); Stopwatch stopwatch = Stopwatch.StartNew(); try { GarbageCollector.GCMode = (Mode)1; if (collectNow) { GC.Collect(); } } catch (Exception ex) { EarlyPatcher.Log.LogWarning((object)("could not resume garbage collection: " + ex.Message)); } EarlyPatcher.Log.LogInfo((object)($"Garbage collection resumed after {clock.Elapsed.TotalSeconds:0.0}s ({why}); heap {totalMemory >> 20} MB" + (collectNow ? $" -> {GC.GetTotalMemory(forceFullCollection: false) >> 20} MB after one collection ({stopwatch.Elapsed.TotalSeconds:0.00}s)" : ""))); } public static void End() { if (ModBundlePrefetch.Enabled) { try { ModBundlePrefetch.Finish(); } catch (Exception ex) { EarlyPatcher.Log.LogWarning((object)("mod bundle prefetch: " + ex.Message)); } } Check(); ResumeGc("startup finished", collectNow: true); if (savedLoadingPriority >= 0) { try { Application.backgroundLoadingPriority = (ThreadPriority)savedLoadingPriority; } catch { } savedLoadingPriority = -1; } if (tracesOff) { tracesOff = false; try { Application.SetStackTraceLogType((LogType)3, (StackTraceLogType)savedLogTrace); Application.SetStackTraceLogType((LogType)2, (StackTraceLogType)savedWarningTrace); } catch { } } } } }