using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETFramework,Version=v4.7.1", FrameworkDisplayName = ".NET Framework 4.7.1")] [assembly: AssemblyCompany("MoreOptimization")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("More Graphics Settings for Gamble With Your Friends")] [assembly: AssemblyFileVersion("1.0.1.0")] [assembly: AssemblyInformationalVersion("1.0.1")] [assembly: AssemblyProduct("MoreOptimization")] [assembly: AssemblyTitle("MoreOptimization")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.1.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 MoreGraphicsSettings { [BepInPlugin("com.ananas1k.gwyf.moreoptimization", "More Optimization", "1.0.1")] public class Plugin : BaseUnityPlugin { private const string PluginGuid = "com.ananas1k.gwyf.moreoptimization"; private const string PluginName = "More Optimization"; private const string PluginVersion = "1.0.1"; private const float TextureLimitSceneDelayFallback = 1.75f; private const float TextureLimitEnforcementDurationFallback = 10f; private const float TextureLimitEnforcementInterval = 0.5f; private const float ContinuousQualityEnforcementInterval = 1f; private const int TextureCopyFixLogLimit = 5; public static ConfigEntry ConfigGraphicsPreset; public static ConfigEntry ConfigRenderScale; public static ConfigEntry ConfigTextureResolution; public static ConfigEntry ConfigShadows; public static ConfigEntry ConfigVSync; public static ConfigEntry ConfigAntiAliasing; public static ConfigEntry ConfigTargetFPS; public static ConfigEntry ConfigLodBias; public static ConfigEntry ConfigAnisotropicFiltering; public static ConfigEntry ConfigReflectionProbes; public static ConfigEntry ConfigSoftParticles; public static ConfigEntry ConfigBackgroundFpsLimit; public static ConfigEntry ConfigLocalizationLanguage; private static ConfigEntry ConfigTextureLimitDelay; private static ConfigEntry ConfigTextureLimitEnforcementDuration; private static ConfigEntry ConfigContinuousQualityEnforcement; private static ConfigEntry ConfigRtlcTextureCopyFix; private static Plugin Instance; private Harmony harmony; private Coroutine textureLimitRoutine; private Coroutine continuousQualityRoutine; private static bool syncingSettingsUi; private static float nextTextureLimitApplyTime; private static bool gameHasFocus = true; private static bool gamePaused; private static bool useRussianText; private static bool textureCopyFallbackInProgress; private static int textureCopyFixLogCount; private static readonly float[] RenderScales = new float[5] { 0.5f, 0.6f, 0.75f, 0.9f, 1f }; private static readonly int[] TargetFpsValues = new int[5] { -1, 30, 60, 120, 144 }; private static readonly int[] QualityAntiAliasingLevels = new int[4] { 0, 2, 4, 8 }; private static readonly int[] UrpMsaaSampleCounts = new int[4] { 1, 2, 4, 8 }; private static readonly float[] LodBiasValues = new float[4] { 0.5f, 0.75f, 1f, 1.5f }; private void Awake() { //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007a: Expected O, but got Unknown Instance = this; BindConfig(); useRussianText = ShouldUseRussianText(); if (ConfigGraphicsPreset.Value != 0) { ApplyPreset(ConfigGraphicsPreset.Value); } PrepareTextureCopyWindow(); ApplyGraphicsSettings(applyTextureLimit: false); QueueDeferredTextureLimit(); StartContinuousQualityEnforcer(); SceneManager.sceneLoaded += OnSceneLoaded; SceneManager.activeSceneChanged += OnActiveSceneChanged; harmony = new Harmony("com.ananas1k.gwyf.moreoptimization"); int num = 0; num += (PatchMethod("GraphicsSettingsApplier", "ApplyAll", null, "PatchGraphicsSettingsApplier_ApplyAll") ? 1 : 0); num += (PatchMethod("SettingsLayoutRuntimeUI", "Awake", "PatchSettingsLayoutAwake", null) ? 1 : 0); num += (PatchMethod("SettingsLayout", "NotifyChanged", null, "PatchNotifyChanged") ? 1 : 0); num += (PatchMethod("Outline", "SmoothNormals", "PatchOutlineSmoothNormals", null) ? 1 : 0); num += PatchGraphicsCopyTextureMethods(); ((BaseUnityPlugin)this).Logger.LogInfo((object)string.Format("{0} {1} loaded. Active patches: {2}. URP, RTLC texture copy fix {3}, and {4} UI text enabled.", "More Optimization", "1.0.1", num, (ConfigRtlcTextureCopyFix.Value == 1) ? "enabled" : "disabled", useRussianText ? "Russian" : "English")); } private void OnDestroy() { SceneManager.sceneLoaded -= OnSceneLoaded; SceneManager.activeSceneChanged -= OnActiveSceneChanged; if (continuousQualityRoutine != null) { ((MonoBehaviour)this).StopCoroutine(continuousQualityRoutine); } if (harmony != null) { harmony.UnpatchSelf(); } } private void BindConfig() { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Expected O, but got Unknown //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Expected O, but got Unknown //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: Expected O, but got Unknown //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Expected O, but got Unknown //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Expected O, but got Unknown //IL_0148: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Expected O, but got Unknown //IL_0179: Unknown result type (might be due to invalid IL or missing references) //IL_0183: Expected O, but got Unknown //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Expected O, but got Unknown //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_01e5: Expected O, but got Unknown //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Expected O, but got Unknown //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0247: Expected O, but got Unknown //IL_027a: Unknown result type (might be due to invalid IL or missing references) //IL_0284: Expected O, but got Unknown //IL_02b7: Unknown result type (might be due to invalid IL or missing references) //IL_02c1: Expected O, but got Unknown //IL_02e8: Unknown result type (might be due to invalid IL or missing references) //IL_02f2: Expected O, but got Unknown //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Expected O, but got Unknown //IL_034a: Unknown result type (might be due to invalid IL or missing references) //IL_0354: Expected O, but got Unknown ConfigGraphicsPreset = ((BaseUnityPlugin)this).Config.Bind("Graphics", "Preset", 0, new ConfigDescription("0=Custom, 1=Potato, 2=Low, 3=Medium, 4=High, 5=Ultra", (AcceptableValueBase)(object)new AcceptableValueRange(0, 5), Array.Empty())); ConfigRenderScale = ((BaseUnityPlugin)this).Config.Bind("Graphics", "RenderScale", 4, new ConfigDescription("0=0.5x, 1=0.6x, 2=0.75x, 3=0.9x, 4=1.0x", (AcceptableValueBase)(object)new AcceptableValueRange(0, 4), Array.Empty())); ConfigTextureResolution = ((BaseUnityPlugin)this).Config.Bind("Graphics", "TextureResolution", 0, new ConfigDescription("0=Full, 1=Half, 2=Quarter, 3=Eighth", (AcceptableValueBase)(object)new AcceptableValueRange(0, 3), Array.Empty())); ConfigShadows = ((BaseUnityPlugin)this).Config.Bind("Graphics", "Shadows", 2, new ConfigDescription("0=Disable, 1=HardOnly, 2=All", (AcceptableValueBase)(object)new AcceptableValueRange(0, 2), Array.Empty())); ConfigVSync = ((BaseUnityPlugin)this).Config.Bind("Graphics", "VSync", 0, new ConfigDescription("0=Off, 1=On", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1), Array.Empty())); ConfigAntiAliasing = ((BaseUnityPlugin)this).Config.Bind("Graphics", "AntiAliasing", 0, new ConfigDescription("0=Off, 1=2x, 2=4x, 3=8x", (AcceptableValueBase)(object)new AcceptableValueRange(0, 3), Array.Empty())); ConfigTargetFPS = ((BaseUnityPlugin)this).Config.Bind("Graphics", "TargetFPS", 0, new ConfigDescription("0=Uncapped, 1=30, 2=60, 3=120, 4=144", (AcceptableValueBase)(object)new AcceptableValueRange(0, 4), Array.Empty())); ConfigLodBias = ((BaseUnityPlugin)this).Config.Bind("Graphics", "LodBias", 2, new ConfigDescription("0=Low, 1=Balanced, 2=Default, 3=High", (AcceptableValueBase)(object)new AcceptableValueRange(0, 3), Array.Empty())); ConfigAnisotropicFiltering = ((BaseUnityPlugin)this).Config.Bind("Graphics", "AnisotropicFiltering", 1, new ConfigDescription("0=Disable, 1=Enable, 2=ForceEnable", (AcceptableValueBase)(object)new AcceptableValueRange(0, 2), Array.Empty())); ConfigReflectionProbes = ((BaseUnityPlugin)this).Config.Bind("Graphics", "RealtimeReflectionProbes", 1, new ConfigDescription("0=Off, 1=On", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1), Array.Empty())); ConfigSoftParticles = ((BaseUnityPlugin)this).Config.Bind("Graphics", "SoftParticles", 1, new ConfigDescription("0=Off, 1=On", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1), Array.Empty())); ConfigBackgroundFpsLimit = ((BaseUnityPlugin)this).Config.Bind("Graphics", "BackgroundFPSLimit", 1, new ConfigDescription("0=Off, 1=10 FPS. Limits FPS while the game is minimized or unfocused.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1), Array.Empty())); ConfigTextureLimitDelay = ((BaseUnityPlugin)this).Config.Bind("Compatibility", "TextureLimitSceneDelay", 1.75f, new ConfigDescription("Delay in seconds before applying Texture Resolution after a scene loads. Helps texture replacement mods avoid CopyTexture mipmap errors.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 5f), Array.Empty())); ConfigTextureLimitEnforcementDuration = ((BaseUnityPlugin)this).Config.Bind("Compatibility", "TextureLimitEnforcementDuration", 10f, new ConfigDescription("How long Texture Resolution is re-applied after startup/scene/settings changes. Keeps Potato/Low texture quality from being reset by the game.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 30f), Array.Empty())); ConfigContinuousQualityEnforcement = ((BaseUnityPlugin)this).Config.Bind("Compatibility", "ContinuousQualityEnforcement", 1, new ConfigDescription("0=Off, 1=On. Re-applies More Optimization graphics settings while playing so level loads cannot reset Potato/Low texture quality or render scale.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1), Array.Empty())); ConfigRtlcTextureCopyFix = ((BaseUnityPlugin)this).Config.Bind("Compatibility", "RTLCTextureCopyFix", 1, new ConfigDescription("0=Off, 1=On. Falls back to Graphics.ConvertTexture when RTLC texture replacements hit a CopyTexture mipmap-count mismatch.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 1), Array.Empty())); ConfigLocalizationLanguage = ((BaseUnityPlugin)this).Config.Bind("Localization", "Language", 0, new ConfigDescription("0=Auto, 1=English, 2=Russian. Auto uses Russian text when RTLC/XUnity Russian localization is detected.", (AcceptableValueBase)(object)new AcceptableValueRange(0, 2), Array.Empty())); } private static bool ShouldUseRussianText() { if (ConfigLocalizationLanguage != null) { if (ConfigLocalizationLanguage.Value == 1) { return false; } if (ConfigLocalizationLanguage.Value == 2) { return true; } } return IsRussianLocalizationDetected(); } private static bool IsRussianLocalizationDetected() { try { foreach (KeyValuePair pluginInfo in Chainloader.PluginInfos) { string text = pluginInfo.Key ?? string.Empty; string text2 = ((pluginInfo.Value != null && pluginInfo.Value.Metadata != null) ? (pluginInfo.Value.Metadata.Name ?? string.Empty) : string.Empty); string text3 = ((pluginInfo.Value != null && pluginInfo.Value.Metadata != null) ? (pluginInfo.Value.Metadata.GUID ?? string.Empty) : string.Empty); string text4 = (text + " " + text2 + " " + text3).ToLowerInvariant(); if (text4.Contains("rtlc.gwyf") || text4.Contains("russian") || text4.Contains("рус")) { return true; } } } catch { } try { if (Directory.Exists(Path.Combine(Paths.PluginPath, "RTLC-GWYF_Russian_Translation"))) { return true; } if (File.Exists(Path.Combine(Paths.ConfigPath, "Translation", "ru", "Text", "Translate.txt"))) { return true; } } catch { } return false; } private bool PatchMethod(string typeName, string methodName, string prefixName, string postfixName) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) try { Type type = AccessTools.TypeByName(typeName); if (type == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find type " + typeName + ". This patch will be skipped.")); return false; } MethodInfo methodInfo = AccessTools.Method(type, methodName, (Type[])null, (Type[])null); if (methodInfo == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Could not find method " + typeName + "." + methodName + ". This patch will be skipped.")); return false; } HarmonyMethod val = ((prefixName == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Plugin), prefixName, (Type[])null)); HarmonyMethod val2 = ((postfixName == null) ? ((HarmonyMethod)null) : new HarmonyMethod(typeof(Plugin), postfixName, (Type[])null)); harmony.Patch((MethodBase)methodInfo, val, val2, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to patch " + typeName + "." + methodName + ": " + ex.Message)); return false; } } private int PatchGraphicsCopyTextureMethods() { return 0 + (PatchGraphicsCopyTextureMethod(new Type[2] { typeof(Texture), typeof(Texture) }, "PatchGraphicsCopyTexture") ? 1 : 0) + (PatchGraphicsCopyTextureMethod(new Type[4] { typeof(Texture), typeof(int), typeof(Texture), typeof(int) }, "PatchGraphicsCopyTextureElement") ? 1 : 0); } private bool PatchGraphicsCopyTextureMethod(Type[] parameterTypes, string prefixName) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Expected O, but got Unknown try { MethodInfo methodInfo = AccessTools.Method(typeof(Graphics), "CopyTexture", parameterTypes, (Type[])null); if (methodInfo == null) { ((BaseUnityPlugin)this).Logger.LogWarning((object)"Could not find UnityEngine.Graphics.CopyTexture overload. RTLC texture copy fix will be skipped for this overload."); return false; } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(Plugin), prefixName, (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Failed to patch UnityEngine.Graphics.CopyTexture: " + ex.Message)); return false; } } private void OnSceneLoaded(Scene scene, LoadSceneMode mode) { PrepareTextureCopyWindow(); ApplyGraphicsSettings(applyTextureLimit: false); QueueDeferredTextureLimit(); } private void OnActiveSceneChanged(Scene oldScene, Scene newScene) { PrepareTextureCopyWindow(); ApplyGraphicsSettings(applyTextureLimit: false); QueueDeferredTextureLimit(); } private void OnApplicationFocus(bool hasFocus) { gameHasFocus = hasFocus; ApplyGraphicsSettings(applyTextureLimit: false); } private void OnApplicationPause(bool pauseStatus) { gamePaused = pauseStatus; ApplyGraphicsSettings(applyTextureLimit: false); } public static void ApplyPreset(int presetIndex) { presetIndex = ClampOption(presetIndex, 0, 5); switch (presetIndex) { case 1: ConfigRenderScale.Value = 0; ConfigTextureResolution.Value = 3; ConfigShadows.Value = 0; ConfigAntiAliasing.Value = 0; ConfigTargetFPS.Value = 1; ConfigLodBias.Value = 0; ConfigAnisotropicFiltering.Value = 0; ConfigReflectionProbes.Value = 0; ConfigSoftParticles.Value = 0; ConfigVSync.Value = 0; break; case 2: ConfigRenderScale.Value = 2; ConfigTextureResolution.Value = 2; ConfigShadows.Value = 0; ConfigAntiAliasing.Value = 0; ConfigTargetFPS.Value = 2; ConfigLodBias.Value = 1; ConfigAnisotropicFiltering.Value = 0; ConfigReflectionProbes.Value = 0; ConfigSoftParticles.Value = 0; ConfigVSync.Value = 0; break; case 3: ConfigRenderScale.Value = 4; ConfigTextureResolution.Value = 1; ConfigShadows.Value = 1; ConfigAntiAliasing.Value = 1; ConfigTargetFPS.Value = 2; ConfigLodBias.Value = 2; ConfigAnisotropicFiltering.Value = 1; ConfigReflectionProbes.Value = 0; ConfigSoftParticles.Value = 0; ConfigVSync.Value = 0; break; case 4: ConfigRenderScale.Value = 4; ConfigTextureResolution.Value = 0; ConfigShadows.Value = 2; ConfigAntiAliasing.Value = 2; ConfigTargetFPS.Value = 4; ConfigLodBias.Value = 2; ConfigAnisotropicFiltering.Value = 1; ConfigReflectionProbes.Value = 1; ConfigSoftParticles.Value = 1; ConfigVSync.Value = 0; break; case 5: ConfigRenderScale.Value = 4; ConfigTextureResolution.Value = 0; ConfigShadows.Value = 2; ConfigAntiAliasing.Value = 3; ConfigTargetFPS.Value = 0; ConfigLodBias.Value = 3; ConfigAnisotropicFiltering.Value = 2; ConfigReflectionProbes.Value = 1; ConfigSoftParticles.Value = 1; ConfigVSync.Value = 0; break; } } public static void ApplyGraphicsSettings() { ApplyGraphicsSettings(applyTextureLimit: true, forceTextureLimit: true); } private static void ApplyGraphicsSettings(bool applyTextureLimit, bool forceTextureLimit = false) { if (ConfigRenderScale != null) { if (applyTextureLimit) { ApplyTextureLimit(forceTextureLimit); } ApplyUnityQualitySettings(); ApplyRenderPipelineSettings(); } } private static void ApplyUnityQualitySettings() { if (IsBackgroundFpsCapActive()) { QualitySettings.vSyncCount = 0; Application.targetFrameRate = 10; } else { QualitySettings.vSyncCount = ClampOption(ConfigVSync.Value, 0, 1); Application.targetFrameRate = TargetFpsValues[ClampOption(ConfigTargetFPS.Value, 0, TargetFpsValues.Length - 1)]; } switch (ClampOption(ConfigShadows.Value, 0, 2)) { case 0: QualitySettings.shadows = (ShadowQuality)0; QualitySettings.shadowCascades = 0; QualitySettings.shadowDistance = 0f; break; case 1: QualitySettings.shadows = (ShadowQuality)1; QualitySettings.shadowCascades = 1; QualitySettings.shadowDistance = 75f; break; default: QualitySettings.shadows = (ShadowQuality)2; QualitySettings.shadowCascades = 4; QualitySettings.shadowDistance = 150f; break; } QualitySettings.antiAliasing = QualityAntiAliasingLevels[ClampOption(ConfigAntiAliasing.Value, 0, QualityAntiAliasingLevels.Length - 1)]; QualitySettings.lodBias = LodBiasValues[ClampOption(ConfigLodBias.Value, 0, LodBiasValues.Length - 1)]; QualitySettings.anisotropicFiltering = (AnisotropicFiltering)ClampOption(ConfigAnisotropicFiltering.Value, 0, 2); QualitySettings.realtimeReflectionProbes = ConfigReflectionProbes.Value == 1; QualitySettings.softParticles = ConfigSoftParticles.Value == 1; } private static bool IsBackgroundFpsCapActive() { if (ConfigBackgroundFpsLimit != null && ConfigBackgroundFpsLimit.Value == 1) { if (gameHasFocus) { return gamePaused; } return true; } return false; } private static void ApplyRenderPipelineSettings() { RenderPipelineAsset val = GraphicsSettings.currentRenderPipeline ?? QualitySettings.renderPipeline; if (!((Object)(object)val == (Object)null)) { Type type = ((object)val).GetType(); float num = RenderScales[ClampOption(ConfigRenderScale.Value, 0, RenderScales.Length - 1)]; TrySetProperty(type, val, "renderScale", num); switch (ClampOption(ConfigShadows.Value, 0, 2)) { case 0: TrySetProperty(type, val, "shadowDistance", 0f); TrySetProperty(type, val, "shadowCascadeCount", 0); break; case 1: TrySetProperty(type, val, "shadowDistance", 75f); TrySetProperty(type, val, "shadowCascadeCount", 1); break; default: TrySetProperty(type, val, "shadowDistance", 150f); TrySetProperty(type, val, "shadowCascadeCount", 4); break; } int num2 = UrpMsaaSampleCounts[ClampOption(ConfigAntiAliasing.Value, 0, UrpMsaaSampleCounts.Length - 1)]; TrySetProperty(type, val, "msaaSampleCount", num2); } } private static void ApplyTextureLimit(bool force = false) { if (ConfigTextureResolution != null && (force || !(Time.realtimeSinceStartup < nextTextureLimitApplyTime))) { QualitySettings.globalTextureMipmapLimit = ClampOption(ConfigTextureResolution.Value, 0, 3); } } private static void PrepareTextureCopyWindow() { if (ConfigTextureResolution != null) { float num = ((ConfigTextureLimitDelay == null) ? 1.75f : Mathf.Clamp(ConfigTextureLimitDelay.Value, 0f, 5f)); nextTextureLimitApplyTime = Time.realtimeSinceStartup + num; if (ConfigTextureResolution.Value > 0) { QualitySettings.globalTextureMipmapLimit = 0; } } } private static void QueueDeferredTextureLimit() { if ((Object)(object)Instance == (Object)null) { ApplyTextureLimit(force: true); } else { Instance.QueueDeferredTextureLimitInstance(); } } private void QueueDeferredTextureLimitInstance() { if (textureLimitRoutine != null) { ((MonoBehaviour)this).StopCoroutine(textureLimitRoutine); } textureLimitRoutine = ((MonoBehaviour)this).StartCoroutine(ApplyTextureLimitAfterDelay()); } private IEnumerator ApplyTextureLimitAfterDelay() { float num = ((ConfigTextureLimitDelay == null) ? 1.75f : Mathf.Clamp(ConfigTextureLimitDelay.Value, 0f, 5f)); if (num > 0f) { yield return (object)new WaitForSecondsRealtime(num); } float duration = ((ConfigTextureLimitEnforcementDuration == null) ? 10f : Mathf.Clamp(ConfigTextureLimitEnforcementDuration.Value, 0f, 30f)); float endTime = Time.realtimeSinceStartup + duration; while (true) { ApplyGraphicsSettings(applyTextureLimit: true, forceTextureLimit: true); if (duration <= 0f || Time.realtimeSinceStartup >= endTime) { break; } yield return (object)new WaitForSecondsRealtime(0.5f); } textureLimitRoutine = null; } private void StartContinuousQualityEnforcer() { if (continuousQualityRoutine != null) { ((MonoBehaviour)this).StopCoroutine(continuousQualityRoutine); } continuousQualityRoutine = ((MonoBehaviour)this).StartCoroutine(ContinuousQualityEnforcer()); } private IEnumerator ContinuousQualityEnforcer() { while (true) { yield return (object)new WaitForSecondsRealtime(1f); if (ConfigContinuousQualityEnforcement != null && ConfigContinuousQualityEnforcement.Value == 1) { ApplyGraphicsSettings(applyTextureLimit: true); } } } private static void PatchGraphicsSettingsApplier_ApplyAll() { ApplyGraphicsSettings(applyTextureLimit: false); QueueDeferredTextureLimit(); } private static bool PatchOutlineSmoothNormals(Mesh mesh, ref List __result) { if ((Object)(object)mesh != (Object)null) { return true; } __result = new List(); return false; } private static bool PatchGraphicsCopyTexture(Texture src, Texture dst) { return !TryHandleMismatchedTextureCopy(src, dst, -1, -1); } private static bool PatchGraphicsCopyTextureElement(Texture src, int srcElement, Texture dst, int dstElement) { return !TryHandleMismatchedTextureCopy(src, dst, srcElement, dstElement); } private static bool TryHandleMismatchedTextureCopy(Texture src, Texture dst, int srcElement, int dstElement) { //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0074: 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) if (ConfigRtlcTextureCopyFix == null || ConfigRtlcTextureCopyFix.Value != 1) { return false; } if ((Object)(object)src == (Object)null || (Object)(object)dst == (Object)null || src.dimension != dst.dimension) { return false; } if (src.mipmapCount == dst.mipmapCount && src.width == dst.width && src.height == dst.height) { return false; } if (textureCopyFallbackInProgress) { return false; } textureCopyFallbackInProgress = true; try { if (src.graphicsFormat != dst.graphicsFormat) { Texture2D val = (Texture2D)(object)((src is Texture2D) ? src : null); if (val != null) { try { val.Compress(true); } catch { } } } if (src.graphicsFormat != dst.graphicsFormat) { return false; } int num = 0; int num2 = src.width; int num3 = src.height; while ((num2 > dst.width || num3 > dst.height) && num < src.mipmapCount - 1) { num2 = Mathf.Max(1, num2 / 2); num3 = Mathf.Max(1, num3 / 2); num++; } if (num2 == dst.width && num3 == dst.height) { int num4 = Mathf.Min(src.mipmapCount - num, dst.mipmapCount); for (int i = 0; i < num4; i++) { if (srcElement >= 0) { Graphics.CopyTexture(src, srcElement, num + i, dst, dstElement, i); } else { Graphics.CopyTexture(src, 0, num + i, dst, 0, i); } } LogTextureCopyFix(src, dst); return true; } return false; } catch (Exception ex) { if (textureCopyFixLogCount < 5) { LogWarning("RTLC texture copy fix failed for " + GetTextureName(src) + " -> " + GetTextureName(dst) + ": " + ex.Message); } textureCopyFixLogCount++; return false; } finally { textureCopyFallbackInProgress = false; } } private static void LogTextureCopyFix(Texture src, Texture dst) { if (textureCopyFixLogCount < 5) { LogInfo($"RTLC texture copy fix handled mipmap mismatch for {GetTextureName(src)} -> {GetTextureName(dst)} (src mips {src.mipmapCount}, dst mips {dst.mipmapCount})."); } textureCopyFixLogCount++; } private static string GetTextureName(Texture texture) { if ((Object)(object)texture == (Object)null) { return ""; } if (!string.IsNullOrEmpty(((Object)texture).name)) { return ((Object)texture).name; } return ((object)texture).GetType().Name; } private static void PatchSettingsLayoutAwake(object __instance) { try { if (!(GetFieldValue(GetFieldValue(__instance, "layout"), "tabs") is IList tabs)) { return; } object obj = FindGraphicsTab(tabs); if (obj == null || !(GetFieldValue(obj, "entries") is IList list) || ContainsModEntry(list, "ModGraphicsPreset")) { return; } List list2 = new List { CreateDropdown("ModGraphicsPreset", GetSettingLabel("ModGraphicsPreset"), GetSettingOptionsList("ModGraphicsPreset"), ConfigGraphicsPreset.Value), CreateDropdown("ModRenderScale", GetSettingLabel("ModRenderScale"), GetSettingOptionsList("ModRenderScale"), ConfigRenderScale.Value), CreateDropdown("ModTextureResolution", GetSettingLabel("ModTextureResolution"), GetSettingOptionsList("ModTextureResolution"), ConfigTextureResolution.Value), CreateDropdown("ModShadows", GetSettingLabel("ModShadows"), GetSettingOptionsList("ModShadows"), ConfigShadows.Value), CreateDropdown("ModAntiAliasing", GetSettingLabel("ModAntiAliasing"), GetSettingOptionsList("ModAntiAliasing"), ConfigAntiAliasing.Value), CreateDropdown("ModVSync", GetSettingLabel("ModVSync"), GetSettingOptionsList("ModVSync"), ConfigVSync.Value), CreateDropdown("ModTargetFPS", GetSettingLabel("ModTargetFPS"), GetSettingOptionsList("ModTargetFPS"), ConfigTargetFPS.Value), CreateDropdown("ModLodBias", GetSettingLabel("ModLodBias"), GetSettingOptionsList("ModLodBias"), ConfigLodBias.Value), CreateDropdown("ModAnisotropicFiltering", GetSettingLabel("ModAnisotropicFiltering"), GetSettingOptionsList("ModAnisotropicFiltering"), ConfigAnisotropicFiltering.Value), CreateDropdown("ModReflectionProbes", GetSettingLabel("ModReflectionProbes"), GetSettingOptionsList("ModReflectionProbes"), ConfigReflectionProbes.Value), CreateDropdown("ModSoftParticles", GetSettingLabel("ModSoftParticles"), GetSettingOptionsList("ModSoftParticles"), ConfigSoftParticles.Value), CreateDropdown("ModBackgroundFpsLimit", GetSettingLabel("ModBackgroundFpsLimit"), GetSettingOptionsList("ModBackgroundFpsLimit"), ConfigBackgroundFpsLimit.Value) }; for (int num = list2.Count - 1; num >= 0; num--) { if ((Object)(object)list2[num] != (Object)null) { list.Insert(0, list2[num]); } } } catch (Exception ex) { LogWarning("Failed to extend the Graphics settings UI: " + ex.Message); } } private static object FindGraphicsTab(IList tabs) { foreach (object tab in tabs) { if (string.Equals(GetFieldValue(tab, "tabName") as string, "Graphics", StringComparison.OrdinalIgnoreCase)) { return tab; } } return null; } private static bool ContainsModEntry(IList entries, string key) { foreach (object entry in entries) { ScriptableObject val = (ScriptableObject)((entry is ScriptableObject) ? entry : null); if ((Object)(object)val != (Object)null && ((Object)val).name == key) { return true; } } return false; } private static ScriptableObject CreateDropdown(string key, string label, List options, int defaultIndex) { try { Type type = AccessTools.TypeByName("DropdownSettingItem"); if (type == null) { LogWarning("Could not find DropdownSettingItem. More Optimization settings will not be shown in the menu."); return null; } ScriptableObject obj = ScriptableObject.CreateInstance(type); ((Object)obj).name = key; TrySetField(obj, "key", key); TrySetField(obj, "label", label); TrySetField(obj, "options", options); TrySetField(obj, "index", defaultIndex); return obj; } catch (Exception ex) { LogWarning("Failed to create dropdown " + key + ": " + ex.Message); return null; } } private static void PatchNotifyChanged(object __instance) { try { if (syncingSettingsUi || !(GetFieldValue(__instance, "tabs") is IList tabs)) { return; } Dictionary dictionary = CollectModItems(tabs); if (dictionary.Count == 0) { return; } bool flag = false; bool flag2 = false; if (TryGetDropdownIndex(dictionary, "ModGraphicsPreset", out var index)) { index = ClampOption(index, 0, 5); if (index != ConfigGraphicsPreset.Value) { ConfigGraphicsPreset.Value = index; flag = true; if (index != 0) { ApplyPreset(index); SyncDropdownsFromConfig(dictionary); RefreshVisibleSettingsUi(dictionary); flag2 = true; } } } if (!flag2) { int num = (int)(0u | (UpdateConfigFromDropdown(dictionary, "ModRenderScale", ConfigRenderScale, 0, 4) ? 1u : 0u) | (UpdateConfigFromDropdown(dictionary, "ModTextureResolution", ConfigTextureResolution, 0, 3) ? 1u : 0u) | (UpdateConfigFromDropdown(dictionary, "ModShadows", ConfigShadows, 0, 2) ? 1u : 0u) | (UpdateConfigFromDropdown(dictionary, "ModAntiAliasing", ConfigAntiAliasing, 0, 3) ? 1u : 0u) | (UpdateConfigFromDropdown(dictionary, "ModVSync", ConfigVSync, 0, 1) ? 1u : 0u) | (UpdateConfigFromDropdown(dictionary, "ModTargetFPS", ConfigTargetFPS, 0, 4) ? 1u : 0u) | (UpdateConfigFromDropdown(dictionary, "ModLodBias", ConfigLodBias, 0, 3) ? 1u : 0u) | (UpdateConfigFromDropdown(dictionary, "ModAnisotropicFiltering", ConfigAnisotropicFiltering, 0, 2) ? 1u : 0u) | (UpdateConfigFromDropdown(dictionary, "ModReflectionProbes", ConfigReflectionProbes, 0, 1) ? 1u : 0u)) | (UpdateConfigFromDropdown(dictionary, "ModSoftParticles", ConfigSoftParticles, 0, 1) ? 1 : 0); bool flag3 = UpdateConfigFromDropdown(dictionary, "ModBackgroundFpsLimit", ConfigBackgroundFpsLimit, 0, 1); if (num != 0) { ConfigGraphicsPreset.Value = 0; SetDropdownIndex(dictionary, "ModGraphicsPreset", 0); RefreshVisibleSettingsUi(dictionary); flag = true; } if (flag3) { flag = true; } } if (flag) { ApplyGraphicsSettings(applyTextureLimit: true, forceTextureLimit: true); QueueDeferredTextureLimit(); SaveConfigFile(); } } catch (Exception ex) { LogWarning("Failed to process Graphics settings change: " + ex.Message); } } private static Dictionary CollectModItems(IList tabs) { Dictionary dictionary = new Dictionary(); foreach (object tab in tabs) { if (!(GetFieldValue(tab, "entries") is IList list)) { continue; } foreach (object item in list) { ScriptableObject val = (ScriptableObject)((item is ScriptableObject) ? item : null); if ((Object)(object)val != (Object)null && ((Object)val).name.StartsWith("Mod", StringComparison.Ordinal)) { dictionary[((Object)val).name] = val; } } } return dictionary; } private static bool UpdateConfigFromDropdown(Dictionary modItems, string key, ConfigEntry config, int min, int max) { if (!TryGetDropdownIndex(modItems, key, out var index)) { return false; } index = ClampOption(index, min, max); if (config.Value == index) { return false; } config.Value = index; return true; } private static void SyncDropdownsFromConfig(Dictionary modItems) { SetDropdownIndex(modItems, "ModGraphicsPreset", ConfigGraphicsPreset.Value); SetDropdownIndex(modItems, "ModRenderScale", ConfigRenderScale.Value); SetDropdownIndex(modItems, "ModTextureResolution", ConfigTextureResolution.Value); SetDropdownIndex(modItems, "ModShadows", ConfigShadows.Value); SetDropdownIndex(modItems, "ModAntiAliasing", ConfigAntiAliasing.Value); SetDropdownIndex(modItems, "ModVSync", ConfigVSync.Value); SetDropdownIndex(modItems, "ModTargetFPS", ConfigTargetFPS.Value); SetDropdownIndex(modItems, "ModLodBias", ConfigLodBias.Value); SetDropdownIndex(modItems, "ModAnisotropicFiltering", ConfigAnisotropicFiltering.Value); SetDropdownIndex(modItems, "ModReflectionProbes", ConfigReflectionProbes.Value); SetDropdownIndex(modItems, "ModSoftParticles", ConfigSoftParticles.Value); SetDropdownIndex(modItems, "ModBackgroundFpsLimit", ConfigBackgroundFpsLimit.Value); } private static void RefreshVisibleSettingsUi(Dictionary modItems) { if (syncingSettingsUi) { return; } syncingSettingsUi = true; try { foreach (KeyValuePair modItem in modItems) { if (TryGetDropdownIndex(modItems, modItem.Key, out var index)) { string settingLabel = GetSettingLabel(modItem.Key); string[] settingOptions = GetSettingOptions(modItem.Key); if (settingLabel != null && settingOptions != null) { RefreshLiveDropdown(settingLabel, settingOptions, index); } } } } finally { syncingSettingsUi = false; } } private static string GetSettingLabel(string key) { return key switch { "ModGraphicsPreset" => T("Quality Preset", "Пресет качества"), "ModRenderScale" => T("Render Scale", "Масштаб рендера"), "ModTextureResolution" => T("Texture Resolution", "Разрешение текстур"), "ModShadows" => T("Shadows", "Тени"), "ModAntiAliasing" => T("Anti-Aliasing", "Сглаживание"), "ModVSync" => "VSync", "ModTargetFPS" => T("Target FPS", "Лимит FPS"), "ModLodBias" => T("LOD Bias", "Дальность LOD"), "ModAnisotropicFiltering" => T("Anisotropic", "Анизотропия"), "ModReflectionProbes" => T("Reflections", "Отражения"), "ModSoftParticles" => T("Soft Particles", "Мягкие частицы"), "ModBackgroundFpsLimit" => T("Background FPS", "FPS в фоне"), _ => null, }; } private static List GetSettingOptionsList(string key) { string[] settingOptions = GetSettingOptions(key); if (settingOptions != null) { return new List(settingOptions); } return new List(); } private static string[] GetSettingOptions(string key) { switch (key) { case "ModGraphicsPreset": if (useRussianText) { return new string[6] { "Свои", "Картошка", "Низкие", "Средние", "Высокие", "Ультра" }; } return new string[6] { "Custom", "Potato", "Low", "Medium", "High", "Ultra" }; case "ModRenderScale": return new string[5] { "50%", "60%", "75%", "90%", "100%" }; case "ModTextureResolution": if (useRussianText) { return new string[4] { "Полное", "Половина", "Четверть", "Восьмая" }; } return new string[4] { "Full Res", "Half Res", "Quarter Res", "Eighth Res" }; case "ModShadows": if (useRussianText) { return new string[3] { "Выкл", "Только жесткие", "Все" }; } return new string[3] { "Off", "Hard Only", "All" }; case "ModAntiAliasing": if (useRussianText) { return new string[4] { "Выкл", "2x", "4x", "8x" }; } return new string[4] { "Off", "2x", "4x", "8x" }; case "ModVSync": if (useRussianText) { return new string[2] { "Выкл", "Вкл" }; } return new string[2] { "Off", "On" }; case "ModTargetFPS": if (useRussianText) { return new string[5] { "Без лимита", "30", "60", "120", "144" }; } return new string[5] { "Uncapped", "30", "60", "120", "144" }; case "ModLodBias": if (useRussianText) { return new string[4] { "Низкая", "Баланс", "Обычная", "Высокая" }; } return new string[4] { "Low", "Balanced", "Default", "High" }; case "ModAnisotropicFiltering": if (useRussianText) { return new string[3] { "Выкл", "Вкл", "Принудительно" }; } return new string[3] { "Off", "On", "Forced" }; case "ModReflectionProbes": if (useRussianText) { return new string[2] { "Выкл", "Вкл" }; } return new string[2] { "Off", "On" }; case "ModSoftParticles": if (useRussianText) { return new string[2] { "Выкл", "Вкл" }; } return new string[2] { "Off", "On" }; case "ModBackgroundFpsLimit": if (useRussianText) { return new string[2] { "Выкл", "10 FPS" }; } return new string[2] { "Off", "10 FPS" }; default: return null; } } private static string T(string english, string russian) { if (!useRussianText) { return english; } return russian; } private static void RefreshLiveDropdown(string label, string[] expectedOptions, int index) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) Component[] array = Resources.FindObjectsOfTypeAll(); foreach (Component val in array) { if ((Object)(object)val == (Object)null || (Object)(object)val.gameObject == (Object)null) { continue; } Scene scene = val.gameObject.scene; if (((Scene)(ref scene)).IsValid()) { string fullName = ((object)val).GetType().FullName; if (fullName != null && fullName.IndexOf("Dropdown", StringComparison.OrdinalIgnoreCase) >= 0 && DropdownOptionsMatch(val, expectedOptions) && (expectedOptions.Length > 2 || HierarchyContainsText(val.transform, label))) { SetLiveDropdownValue(val, index); } } } } private static bool DropdownOptionsMatch(object dropdown, string[] expectedOptions) { if (!(GetMemberValue(dropdown, "options") is IEnumerable enumerable)) { return false; } List list = new List(); foreach (object item2 in enumerable) { if (GetMemberValue(item2, "text") is string item) { list.Add(item); } } if (list.Count != expectedOptions.Length) { return false; } for (int i = 0; i < expectedOptions.Length; i++) { if (!string.Equals(NormalizeUiText(list[i]), NormalizeUiText(expectedOptions[i]), StringComparison.Ordinal)) { return false; } } return true; } private static bool HierarchyContainsText(Transform transform, string expectedText) { string normalizedExpected = NormalizeUiText(expectedText); Transform val = transform; int num = 0; while ((Object)(object)val != (Object)null && num < 5) { if (TransformContainsText(val, normalizedExpected)) { return true; } val = val.parent; num++; } return false; } private static bool TransformContainsText(Transform root, string normalizedExpected) { Component[] componentsInChildren = ((Component)root).GetComponentsInChildren(true); foreach (Component val in componentsInChildren) { if (!((Object)(object)val == (Object)null) && GetMemberValue(val, "text") is string value && string.Equals(NormalizeUiText(value), normalizedExpected, StringComparison.Ordinal)) { return true; } } return false; } private static void SetLiveDropdownValue(object dropdown, int index) { if (!TryInvokeMethod(dropdown, "SetValueWithoutNotify", new object[1] { index })) { TrySetProperty(dropdown, "value", index); } TryInvokeMethod(dropdown, "RefreshShownValue", new object[0]); } private static bool TryGetDropdownIndex(Dictionary modItems, string key, out int index) { index = 0; if (!modItems.TryGetValue(key, out var value) || (Object)(object)value == (Object)null) { return false; } object fieldValue = GetFieldValue(value, "index"); if (fieldValue is int) { index = (int)fieldValue; return true; } return false; } private static void SetDropdownIndex(Dictionary modItems, string key, int index) { if (modItems.TryGetValue(key, out var value) && (Object)(object)value != (Object)null) { TrySetField(value, "index", index); } } private static object GetFieldValue(object target, string fieldName) { if (target == null) { return null; } try { FieldInfo fieldInfo = AccessTools.Field(target.GetType(), fieldName); return (fieldInfo == null) ? null : fieldInfo.GetValue(target); } catch { return null; } } private static object GetMemberValue(object target, string memberName) { if (target == null) { return null; } try { Type type = target.GetType(); PropertyInfo property = type.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(target, null); } FieldInfo field = type.GetField(memberName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); return (field == null) ? null : field.GetValue(target); } catch { return null; } } private static bool TrySetField(object target, string fieldName, object value) { if (target == null) { return false; } try { FieldInfo fieldInfo = AccessTools.Field(target.GetType(), fieldName); if (fieldInfo == null) { return false; } fieldInfo.SetValue(target, value); return true; } catch { return false; } } private static bool TrySetProperty(object target, string propertyName, object value) { if (target == null) { return false; } return TrySetProperty(target.GetType(), target, propertyName, value); } private static bool TrySetProperty(Type type, object target, string propertyName, object value) { try { PropertyInfo property = type.GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property == null) { return false; } property.SetValue(target, value, null); return true; } catch { return false; } } private static bool TryInvokeMethod(object target, string methodName, object[] args) { if (target == null) { return false; } try { Type[] array; if (args == null || args.Length == 0) { array = Type.EmptyTypes; args = new object[0]; } else { array = new Type[args.Length]; for (int i = 0; i < args.Length; i++) { array[i] = args[i].GetType(); } } Type type = target.GetType(); MethodInfo methodInfo = type.GetMethod(methodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, array, null); if (methodInfo == null) { MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); for (int j = 0; j < methods.Length; j++) { if (methods[j].Name == methodName && methods[j].GetParameters().Length == args.Length) { methodInfo = methods[j]; break; } } } if (methodInfo == null) { return false; } methodInfo.Invoke(target, args); return true; } catch { return false; } } private static string NormalizeUiText(string value) { if (value == null) { return string.Empty; } return value.Trim().Replace(" ", string.Empty).Replace("-", string.Empty) .Replace("_", string.Empty) .ToLowerInvariant(); } private static int ClampOption(int value, int min, int max) { return Mathf.Clamp(value, min, max); } private static void SaveConfigFile() { if ((Object)(object)Instance == (Object)null) { return; } try { ((BaseUnityPlugin)Instance).Config.Save(); } catch (Exception ex) { LogWarning("Failed to save config: " + ex.Message); } } private static void LogWarning(string message) { if ((Object)(object)Instance != (Object)null) { ((BaseUnityPlugin)Instance).Logger.LogWarning((object)message); } } private static void LogInfo(string message) { if ((Object)(object)Instance != (Object)null) { ((BaseUnityPlugin)Instance).Logger.LogInfo((object)message); } } } }