diff --git a/CoreLimiter/CoreLimiter.csproj b/CoreLimiter/CoreLimiter.csproj index 42c651d..8c6e9cc 100644 --- a/CoreLimiter/CoreLimiter.csproj +++ b/CoreLimiter/CoreLimiter.csproj @@ -1,6 +1,6 @@  net472 - 1.0.1.0 + 1.0.2.0 diff --git a/CoreLimiter/CoreLimiterMod.cs b/CoreLimiter/CoreLimiterMod.cs index e819b4c..30c3b3d 100644 --- a/CoreLimiter/CoreLimiterMod.cs +++ b/CoreLimiter/CoreLimiterMod.cs @@ -4,49 +4,71 @@ using CoreLimiter; using MelonLoader; -[assembly:MelonInfo(typeof(CoreLimiterMod), "Core Limiter", "1.0.1", "knah", "https://github.com/knah/ML-UniversalMods")] +[assembly:MelonInfo(typeof(CoreLimiterMod), "Core Limiter", "1.0.2", "knah", "https://github.com/knah/ML-UniversalMods")] [assembly:MelonGame] namespace CoreLimiter { public class CoreLimiterMod : MelonMod { - private const string CoreLimiterPrefCategory = "CoreLimiter"; - private const string MaxCoresPref = "MaxCores"; - private const string SkipHyperThreadsPref = "SkipHyperThreads"; - public override void OnApplicationStart() { - MelonPrefs.RegisterCategory(CoreLimiterPrefCategory, "Core Limiter"); - MelonPrefs.RegisterInt(CoreLimiterPrefCategory, MaxCoresPref, 4, "Maximum cores"); - MelonPrefs.RegisterBool(CoreLimiterPrefCategory, SkipHyperThreadsPref, true, "Don't use both threads of a core"); + var category = MelonPreferences.CreateCategory("CoreLimiter", "Core Limiter"); + + var maxCores = category.CreateEntry("MaxCores", Math.Max(1, Environment.ProcessorCount / 4), "Maximum cores"); + var useBothHyperthreads = category.CreateEntry("UseBothHyperthreads", true, "Use both hyperthreads"); + var useFirstCores = category.CreateEntry("UseFirstCores", false, "Use first X cores (instead of last)"); + var specificCores = category.CreateEntry("SpecificCores", "", "Use specific cores (comma separated, i.e. '0,2,4,6'; overrides other settings)"); - MelonLogger.Log($"[CoreLimiter] Have {Environment.ProcessorCount} processor cores"); + MelonLogger.Msg($"Have {Environment.ProcessorCount} processor cores"); - ApplyAffinity(); - } + void UpdateState() => ApplyAffinity(maxCores.Value, useBothHyperthreads.Value, useFirstCores.Value, specificCores.Value); - public override void OnModSettingsApplied() - { - ApplyAffinity(); + maxCores.OnValueChangedUntyped += UpdateState; + useBothHyperthreads.OnValueChangedUntyped += UpdateState; + useFirstCores.OnValueChangedUntyped += UpdateState; + specificCores.OnValueChangedUntyped += UpdateState; + + UpdateState(); } - private static void ApplyAffinity() + private static long GetMaskAuto(int coreCount, bool useHyperthreads, bool useFirstCores) { var processorCount = Environment.ProcessorCount; long mask = 0; - var targetNumCores = MelonPrefs.GetInt(CoreLimiterPrefCategory, MaxCoresPref); - var targetBit = processorCount - 1; - var bitStep = MelonPrefs.GetBool(CoreLimiterPrefCategory, SkipHyperThreadsPref) ? 2 : 1; - for (var i = 0; i < targetNumCores && targetBit > 0; i++) + var startBit = useFirstCores ? 0 : processorCount - coreCount * 2; + var endBit = useFirstCores ? coreCount * 2 : processorCount; + for (var i = startBit; i < endBit; i += 2) { - mask |= 1L << targetBit; - targetBit -= bitStep; + mask |= 1L << i; + if (useHyperthreads) + mask |= 1L << (i + 1); } + + return mask; + } + + private static long GetMaskManual(string input) + { + long mask = 0; + foreach (var s in input.Split(',')) + { + if (int.TryParse(s, out var i)) + mask |= 1L << i; + } + + return mask; + } + + private static void ApplyAffinity(int coreCount, bool useHyperthreads, bool useFirstCores, string specificCores) + { + var mask = string.IsNullOrEmpty(specificCores) ? GetMaskAuto(coreCount, useHyperthreads, useFirstCores) : GetMaskManual(specificCores); + + if (mask == 0) mask = 1; // don't set empty masks var process = Process.GetCurrentProcess().Handle; - MelonLogger.Log($"[CoreLimiter] Assigning affinity mask: {mask}"); + MelonLogger.Msg($"Assigning affinity mask: {mask}"); SetProcessAffinityMask(process, new IntPtr(mask)); } diff --git a/HWIDPatch/HWIDPatch.csproj b/HWIDPatch/HWIDPatch.csproj index ac61ca2..72e550a 100644 --- a/HWIDPatch/HWIDPatch.csproj +++ b/HWIDPatch/HWIDPatch.csproj @@ -5,6 +5,6 @@ true latest true - 1.0.1.0 + 1.0.2.0 diff --git a/HWIDPatch/HWIDPatchMod.cs b/HWIDPatch/HWIDPatchMod.cs index a75c03b..eac25f2 100644 --- a/HWIDPatch/HWIDPatchMod.cs +++ b/HWIDPatch/HWIDPatchMod.cs @@ -6,12 +6,12 @@ using UnhollowerBaseLib; using UnityEngine; -[assembly:MelonInfo(typeof(HWIDPatchMod), "HWIDPatch", "1.0.1", "knah", "https://github.com/knah/ML-UniversalMods")] +[assembly:MelonInfo(typeof(HwidPatchMod), "HWIDPatch", "1.0.2", "knah", "https://github.com/knah/ML-UniversalMods")] [assembly:MelonGame] namespace HWIDPatch { - public class HWIDPatchMod : MelonMod + public class HwidPatchMod : MelonMod { private static Il2CppSystem.Object ourGeneratedHwidString; @@ -19,18 +19,19 @@ public override unsafe void OnApplicationStart() { try { - var settingsCategory = "HWIDPatch"; - MelonPrefs.RegisterCategory(settingsCategory, "HWID Patch"); - MelonPrefs.RegisterString(settingsCategory, "HWID", "", hideFromList: true); + var category = MelonPreferences.CreateCategory("HWIDPatch", "HWID Patch"); + var hwidEntry = category.CreateEntry("HWID", "", is_hidden: true); - var newId = MelonPrefs.GetString(settingsCategory, "HWID"); + var newId = hwidEntry.Value; if (newId.Length != SystemInfo.deviceUniqueIdentifier.Length) { var random = new System.Random(Environment.TickCount); var bytes = new byte[SystemInfo.deviceUniqueIdentifier.Length / 2]; random.NextBytes(bytes); newId = string.Join("", bytes.Select(it => it.ToString("x2"))); - MelonPrefs.SetString(settingsCategory, "HWID", newId); + MelonLogger.Msg("Generated and saved a new HWID"); + hwidEntry.Value = newId; + category.SaveToFile(false); } ourGeneratedHwidString = new Il2CppSystem.Object(IL2CPP.ManagedStringToIl2Cpp(newId)); @@ -39,30 +40,24 @@ public override unsafe void OnApplicationStart() var icallAddress = IL2CPP.il2cpp_resolve_icall(icallName); if (icallAddress == IntPtr.Zero) { - MelonLogger.LogError("Can't resolve the icall, not patching"); + MelonLogger.Error("Can't resolve the icall, not patching"); return; } - CompatHook((IntPtr) (&icallAddress), - typeof(HWIDPatchMod).GetMethod(nameof(GetDeviceIdPatch), + MelonUtils.NativeHookAttach((IntPtr) (&icallAddress), + typeof(HwidPatchMod).GetMethod(nameof(GetDeviceIdPatch), BindingFlags.Static | BindingFlags.NonPublic)!.MethodHandle.GetFunctionPointer()); - MelonLogger.Log("Patched HWID; below two should match:"); - MelonLogger.Log($"Current: {SystemInfo.deviceUniqueIdentifier}"); - MelonLogger.Log($"Target: {newId}"); + MelonLogger.Msg("Patched HWID; below two should match:"); + MelonLogger.Msg($"Current: {SystemInfo.deviceUniqueIdentifier}"); + MelonLogger.Msg($"Target: {newId}"); } catch (Exception ex) { - MelonLogger.LogError(ex.ToString()); + MelonLogger.Error(ex.ToString()); } } private static IntPtr GetDeviceIdPatch() => ourGeneratedHwidString.Pointer; - - private static void CompatHook(IntPtr first, IntPtr second) - { - typeof(Imports).GetMethod("Hook", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)! - .Invoke(null, new object[] {first, second}); - } } } \ No newline at end of file diff --git a/LocalPlayerPrefs/LocalPlayerPrefs.csproj b/LocalPlayerPrefs/LocalPlayerPrefs.csproj index e77895e..68db247 100644 --- a/LocalPlayerPrefs/LocalPlayerPrefs.csproj +++ b/LocalPlayerPrefs/LocalPlayerPrefs.csproj @@ -3,7 +3,7 @@ net472 true - 1.0.1.0 + 1.0.2.0 diff --git a/LocalPlayerPrefs/LocalPlayerPrefsMod.cs b/LocalPlayerPrefs/LocalPlayerPrefsMod.cs index 632d2cf..e4bdcc1 100644 --- a/LocalPlayerPrefs/LocalPlayerPrefsMod.cs +++ b/LocalPlayerPrefs/LocalPlayerPrefsMod.cs @@ -10,7 +10,7 @@ using UnhollowerBaseLib; using UnityEngine; -[assembly:MelonInfo(typeof(LocalPlayerPrefsMod), "LocalPlayerPrefs", "1.0.1", "knah", "https://github.com/knah/ML-UniversalMods")] +[assembly:MelonInfo(typeof(LocalPlayerPrefsMod), "LocalPlayerPrefs", "1.0.2", "knah", "https://github.com/knah/ML-UniversalMods")] [assembly:MelonGame] namespace LocalPlayerPrefs @@ -22,7 +22,7 @@ public class LocalPlayerPrefsMod : MelonMod private readonly List myPinnedDelegates = new List(); private readonly ConcurrentDictionary myPrefs = new ConcurrentDictionary(); - private bool myHadChanges = false; + private bool myHadChanges; [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool TrySetFloatDelegate(IntPtr keyPtr, float value); [UnmanagedFunctionPointer(CallingConvention.Cdecl)] private delegate bool TrySetIntDelegate(IntPtr keyPtr, int value); @@ -45,12 +45,12 @@ public override void OnApplicationStart() { var dict = (ProxyObject) JSON.Load(File.ReadAllText(FileName)); foreach (var keyValuePair in dict) myPrefs[keyValuePair.Key] = ToObject(keyValuePair.Key, keyValuePair.Value); - MelonLogger.Log($"Loaded {dict.Count} prefs from PlayerPrefs.json"); + MelonLogger.Msg($"Loaded {dict.Count} prefs from PlayerPrefs.json"); } } catch (Exception ex) { - MelonLogger.LogError($"Unable to load PlayerPrefs.json: {ex}"); + MelonLogger.Error($"Unable to load PlayerPrefs.json: {ex}"); } HookICall(nameof(PlayerPrefs.TrySetFloat), TrySetFloat); @@ -73,7 +73,7 @@ private object ToObject(string key, Variant value) if (value is null) return null; if (value is ProxyString proxyString) - return proxyString.ToString(); + return proxyString.ToString(CultureInfo.InvariantCulture); if (value is ProxyNumber number) { @@ -87,16 +87,16 @@ private object ToObject(string key, Variant value) throw new ArgumentException($"Unknown value in prefs: {key} = {value?.GetType()} / {value}"); } - public override void OnLevelWasLoaded(int level) + public override void OnSceneWasLoaded(int buildIndex, string name) { Save(); - MelonLogger.Log("Saved PlayerPrefs.json on level load"); + MelonLogger.Msg("Saved PlayerPrefs.json on level load"); } public override void OnApplicationQuit() { Save(); - MelonLogger.Log("Saved PlayerPrefs.json on exit"); + MelonLogger.Msg("Saved PlayerPrefs.json on exit"); } private bool HasKey(IntPtr keyPtr) @@ -133,7 +133,7 @@ private void Save() } catch (IOException ex) { - MelonLogger.LogWarning($"Exception while saving PlayerPrefs: {ex}"); + MelonLogger.Warning($"Exception while saving PlayerPrefs: {ex}"); } } @@ -215,12 +215,12 @@ private unsafe void HookICall(string name, T target) where T: Delegate var originalPointer = IL2CPP.il2cpp_resolve_icall("UnityEngine.PlayerPrefs::" + name); if (originalPointer == IntPtr.Zero) { - MelonLogger.LogWarning($"ICall {name} was not found, not patching"); + MelonLogger.Warning($"ICall {name} was not found, not patching"); return; } myPinnedDelegates.Add(target); - Imports.Hook((IntPtr) (&originalPointer), Marshal.GetFunctionPointerForDelegate(target)); + MelonUtils.NativeHookAttach((IntPtr) (&originalPointer), Marshal.GetFunctionPointerForDelegate(target)); } } } \ No newline at end of file diff --git a/NoSteamAtAll/NoSteamAtAll.csproj b/NoSteamAtAll/NoSteamAtAll.csproj index aaf144b..7b7cb6d 100644 --- a/NoSteamAtAll/NoSteamAtAll.csproj +++ b/NoSteamAtAll/NoSteamAtAll.csproj @@ -3,7 +3,7 @@ net472 true - 1.0.2 + 1.0.3 diff --git a/NoSteamAtAll/NoSteamAtAllMod.cs b/NoSteamAtAll/NoSteamAtAllMod.cs index 52755b8..893bee0 100644 --- a/NoSteamAtAll/NoSteamAtAllMod.cs +++ b/NoSteamAtAll/NoSteamAtAllMod.cs @@ -1,11 +1,12 @@ using System; +using System.IO; using System.Runtime.InteropServices; -using Harmony; +using HarmonyLib; using MelonLoader; using NoSteamAtAll; [assembly:MelonGame] -[assembly:MelonInfo(typeof(NoSteamAtAllMod), "No Steam. At all.", "1.0.2", "knah")] +[assembly:MelonInfo(typeof(NoSteamAtAllMod), "No Steam. At all.", "1.0.3", "knah")] namespace NoSteamAtAll { @@ -19,10 +20,13 @@ public class NoSteamAtAllMod : MelonMod public override void OnApplicationStart() { - var library = LoadLibrary(MelonUtils.GetGameDataDirectory() + "\\Plugins\\steam_api64.dll"); + var path = MelonUtils.GetGameDataDirectory() + "\\Plugins\\steam_api64.dll"; + if (!File.Exists(path)) path = MelonUtils.GetGameDataDirectory() + "\\Plugins\\x86_64\\steam_api64.dll"; + if (!File.Exists(path)) path = MelonUtils.GetGameDataDirectory() + "\\Plugins\\x86\\steam_api64.dll"; + var library = LoadLibrary(path); if (library == IntPtr.Zero) { - MelonLogger.LogError("Library load failed"); + MelonLogger.Error($"Library load failed; used path: {path}"); return; } var names = new[] @@ -43,10 +47,10 @@ public override void OnApplicationStart() var address = GetProcAddress(library, name); if (address == IntPtr.Zero) { - MelonLogger.LogError($"Procedure {name} not found"); + MelonLogger.Error($"Procedure {name} not found"); continue; } - Imports.Hook((IntPtr) (&address), + MelonUtils.NativeHookAttach((IntPtr) (&address), AccessTools.Method(typeof(NoSteamAtAllMod), nameof(InitFail)).MethodHandle .GetFunctionPointer()); } diff --git a/ReleaseChangelog.md b/ReleaseChangelog.md index fac74eb..dab98bf 100644 --- a/ReleaseChangelog.md +++ b/ReleaseChangelog.md @@ -1 +1,3 @@ -Initial release in the new repository \ No newline at end of file +Changes: + * All mods: updated to MelonLoader 0.3.0+ + * CoreLimiter: changed settings to make a bit more sense \ No newline at end of file diff --git a/RuntimeGraphicsSettings/RuntimeGraphicsSettings.cs b/RuntimeGraphicsSettings/RuntimeGraphicsSettings.cs index 19d56bc..c1b8ccb 100644 --- a/RuntimeGraphicsSettings/RuntimeGraphicsSettings.cs +++ b/RuntimeGraphicsSettings/RuntimeGraphicsSettings.cs @@ -1,45 +1,75 @@ using MelonLoader; using UnityEngine; +using UnityEngine.Rendering; namespace RuntimeGraphicsSettings { public static class RuntimeGraphicsSettings { - private const string CategoryName = "GraphicsSettings"; - private const string MsaaLevel = "MSAALevel"; - private const string AllowMsaa = "AllowMSAA"; - private const string AnisoFilter = "AnisotropicFiltering"; - private const string RealtimeShadows = "RealtimeShadows"; - private const string SoftShadows = "SoftShadows"; - private const string PixelLights = "PixelLights"; - private const string TextureLimit = "MasterTextureLimit"; - private const string GraphicsTier = "GraphicsTier"; - public static void RegisterSettings() { - MelonPrefs.RegisterCategory(CategoryName, "Graphics settings"); + var category = MelonPreferences.CreateCategory("GraphicsSettings", "Graphics settings"); + + var msaaLevel = category.CreateEntry("MSAALevel", -1, "MSAA Level (1/2/4/8)"); + var allowMsaa = category.CreateEntry("AllowMSAA", true, "Enable MSAA"); + var anisoFilter = category.CreateEntry("AnisotropicFiltering", true, "Enable anisotropic filtering"); + var rtShadows = category.CreateEntry("RealtimeShadows", true, "Realtime shadows"); + var softShadows = category.CreateEntry("SoftShadows", true, "Soft shadows"); + var maxPixelLights = category.CreateEntry("PixelLights", -1, "Max pixel lights"); + var textureDecimation = category.CreateEntry("MasterTextureLimit", -1, "Texture decimation"); + var graphicsTier = category.CreateEntry("GraphicsTier", -1, "Graphics tier (1/2/3)"); + + + anisoFilter.OnValueChanged += (_, value) => + { + QualitySettings.anisotropicFiltering = value ? AnisotropicFiltering.ForceEnable : AnisotropicFiltering.Disable; + }; + QualitySettings.anisotropicFiltering = anisoFilter.Value ? AnisotropicFiltering.ForceEnable : AnisotropicFiltering.Disable; + + textureDecimation.OnValueChanged += (_, value) => + { + if (value >= 0) QualitySettings.masterTextureLimit = value; + }; + if (textureDecimation.Value >= 0) QualitySettings.masterTextureLimit = textureDecimation.Value; + + maxPixelLights.OnValueChanged += (_, value) => + { + if (value >= 0) QualitySettings.pixelLightCount = value; + }; + if (maxPixelLights.Value >= 0) QualitySettings.pixelLightCount = maxPixelLights.Value; + + graphicsTier.OnValueChanged += (_, value) => + { + if (value > 0) Graphics.activeTier = (GraphicsTier)(value - 1); + }; + if (graphicsTier.Value > 0) Graphics.activeTier = (GraphicsTier)(graphicsTier.Value - 1); + + void UpdateShadows() + { + QualitySettings.shadows = rtShadows.Value + ? softShadows.Value ? ShadowQuality.All : ShadowQuality.HardOnly + : ShadowQuality.Disable; + } - MelonPrefs.RegisterInt(CategoryName, MsaaLevel, -1, "MSAA Level (1/2/4/8)"); - MelonPrefs.RegisterBool(CategoryName, AllowMsaa, true, "Enable MSAA"); - MelonPrefs.RegisterBool(CategoryName, AnisoFilter, true, "Enable anisotropic filtering"); - MelonPrefs.RegisterBool(CategoryName, RealtimeShadows, true, "Realtime shadows"); - MelonPrefs.RegisterBool(CategoryName, SoftShadows, true, "Soft shadows"); - MelonPrefs.RegisterInt(CategoryName, PixelLights, -1, "Max pixel lights"); - MelonPrefs.RegisterInt(CategoryName, TextureLimit, -1, "Texture decimation"); - MelonPrefs.RegisterInt(CategoryName, GraphicsTier, -1, "Graphics tier (1/2/3)"); - } + UpdateShadows(); - public static bool AllowMSAA => MelonPrefs.GetBool(CategoryName, AllowMsaa); - public static int MSAALevel => MelonPrefs.GetInt(CategoryName, MsaaLevel); - public static bool AllowAniso => MelonPrefs.GetBool(CategoryName, AnisoFilter); + rtShadows.OnValueChangedUntyped += UpdateShadows; + softShadows.OnValueChangedUntyped += UpdateShadows; - public static ShadowQuality ShadowQuality => MelonPrefs.GetBool(CategoryName, RealtimeShadows) - ? (MelonPrefs.GetBool(CategoryName, SoftShadows) ? ShadowQuality.All : ShadowQuality.HardOnly) - : ShadowQuality.Disable; + void UpdateMsaa() + { + if (allowMsaa.Value) + { + if (msaaLevel.Value > 0) + QualitySettings.antiAliasing = msaaLevel.Value; + } + else + QualitySettings.antiAliasing = 1; + } - public static int PixelLightCount => MelonPrefs.GetInt(CategoryName, PixelLights); - public static int TextureSizeLimit => MelonPrefs.GetInt(CategoryName, TextureLimit); - public static int HardwareGraphicsTier => MelonPrefs.GetInt(CategoryName, GraphicsTier); - + msaaLevel.OnValueChangedUntyped += UpdateMsaa; + allowMsaa.OnValueChangedUntyped += UpdateMsaa; + UpdateMsaa(); + } } } \ No newline at end of file diff --git a/RuntimeGraphicsSettings/RuntimeGraphicsSettings.csproj b/RuntimeGraphicsSettings/RuntimeGraphicsSettings.csproj index 260907f..3e21ed8 100644 --- a/RuntimeGraphicsSettings/RuntimeGraphicsSettings.csproj +++ b/RuntimeGraphicsSettings/RuntimeGraphicsSettings.csproj @@ -1,7 +1,7 @@  net472 - 0.2.1.0 + 0.2.2.0 true \ No newline at end of file diff --git a/RuntimeGraphicsSettings/RuntimeGraphisSettingsMod.cs b/RuntimeGraphicsSettings/RuntimeGraphisSettingsMod.cs index 0799f72..12e9832 100644 --- a/RuntimeGraphicsSettings/RuntimeGraphisSettingsMod.cs +++ b/RuntimeGraphicsSettings/RuntimeGraphisSettingsMod.cs @@ -1,51 +1,15 @@ using MelonLoader; using RuntimeGraphicsSettings; -using UnityEngine; -using UnityEngine.Rendering; -[assembly:MelonInfo(typeof(RuntimeGraphicsSettingsMod), "Runtime Graphics Settings", RuntimeGraphicsSettingsMod.ModVersion, "knah", "https://github.com/knah/ML-UniversalMods")] +[assembly:MelonInfo(typeof(RuntimeGraphicsSettingsMod), "Runtime Graphics Settings", "0.2.2", "knah", "https://github.com/knah/ML-UniversalMods")] [assembly:MelonGame] // universal namespace RuntimeGraphicsSettings { public class RuntimeGraphicsSettingsMod : MelonMod { - public const string ModVersion = "0.2.1"; - public override void OnApplicationStart() { RuntimeGraphicsSettings.RegisterSettings(); - DoApplySettings(); - } - - public override void OnModSettingsApplied() - { - DoApplySettings(); - } - - void DoApplySettings() - { - if (RuntimeGraphicsSettings.AllowMSAA) - { - if (RuntimeGraphicsSettings.MSAALevel > 0) - QualitySettings.antiAliasing = RuntimeGraphicsSettings.MSAALevel; - } - else - QualitySettings.antiAliasing = 1; - - QualitySettings.anisotropicFiltering = RuntimeGraphicsSettings.AllowAniso - ? AnisotropicFiltering.ForceEnable - : AnisotropicFiltering.Disable; - - if (RuntimeGraphicsSettings.TextureSizeLimit >= 0) - QualitySettings.masterTextureLimit = RuntimeGraphicsSettings.TextureSizeLimit; - - QualitySettings.shadows = RuntimeGraphicsSettings.ShadowQuality; - - if (RuntimeGraphicsSettings.PixelLightCount >= 0) - QualitySettings.pixelLightCount = RuntimeGraphicsSettings.PixelLightCount; - - if (RuntimeGraphicsSettings.HardwareGraphicsTier > 0) - Graphics.activeTier = (GraphicsTier) (RuntimeGraphicsSettings.HardwareGraphicsTier - 1); } } } \ No newline at end of file