Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 104 additions & 1 deletion Core/Optimizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ namespace RustOptimizer.Core
{
public static class Optimizer
{
public const string PvpGuideProfileName = "PvP (Steam Guide 2026)";

[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);

Expand Down Expand Up @@ -49,6 +51,12 @@ public static Dictionary<string, string> GetOptimalSettings(string profile)
{
var settings = new Dictionary<string, string>();

if (profile == PvpGuideProfileName)
{
ApplyPvpGuideSettings(settings);
return settings;
}

// Universal Settings for all profiles
settings.Add("graphics.vsync", "False");
settings.Add("client.headbob", "False");
Expand Down Expand Up @@ -154,6 +162,101 @@ public static Dictionary<string, string> GetOptimalSettings(string profile)
}
return settings;
}

private static void ApplyPvpGuideSettings(Dictionary<string, string> settings)
{
var displayMode = HardwareDetector.GetPrimaryDisplayMode();

// Rust-only values shown in the guide. GPU control-panel and Windows tweaks
// are intentionally excluded from this profile.
settings["graphics.fov"] = "90";
settings["client.headbob"] = "False";
settings["client.crosshair"] = "True";
settings["client.hitcross"] = "True";
settings["gametip.showgametips"] = "False";
settings["client.hurtpunch"] = "False";
settings["effects.maxgibs"] = "1";
settings["rgbeffects.enabled"] = "False";
settings["rgbeffects.brightness"] = "0";

settings["fps.limit"] = displayMode.RefreshRate.ToString();
settings["graphics.vsync"] = "False";
settings["fps.limitinmenu"] = "True";
settings["fps.limitinbackground"] = "True";

settings["graphics.renderscale"] = "1";
settings["graphics.shaderlod"] = "200";
settings["graphics.drawdistance"] = "1000";
settings["water.reflections"] = "2";
settings["water.quality"] = "0";
settings["graphics.grassshadows"] = "False";
settings["grass.displacement"] = "True";
settings["graphics.af"] = "1";
settings["graphics.parallax"] = "0";

settings["graphics.shadowquality"] = "0";
settings["graphics.shadowmode"] = "1";
settings["graphics.shadowcascades"] = "0";
settings["graphics.shadowdistance"] = "0";
settings["graphics.shadowlights"] = "0";

settings["decor.quality"] = "0";
settings["grass.quality"] = "0";
settings["terrain.quality"] = "0";
settings["tree.meshes"] = "10";
settings["tree.quality"] = "100";
settings["mesh.quality"] = "100";
settings["graphics.lodbias"] = "1";
settings["particle.quality"] = "0";

if (HardwareDetector.IsNvidiaGpu())
{
settings["graphics.dlss"] = "2";
settings["effects.antialiasing"] = "0";
}
else
{
settings["graphics.dlss"] = "-1";
settings["effects.antialiasing"] = "3";
}
settings["effects.bloom"] = "False";
settings["effects.ao"] = "False";
settings["effects.sharpen"] = "True";
settings["effects.vignet"] = "False";
settings["graphics.dof"] = "False";
settings["effects.lensdirt"] = "False";
settings["effects.motionblur"] = "False";
settings["effects.shafts"] = "False";
settings["global.showblood"] = "False";

settings["culling.env"] = "False";
settings["graphics.contactshadows"] = "False";
settings["gc.buffer"] = "4085";
}

public static Dictionary<string, string> GetPvpGuideShortcuts()
{
return new Dictionary<string, string>
{
["graphics.vm_fov_scale"] = "False",
["graphics.vm_horizontal_flip"] = "True",
["client.clampscreenshake"] = "True",
["hitnotify.notification_level"] = "2",
["legs.enablelegs"] = "0",
["effects.showoutlines"] = "True"
};
}

public static Dictionary<string, string> GetPvpGuideBindings()
{
return new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["c"] = "+graphics.fov 90;graphics.fov 70",
["f1"] = "client.consoletoggle;combatlog;client.ping",
["l"] = "~meta.exec \"client.lookatradius 0\" \"chat.add 0 0 MIN\";meta.exec \"client.lookatradius 0.2\" \"chat.add 0 0 DEFAULT\";meta.exec \"client.lookatradius 10\" \"chat.add 0 0 MAX\"",
["y"] = "forward;sprint"
};
}
/// <summary>
/// Plays a Toilet Flush sound
/// </summary>
Expand Down Expand Up @@ -351,4 +454,4 @@ public static void SetPriority(bool high)
}
}
}
}
}
39 changes: 36 additions & 3 deletions Core/RustConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ public class RustConfig
/// </summary>
public void LoadSettings(string filePath)
{
settings.Clear();

if (File.Exists(filePath))
{
foreach (string line in File.ReadAllLines(filePath))
Expand All @@ -28,11 +30,27 @@ public void LoadSettings(string filePath)
continue;
}

string[] parts = line.Split(new char[] { ' ' }, 2);
string[] parts = line.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length == 2)
{
string key = parts[0].Trim();
string value = parts[1].Trim().Trim('"');
string value = parts[1].Trim();

// Bind entries have a second key component (for example, "input.bind c").
// Keeping it in the dictionary key prevents different binds overwriting each other.
if (key.Equals("input.bind", StringComparison.OrdinalIgnoreCase))
{
string[] bindParts = value.Split(new char[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries);
if (bindParts.Length != 2)
{
continue;
}

key = $"{key} {bindParts[0]}";
value = bindParts[1];
}

value = Unquote(value);
settings[key] = value;
}
}
Expand All @@ -53,10 +71,25 @@ public void SaveSettings(string filePath)
{
foreach (var kvp in settings)
{
writer.WriteLine($"{kvp.Key} \"{kvp.Value}\"");
writer.WriteLine($"{kvp.Key} \"{EscapeValue(kvp.Value)}\"");
}
}
}

