366 lines
16 KiBLFS
C#
Executable File
366 lines
16 KiBLFS
C#
Executable File
using System;
|
|
using System.Reflection;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Barotrauma;
|
|
using Microsoft.Xna.Framework;
|
|
using HarmonyLib;
|
|
|
|
namespace FastLoadMod
|
|
{
|
|
// -------------------------------------------------------------------
|
|
// COMMUNITY REQUESTED FEATURE
|
|
// -------------------------------------------------------------------
|
|
public class LoadOptimizer : IAssemblyPlugin
|
|
{
|
|
private static void PreventCrashing()
|
|
{
|
|
bool isCrashing = false;
|
|
if (isCrashing) return;
|
|
|
|
// This prevents 100% of ALL crashes thanks to the innovative minds over on the Barotrauma discord server.
|
|
// In other words, yucky you. This is obviously a joke, but god forbid I wrap everything in try-catch.
|
|
}
|
|
|
|
private static Harmony harmony;
|
|
private static int originalFrameLimit;
|
|
private static bool originalVSync;
|
|
private static bool originalFixedTimeStep;
|
|
private static TimeSpan originalSleepTime;
|
|
|
|
// STATE FLAGS
|
|
private static bool isOptimized = false;
|
|
private static bool isBenchmarking = false;
|
|
private static bool hasLoggedState = false;
|
|
public static bool VerboseLogging = false;
|
|
public static bool BenchmarkMode = false;
|
|
|
|
// STATS VARIABLES
|
|
private static DateTime loadStartTime;
|
|
private static int loadFrameCount;
|
|
private static double maxFrameTimeRecorded = 0f;
|
|
private static DateTime lastProfileLog = DateTime.MinValue;
|
|
|
|
private static DateTime optimizationExpiryTime = DateTime.MinValue;
|
|
|
|
public void Initialize()
|
|
{
|
|
harmony = new Harmony("com.fastload.mod");
|
|
|
|
try
|
|
{
|
|
var updateMethod = typeof(GameMain).GetMethod("Update",
|
|
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
|
|
null, new Type[] { typeof(GameTime) }, null);
|
|
|
|
if (updateMethod != null)
|
|
{
|
|
harmony.Patch(updateMethod, new HarmonyMethod(AccessTools.Method(typeof(LoadOptimizer), "CheckLoadingState")));
|
|
}
|
|
|
|
var consoleMethod = typeof(DebugConsole).GetMethod("ExecuteCommand", BindingFlags.Static | BindingFlags.Public);
|
|
if (consoleMethod != null)
|
|
{
|
|
harmony.Patch(consoleMethod, new HarmonyMethod(AccessTools.Method(typeof(LoadOptimizer), "InterceptConsoleCommand")));
|
|
}
|
|
|
|
var forceTurboProbe = new HarmonyMethod(AccessTools.Method(typeof(LoadOptimizer), "ProbeLoadSession"));
|
|
int hookCount = 0;
|
|
|
|
foreach (MethodInfo method in typeof(Level).GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance))
|
|
{
|
|
if (method.Name == "Generate") { harmony.Patch(method, forceTurboProbe); hookCount++; }
|
|
}
|
|
|
|
foreach (MethodInfo method in typeof(Submarine).GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance))
|
|
{
|
|
if (method.Name == "Load") { harmony.Patch(method, forceTurboProbe); hookCount++; }
|
|
}
|
|
|
|
Type saveUtilType = Type.GetType("Barotrauma.SaveUtil, Barotrauma");
|
|
if (saveUtilType != null)
|
|
{
|
|
foreach (MethodInfo method in saveUtilType.GetMethods(BindingFlags.Public | BindingFlags.Static))
|
|
{
|
|
if (method.Name == "LoadGame") { harmony.Patch(method, forceTurboProbe); hookCount++; }
|
|
}
|
|
}
|
|
|
|
foreach (MethodInfo method in typeof(GameSession).GetMethods(BindingFlags.Public | BindingFlags.Instance))
|
|
{
|
|
if (method.Name == "StartRound") { harmony.Patch(method, forceTurboProbe); hookCount++; }
|
|
}
|
|
|
|
foreach (MethodInfo method in typeof(Screen).GetMethods(BindingFlags.Public | BindingFlags.Instance))
|
|
{
|
|
if (method.Name == "Select") { harmony.Patch(method, forceTurboProbe); hookCount++; }
|
|
}
|
|
|
|
Type campaignType = Type.GetType("Barotrauma.CampaignMode, Barotrauma");
|
|
if (campaignType != null)
|
|
{
|
|
foreach (MethodInfo method in campaignType.GetMethods(BindingFlags.Public | BindingFlags.Instance))
|
|
{
|
|
if (method.Name == "LoadNewLevel") { harmony.Patch(method, forceTurboProbe); hookCount++; }
|
|
}
|
|
}
|
|
|
|
Type gameServerType = Type.GetType("Barotrauma.Networking.GameServer, Barotrauma");
|
|
if (gameServerType != null)
|
|
{
|
|
foreach (MethodInfo method in gameServerType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic))
|
|
{
|
|
if (method.Name == "StartGameCore") { harmony.Patch(method, forceTurboProbe); hookCount++; }
|
|
}
|
|
}
|
|
|
|
DebugConsole.NewMessage($"[EnLo] Optimizer Ready. Hooked {hookCount} telemetry triggers.", Color.Green);
|
|
|
|
// Console Command Stubs
|
|
DebugConsole.Commands.Add(new DebugConsole.Command("fastload_verbose", "Toggle logs", (string[] args) => { }));
|
|
DebugConsole.Commands.Add(new DebugConsole.Command("fastload_benchmark", "Toggle comparison simulation of the mod. Limits fps to 60 during loads", (string[] args) => { }));
|
|
DebugConsole.Commands.Add(new DebugConsole.Command(
|
|
"fastload_setfps",
|
|
"Changes max FPS. Usage: fastload_setfps [number]",
|
|
(string[] args) => { },
|
|
() => new string[][] { new string[] { "60", "120", "144", "240", "0" } }
|
|
));
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
DebugConsole.NewMessage($"[EnLo] Init Error: {ex.Message}", Color.Orange);
|
|
}
|
|
}
|
|
|
|
public void OnLoadCompleted() { }
|
|
public void PreInitPatching() { }
|
|
public void Dispose()
|
|
{
|
|
if (isOptimized) RestoreFramerate();
|
|
if (harmony != null) Harmony.UnpatchID("com.fastload.mod");
|
|
harmony = null;
|
|
}
|
|
|
|
public static bool InterceptConsoleCommand(string inputtedCommands)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(inputtedCommands)) return true;
|
|
string[] parts = inputtedCommands.Trim().Split(' ');
|
|
string cleanCmd = parts[0];
|
|
|
|
if (cleanCmd.Equals("fastload_verbose", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
VerboseLogging = !VerboseLogging;
|
|
DebugConsole.NewMessage($"[EnLo] Logging: {VerboseLogging}", Color.Cyan);
|
|
return false;
|
|
}
|
|
|
|
if (cleanCmd.Equals("fastload_benchmark", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
BenchmarkMode = !BenchmarkMode;
|
|
string status = BenchmarkMode ? "ENABLED (Next load forced to 60 FPS)" : "DISABLED (Unlimited FPS)";
|
|
DebugConsole.NewMessage($"[EnLo] Benchmark: {status}", Color.Yellow);
|
|
return false;
|
|
}
|
|
|
|
if (cleanCmd.Equals("fastload_setfps", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
// No arguments provided? Print current FPS
|
|
if (parts.Length == 1)
|
|
{
|
|
int currentLimit = GameSettings.CurrentConfig.Graphics.FrameLimit;
|
|
DebugConsole.NewMessage($"[EnLo] Current FPS cap is {currentLimit}.", Color.Cyan);
|
|
return false;
|
|
}
|
|
|
|
// Argument provided but it's not a valid number? Show usage
|
|
if (!int.TryParse(parts[1], out int newLimit))
|
|
{
|
|
DebugConsole.NewMessage("[EnLo] Usage: fastload_setfps <number> (e.g., fastload_setfps 144)", Color.Yellow);
|
|
return false;
|
|
}
|
|
|
|
// Valid number provided? Apply and save
|
|
GameSettings.SetCurrentConfig(GameSettings.CurrentConfig with { Graphics = GameSettings.CurrentConfig.Graphics with { FrameLimit = newLimit } });
|
|
GameSettings.SaveCurrentConfig();
|
|
originalFrameLimit = newLimit;
|
|
|
|
DebugConsole.NewMessage($"[EnLo] Success! Max FPS set to {newLimit} and saved to config_player.xml.", Color.Lime);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public static void ProbeLoadSession(MethodBase __originalMethod)
|
|
{
|
|
if (VerboseLogging && __originalMethod != null)
|
|
{
|
|
DebugConsole.NewMessage($"[EnLo Probe] Event Fired: {__originalMethod.DeclaringType.Name}.{__originalMethod.Name}", Color.Magenta);
|
|
}
|
|
StartLoadSession(__originalMethod?.Name ?? "Unknown");
|
|
}
|
|
|
|
public static void StartLoadSession(string triggerName)
|
|
{
|
|
if (!isOptimized)
|
|
{
|
|
loadStartTime = DateTime.UtcNow;
|
|
loadFrameCount = 0;
|
|
maxFrameTimeRecorded = 0f;
|
|
hasLoggedState = false;
|
|
}
|
|
float timerSeconds = 4.0f;
|
|
|
|
if (triggerName == "Select")
|
|
{
|
|
timerSeconds = 2.0f;
|
|
}
|
|
else if (triggerName == "StartRound" || triggerName == "StartGameCore" || triggerName == "LoadGame")
|
|
{
|
|
timerSeconds = 8.0f;
|
|
}
|
|
else if (triggerName == "Generate" || triggerName == "Load")
|
|
{
|
|
timerSeconds = 6.0f;
|
|
}
|
|
optimizationExpiryTime = DateTime.UtcNow.AddSeconds(timerSeconds);
|
|
isBenchmarking = BenchmarkMode;
|
|
}
|
|
|
|
public static void CheckLoadingState(GameMain __instance, GameTime gameTime)
|
|
{
|
|
bool isVisualLoading = __instance.LoadingScreenOpen;
|
|
if (isVisualLoading)
|
|
{
|
|
if (optimizationExpiryTime < DateTime.UtcNow.AddSeconds(1.0))
|
|
optimizationExpiryTime = DateTime.UtcNow.AddSeconds(1.0);
|
|
}
|
|
|
|
if (gameTime != null && (isOptimized || isBenchmarking))
|
|
{
|
|
double frameMs = gameTime.ElapsedGameTime.TotalMilliseconds;
|
|
if (frameMs > maxFrameTimeRecorded) maxFrameTimeRecorded = frameMs;
|
|
loadFrameCount++;
|
|
|
|
if (!isBenchmarking)
|
|
{
|
|
if (frameMs > 33.0)
|
|
{
|
|
if (optimizationExpiryTime < DateTime.UtcNow.AddSeconds(1.5))
|
|
optimizationExpiryTime = DateTime.UtcNow.AddSeconds(1.5);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (DateTime.UtcNow < optimizationExpiryTime)
|
|
{
|
|
if (isBenchmarking)
|
|
{
|
|
var settings = GameSettings.CurrentConfig.Graphics;
|
|
if (settings.FrameLimit != 60 || settings.VSync != true)
|
|
{
|
|
originalFrameLimit = settings.FrameLimit;
|
|
originalVSync = settings.VSync;
|
|
originalFixedTimeStep = GameMain.Instance.IsFixedTimeStep;
|
|
|
|
settings.FrameLimit = 60;
|
|
settings.VSync = true;
|
|
GameMain.GraphicsDeviceManager.SynchronizeWithVerticalRetrace = true;
|
|
GameMain.Instance.IsFixedTimeStep = true;
|
|
GameMain.GraphicsDeviceManager.ApplyChanges();
|
|
isOptimized = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!isOptimized) UncapFramerate();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Timer fully ran out
|
|
if (isOptimized) RestoreFramerate();
|
|
}
|
|
|
|
if (VerboseLogging && (isOptimized || isBenchmarking))
|
|
{
|
|
if (!hasLoggedState)
|
|
{
|
|
DebugConsole.NewMessage(">>> [EnLo] LOAD STARTED >>>", Color.Cyan);
|
|
hasLoggedState = true;
|
|
}
|
|
|
|
if ((DateTime.UtcNow - lastProfileLog).TotalMilliseconds > 500)
|
|
{
|
|
double currentFPS = (gameTime != null && gameTime.ElapsedGameTime.TotalMilliseconds > 0)
|
|
? (1000.0 / gameTime.ElapsedGameTime.TotalMilliseconds)
|
|
: 0;
|
|
DebugConsole.NewMessage($"[EnLo Monitor] FPS: {currentFPS:F0} | FrameTime: {gameTime?.ElapsedGameTime.TotalMilliseconds:F1}ms", Color.Gray);
|
|
lastProfileLog = DateTime.UtcNow;
|
|
}
|
|
}
|
|
else if (!isOptimized && hasLoggedState)
|
|
{
|
|
hasLoggedState = false;
|
|
}
|
|
}
|
|
|
|
public static void UncapFramerate()
|
|
{
|
|
try
|
|
{
|
|
var graphicsSettings = GameSettings.CurrentConfig.Graphics;
|
|
originalFrameLimit = graphicsSettings.FrameLimit;
|
|
originalVSync = graphicsSettings.VSync;
|
|
originalFixedTimeStep = GameMain.Instance.IsFixedTimeStep;
|
|
|
|
// Save the user's normal background sleep time and disable it
|
|
originalSleepTime = GameMain.Instance.InactiveSleepTime;
|
|
GameMain.Instance.InactiveSleepTime = TimeSpan.Zero;
|
|
|
|
GameMain.Instance.IsFixedTimeStep = false;
|
|
GameMain.GraphicsDeviceManager.SynchronizeWithVerticalRetrace = false;
|
|
GameMain.GraphicsDeviceManager.ApplyChanges();
|
|
|
|
isOptimized = true;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (VerboseLogging) DebugConsole.NewMessage($"[EnLo] Warning: Failed to Uncap: {ex.Message}", Color.Yellow);
|
|
}
|
|
}
|
|
|
|
public static void RestoreFramerate()
|
|
{
|
|
try
|
|
{
|
|
GameMain.Instance.IsFixedTimeStep = originalFixedTimeStep;
|
|
GameMain.GraphicsDeviceManager.SynchronizeWithVerticalRetrace = originalVSync;
|
|
|
|
// Restores vanilla game config after load
|
|
GameMain.Instance.InactiveSleepTime = originalSleepTime;
|
|
|
|
GameMain.GraphicsDeviceManager.ApplyChanges();
|
|
isOptimized = false;
|
|
isBenchmarking = false;
|
|
|
|
if (VerboseLogging)
|
|
{
|
|
double durationMs = (DateTime.UtcNow - loadStartTime).TotalMilliseconds;
|
|
double avgFps = durationMs > 0 ? (loadFrameCount / (durationMs / 1000.0)) : 0;
|
|
string mode = BenchmarkMode ? "BENCHMARK" : "TURBO";
|
|
|
|
DebugConsole.NewMessage($"<<< [EnLo] {mode} FINISHED <<<", Color.Cyan);
|
|
DebugConsole.NewMessage($" Duration: {durationMs:F0}ms", Color.Cyan);
|
|
DebugConsole.NewMessage($" Avg FPS: {avgFps:F0}", Color.Cyan);
|
|
Color statColor = (maxFrameTimeRecorded > 1000) ? Color.Red : Color.Green;
|
|
DebugConsole.NewMessage($" Longest Stutter (MaxFrameTime): {maxFrameTimeRecorded:F0}ms", statColor);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (VerboseLogging) DebugConsole.NewMessage($"[EnLo] Warning: Failed to Restore: {ex.Message}", Color.Yellow);
|
|
}
|
|
}
|
|
}
|
|
} |