diff --git a/Celeste64.Launcher/Program.cs b/Celeste64.Launcher/Program.cs
index 78500c31..b4e28e42 100644
--- a/Celeste64.Launcher/Program.cs
+++ b/Celeste64.Launcher/Program.cs
@@ -19,7 +19,12 @@ public static void Main(string[] args)
Version loaderVersion = typeof(Program).Assembly.GetName().Version!;
Game.LoaderVersion = $"Fuji: v.{loaderVersion.Major}.{loaderVersion.Minor}.{loaderVersion.Build}";
Game.IsDynamicRes = parsedArgs.Has("dynamic-res");
- Game.AppArgs = parsedArgs; // Expose our parsed args to the game
+
+ // Expose our parsed args to the game
+ Game.AppArgs = parsedArgs;
+
+ LogHelper.Initialize();
+
if (!string.IsNullOrEmpty(BuildProperties.ModVersion()))
{
Game.LoaderVersion += "-" + BuildProperties.ModVersion();
diff --git a/Source/Data/Assets.cs b/Source/Data/Assets.cs
index 8e428c47..c840fb60 100644
--- a/Source/Data/Assets.cs
+++ b/Source/Data/Assets.cs
@@ -118,7 +118,7 @@ internal static void LoadAssetsForMod(GameMod mod)
if (modFs == null)
{
- Log.Error($"Error loading assets for {mod.ModInfo.Id}. Mod FileSystem not initialized.");
+ Log.Error($"Failed to load assets for {mod.ModInfo.Id}. Mod FileSystem not initialized.");
return;
}
diff --git a/Source/Data/Map.cs b/Source/Data/Map.cs
index f2829ad9..6d01e1f9 100644
--- a/Source/Data/Map.cs
+++ b/Source/Data/Map.cs
@@ -154,8 +154,7 @@ public Map(string name, string virtPath, Stream stream)
readExceptionMessage = e.Message;
- Log.Error($"Failed to load map {name}, more details below.");
- Log.Error(e.ToString());
+ LogHelper.Error($"Failed to load map {name}", e);
}
if (Data != null)
diff --git a/Source/Data/PersistedData/PersistedData.cs b/Source/Data/PersistedData/PersistedData.cs
index cc335c73..e82dfdc9 100644
--- a/Source/Data/PersistedData/PersistedData.cs
+++ b/Source/Data/PersistedData/PersistedData.cs
@@ -24,7 +24,7 @@ public virtual void Serialize(Utf8JsonWriter writer, object instance)
}
catch (Exception e)
{
- Log.Error(e.ToString());
+ LogHelper.Error("Failed to load persisted data", e);
return null;
}
}
diff --git a/Source/Data/PersistedData/VersionedPersistedData.cs b/Source/Data/PersistedData/VersionedPersistedData.cs
index 1a4b0d09..040594f0 100644
--- a/Source/Data/PersistedData/VersionedPersistedData.cs
+++ b/Source/Data/PersistedData/VersionedPersistedData.cs
@@ -24,7 +24,7 @@ namespace Celeste64;
}
catch (Exception e)
{
- Log.Error(e.ToString());
+ LogHelper.Error("Failed to load versioned persisted data", e);
return null;
}
}
diff --git a/Source/Game.cs b/Source/Game.cs
index abdefc7e..70a743d2 100644
--- a/Source/Game.cs
+++ b/Source/Game.cs
@@ -237,7 +237,6 @@ public override void Shutdown()
instance = null;
Log.Info("Shutting down...");
- WriteToLog();
}
public bool IsMidTransition => transitionStep != TransitionStep.None;
@@ -284,65 +283,10 @@ private void HandleError(Exception e)
}
scenes.Clear();
- Log.Error("== ERROR ==\n\n" + e.ToString());
- WriteToLog();
+ LogHelper.Error("An Unhandled Exception occurred: ", e);
UnsafelySetScene(new GameErrorMessage(e));
}
- // Fuji Custom
- public static void WriteToLog()
- {
- if (!Settings.WriteLog)
- {
- return;
- }
-
- // construct a log message
- const string LogFileName = "Log.txt";
- StringBuilder log = new();
- lock (Log.Logs)
- log.AppendLine(Log.Logs.ToString());
-
- // write to file
- string path = LogFileName;
- {
- if (App.Running)
- {
- try
- {
- path = Path.Join(App.UserPath, LogFileName);
- }
- catch
- {
- path = LogFileName;
- }
- }
-
- File.WriteAllText(path, log.ToString());
- }
- }
-
- internal static void OpenLog()
- {
- const string LogFileName = "Log.txt";
- string path = "";
- if (App.Running)
- {
- try
- {
- path = Path.Join(App.UserPath, LogFileName);
- }
- catch
- {
- path = LogFileName;
- }
- }
- if (File.Exists(path))
- {
- new Process { StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } }.Start();
- }
- }
-
internal void ReloadAssets(bool reloadAll)
{
if (!scenes.TryPeek(out var scene))
@@ -629,8 +573,6 @@ public override void Update()
// in case new music was played
Settings.SyncSettings();
transitionStep = TransitionStep.FadeIn;
-
- WriteToLog();
}
else if (transitionStep == TransitionStep.FadeIn)
{
diff --git a/Source/Helpers/LogHelper.cs b/Source/Helpers/LogHelper.cs
new file mode 100644
index 00000000..958e08e8
--- /dev/null
+++ b/Source/Helpers/LogHelper.cs
@@ -0,0 +1,113 @@
+using System.Diagnostics;
+using System.Text;
+namespace Celeste64;
+
+///
+/// Fuji Custom
+/// This class improves logging functionality by better distinguishing between info messages, warnings, and logs
+/// It also provides functions for writing logs to the log file, and opening the log file
+/// This wraps fosters Log events by subscribing to the OnInfo, OnWarn, and OnError events
+///
+public static class LogHelper
+{
+ public static readonly StringBuilder Logs = new StringBuilder();
+
+ public static void Initialize()
+ {
+ Log.OnInfo += Info;
+ Log.OnWarn += Warn;
+ Log.OnError += Error;
+ }
+
+ public static void Info(ReadOnlySpan text)
+ {
+ Append(text);
+ Console.Out.WriteLine(text);
+ WriteToLog();
+ }
+
+ public static void Warn(ReadOnlySpan text)
+ {
+ Append($"[Warning] {text}");
+ Console.ForegroundColor = ConsoleColor.Yellow;
+ Console.Out.WriteLine($"[Warning] {text}");
+ Console.ResetColor();
+ WriteToLog();
+ }
+
+ public static void Error(ReadOnlySpan text)
+ {
+ Append($"[Error] {text}");
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.Out.WriteLine($"[Error] {text}");
+ Console.ResetColor();
+ WriteToLog();
+ }
+
+ public static void Error(ReadOnlySpan text, Exception ex)
+ {
+ Error($"{text}\n {ex}");
+ }
+
+ public static void WriteToLog()
+ {
+ if (!Settings.WriteLog)
+ {
+ return;
+ }
+
+ // construct a log message
+ const string LogFileName = "Log.txt";
+ StringBuilder log = new();
+ lock (Logs)
+ log.AppendLine(Logs.ToString());
+
+ // write to file
+ string path = LogFileName;
+ {
+ if (App.Running)
+ {
+ try
+ {
+ path = Path.Join(App.UserPath, LogFileName);
+ }
+ catch
+ {
+ path = LogFileName;
+ }
+ }
+
+ File.WriteAllText(path, log.ToString());
+ }
+ }
+
+ public static void OpenLog()
+ {
+ const string LogFileName = "Log.txt";
+ string path = "";
+ if (App.Running)
+ {
+ try
+ {
+ path = Path.Join(App.UserPath, LogFileName);
+ }
+ catch
+ {
+ path = LogFileName;
+ }
+ }
+ if (File.Exists(path))
+ {
+ new Process { StartInfo = new ProcessStartInfo(path) { UseShellExecute = true } }.Start();
+ }
+ }
+
+ public static void Append(ReadOnlySpan message)
+ {
+ lock (Logs)
+ {
+ Logs.Append(message);
+ Logs.Append('\n');
+ }
+ }
+}
diff --git a/Source/Mod/Core/GameMod.cs b/Source/Mod/Core/GameMod.cs
index 3b263120..ab56494b 100644
--- a/Source/Mod/Core/GameMod.cs
+++ b/Source/Mod/Core/GameMod.cs
@@ -160,8 +160,7 @@ public bool SaveSettings()
}
catch (Exception e)
{
- Log.Error($"Failed to save the settings of {ModInfo.Id}!");
- Log.Error(e.Message);
+ LogHelper.Error($"Failed to save the settings of {ModInfo.Id}!", e);
return false;
}
}
@@ -219,8 +218,7 @@ public bool LoadSettings()
}
catch (Exception e)
{
- Log.Error($"Failed to save the settings of {ModInfo.Id}!");
- Log.Error(e.Message);
+ LogHelper.Error($"Failed to save the settings of {ModInfo.Id}!", e);
return false;
}
}
diff --git a/Source/Mod/Core/ModLoader.cs b/Source/Mod/Core/ModLoader.cs
index 43e94bff..216d00e9 100644
--- a/Source/Mod/Core/ModLoader.cs
+++ b/Source/Mod/Core/ModLoader.cs
@@ -254,8 +254,7 @@ internal static bool Load(ModInfo info, IModFilesystem fs)
catch (Exception ex)
{
FailedToLoadMods.Add(info.Id);
- Log.Error($"Fuji Error: An error occurred while trying to load mod: {info.Id}");
- Log.Error(ex.ToString());
+ LogHelper.Error($"Fuji Error: An error occurred while trying to load mod: {info.Id}", ex);
return false;
}
diff --git a/Source/Mod/Helpers/SkinInfo.cs b/Source/Mod/Helpers/SkinInfo.cs
index 0dc9948b..a9ce2e29 100644
--- a/Source/Mod/Helpers/SkinInfo.cs
+++ b/Source/Mod/Helpers/SkinInfo.cs
@@ -56,7 +56,7 @@ public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSeri
}
catch (Exception ex)
{
- Log.Error("Error: Could not parse value in skin file: " + ex.ToString());
+ LogHelper.Error("Error: Could not parse value in skin file: ", ex);
return 0;
}
}
diff --git a/Source/Mod/Menu/ModSelectionMenu.cs b/Source/Mod/Menu/ModSelectionMenu.cs
index d3237092..bcbddb55 100644
--- a/Source/Mod/Menu/ModSelectionMenu.cs
+++ b/Source/Mod/Menu/ModSelectionMenu.cs
@@ -47,8 +47,7 @@ internal ModSelectionMenu(Menu? rootMenu)
}));
FailedToLoadModsMenu.Add(new Option("FujiOpenLogFile", () =>
{
- Game.WriteToLog();
- Game.OpenLog();
+ LogHelper.OpenLog();
}));
}
diff --git a/Source/Scenes/GameErrorMessage.cs b/Source/Scenes/GameErrorMessage.cs
index ae6e417f..23952158 100644
--- a/Source/Scenes/GameErrorMessage.cs
+++ b/Source/Scenes/GameErrorMessage.cs
@@ -12,8 +12,7 @@ public GameErrorMessage(Exception e)
menu.Add(new Menu.Option("FujiOpenLogFile", () =>
{
- Game.WriteToLog();
- Game.OpenLog();
+ LogHelper.OpenLog();
}));
menu.Add(new Menu.Option("QuitToMainMenu", () =>
diff --git a/Source/Scenes/World.cs b/Source/Scenes/World.cs
index 602fa37a..89b9d9cc 100644
--- a/Source/Scenes/World.cs
+++ b/Source/Scenes/World.cs
@@ -88,8 +88,7 @@ public World(EntryInfo entry)
badMapWarningMenu.Add(new Menu.Option("FujiOpenLogFile", () =>
{
- Game.WriteToLog();
- Game.OpenLog();
+ LogHelper.OpenLog();
}));
badMapWarningMenu.Add(new Menu.Option("QuitToMainMenu", () => Game.Instance.Goto(new Transition()
@@ -522,8 +521,7 @@ public override void Update()
catch (Exception err)
{
string currentModName = ModManager.Instance.CurrentLevelMod != null && ModManager.Instance.CurrentLevelMod.ModInfo != null ? ModManager.Instance.CurrentLevelMod.ModInfo.Id : "unknown";
- Log.Error($"--- ERROR in the map {currentModName}:{Entry.Map}. More details below ---");
- Log.Error(err.ToString());
+ LogHelper.Error($"--- ERROR in the map {currentModName}:{Entry.Map}. More details below ---", err);
Panic(err, $"Oops, critical error :(\n{err.Message}\nYou can try to recover from this error by pressing Retry,\nbut we can't promise stability!", Panicked);
} // We wrap most of Update() in a try-catch to hopefully catch errors that occur during gameplay.