private static string Unquote(string value)
{
if (value.Length >= 2 && value[0] == '"' && value[^1] == '"')
{
value = value.Substring(1, value.Length - 2);
}

return value.Replace("\\\"", "\"");
}

private static string EscapeValue(string value)
{
return value.Replace("\"", "\\\"");
}
/// <summary>
/// Tries to get a specific setting from the loaded settings.
/// </summary>
Expand Down
61 changes: 61 additions & 0 deletions Core/RustKeyConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
namespace RustOptimizer.Core
{
public static class RustKeyConfig
{
public static void ApplyBindings(string filePath, IReadOnlyDictionary<string, string> bindings)
{
string? directory = Path.GetDirectoryName(filePath);
if (string.IsNullOrEmpty(directory) || !Directory.Exists(directory))
{
throw new DirectoryNotFoundException($"Rust config directory was not found: {directory}");
}

List<string> lines = File.Exists(filePath)
? File.ReadAllLines(filePath).ToList()
: new List<string>();

Dictionary<string, int> bindingLines = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
for (int i = 0; i < lines.Count; i++)
{
if (TryGetBindingKey(lines[i], out string key))
{
bindingLines[key] = i;
}
}

foreach (var binding in bindings)
{
string line = $"bind {binding.Key.ToLowerInvariant()} {binding.Value}";
if (bindingLines.TryGetValue(binding.Key, out int lineIndex))
{
lines[lineIndex] = line;
}
else
{
bindingLines[binding.Key] = lines.Count;
lines.Add(line);
}
}

if (File.Exists(filePath))
{
File.Copy(filePath, filePath + ".bak", true);
}

File.WriteAllLines(filePath, lines);
}

private static bool TryGetBindingKey(string line, out string key)
{
key = string.Empty;
string[] parts = line.Trim().Split(new char[] { ' ' }, 3, StringSplitOptions.RemoveEmptyEntries);
if (parts.Length < 3 || !parts[0].Equals("bind", StringComparison.OrdinalIgnoreCase))
{
return false;
}

key = parts[1];
return true;
}
}
}
19 changes: 16 additions & 3 deletions Core/UserConfigs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,26 @@ public static class UserConfigs
{
public static string ConfigPath { get; private set; } = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Rust Optimizer", "User", "UserCFG.ini");
public static string BackupsPath { get; set; } = Path.Combine(Application.StartupPath, "backups");
public static string GamePath { get; set; } = MainFrm.Instance.gamePathString.Text;
public static string GamePath { get; set; } = string.Empty;
public static string ?SavedProfile { get; set; } = null;
public static bool AutoFlushEnabled { get; set; } = false;
public static bool AutoFlushSfx { get; set; } = false;
public static int AutoFlushInterval { get; set; } = 15;
public static string AutoFlushUnit { get; set; } = "Minutes";
public static bool CPUHighPriority { get; set; } = false;
public static string GameConfigPath { get; set; } = Path.Combine(MainFrm.Instance.gamePathString.Text, "cfg", "client.cfg");
public static bool PvpGuideShortcuts { get; set; } = false;
public static string GameConfigPath => GetGameConfigPath(GamePath);
public static string KeysConfigPath => GetKeysConfigPath(GamePath);

public static string GetGameConfigPath(string gamePath)
{
return Path.Combine(gamePath ?? string.Empty, "cfg", "client.cfg");
}

public static string GetKeysConfigPath(string gamePath)
{
return Path.Combine(gamePath ?? string.Empty, "cfg", "keys.cfg");
}

/// <summary>
/// Loads, and refreshes Global Settings.
Expand All @@ -32,7 +44,8 @@ public static void Refresh()
AutoFlushInterval = ini.GetInteger("AppSettings", "FlushInterval", 15);
AutoFlushUnit = ini.ReadValue("AppSettings", "FlushUnit", "Minutes");
CPUHighPriority = ini.GetBoolean("AppSettings", "CPUHighPriority", false);
PvpGuideShortcuts = ini.GetBoolean("AppSettings", "PvpGuideShortcuts", false);

}
}
}
}
79 changes: 79 additions & 0 deletions Helpers/HardwareDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Diagnostics;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
Expand All @@ -12,6 +13,84 @@ namespace RustOptimizer.Helpers
{
public static class HardwareDetector
{
private const int EnumCurrentSettings = -1;

[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern bool EnumDisplaySettings(string deviceName, int modeNum, ref DevMode devMode);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct DevMode
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string DeviceName;
public ushort SpecVersion;
public ushort DriverVersion;
public ushort Size;
public ushort DriverExtra;
public uint Fields;
public int PositionX;
public int PositionY;
public uint DisplayOrientation;
public uint DisplayFixedOutput;
public short Color;
public short Duplex;
public short YResolution;
public short TTOption;
public short Collate;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string FormName;
public ushort LogPixels;
public uint BitsPerPixel;
public uint PelsWidth;
public uint PelsHeight;
public uint DisplayFlags;
public uint DisplayFrequency;
public uint ICMMethod;
public uint ICMIntent;
public uint MediaType;
public uint DitherType;
public uint Reserved1;
public uint Reserved2;
public uint PanningWidth;
public uint PanningHeight;
}

public static (int Width, int Height, int RefreshRate) GetPrimaryDisplayMode()
{
Screen? primaryScreen = Screen.PrimaryScreen;
int width = primaryScreen?.Bounds.Width ?? 1920;
int height = primaryScreen?.Bounds.Height ?? 1080;
int refreshRate = 60;

if (primaryScreen == null)
{
return (width, height, refreshRate);
}

DevMode mode = new DevMode
{
DeviceName = string.Empty,
FormName = string.Empty,
Size = (ushort)Marshal.SizeOf<DevMode>()
};

if (EnumDisplaySettings(primaryScreen.DeviceName, EnumCurrentSettings, ref mode))
{
width = mode.PelsWidth > 0 ? (int)mode.PelsWidth : width;
height = mode.PelsHeight > 0 ? (int)mode.PelsHeight : height;
refreshRate = mode.DisplayFrequency > 1 ? (int)mode.DisplayFrequency : refreshRate;
}

return (width, height, refreshRate);
}

public static bool IsNvidiaGpu()
{
string gpuName = GetGpuName();
return gpuName.Contains("NVIDIA", StringComparison.OrdinalIgnoreCase)
|| gpuName.Contains("GeForce", StringComparison.OrdinalIgnoreCase);
}

/// <summary>
/// Grabs the name of the CPU from your system info.
/// </summary>
Expand Down
Loading