diff --git a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.StateMachine.cs b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.StateMachine.cs index 30fb782..6fda62d 100644 --- a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.StateMachine.cs +++ b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.StateMachine.cs @@ -322,7 +322,7 @@ public partial class MainForm else { // We know that profiles exist at this point, so we're going to point it to 0 instead so the following code doesn't fail - if (profIndexString == "null") + if ((profIndexString == "null") || String.IsNullOrWhiteSpace(profIndexString)) profIndexString = "0"; // We parse from the settings, and check if profiles got deleted from the last time the launcher has been selected. if yes, we revert the last selection to 0; diff --git a/AM2RLauncher/AM2RLauncherLib/Core.cs b/AM2RLauncher/AM2RLauncherLib/Core.cs index 33883b5..17a31c4 100644 --- a/AM2RLauncher/AM2RLauncherLib/Core.cs +++ b/AM2RLauncher/AM2RLauncherLib/Core.cs @@ -43,7 +43,7 @@ public static class Core public static readonly string ModsPath = CrossPlatformOperations.CurrentPath + "/Mods"; /// - /// This is used on Windows only. This sets a window to be in foreground, is used i.e. to fix am2r just being hidden. + /// This is used on Windows only. This sets a window to be in foreground. It's used i.e. to fix AM2R just being hidden. /// /// Pointer to the process you want to have in the foreground. /// diff --git a/AM2RLauncher/AM2RLauncherLib/HelperMethods.cs b/AM2RLauncher/AM2RLauncherLib/HelperMethods.cs index 7e0fd29..013af9c 100644 --- a/AM2RLauncher/AM2RLauncherLib/HelperMethods.cs +++ b/AM2RLauncher/AM2RLauncherLib/HelperMethods.cs @@ -61,7 +61,6 @@ public static class HelperMethods } - /// /// This is a custom method, that deletes a Directory. The reason this is used, instead of , /// is because this one sets the attributes of all files to be deletable, while does not do that on it's own. @@ -113,6 +112,7 @@ public static class HelperMethods /// /// The log file to begin the rollover from. /// The maximum amount of log files to retain. Default is 9, as that's the highest digit. + //TODO: double check this method public static void RecursiveRollover(string logFile, int max = 9) { int index = 1; diff --git a/AM2RLauncher/AM2RLauncherLib/Profile.cs b/AM2RLauncher/AM2RLauncherLib/Profile.cs index 5a5413e..ee2c6ef 100644 --- a/AM2RLauncher/AM2RLauncherLib/Profile.cs +++ b/AM2RLauncher/AM2RLauncherLib/Profile.cs @@ -2,10 +2,13 @@ using log4net; using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Diagnostics; +using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; +using System.Text; using System.Text.RegularExpressions; using AM2RLauncherLib.XML; @@ -150,6 +153,16 @@ public static class Profile return IsZipAM2R11ReturnCodes.Successful; } + /// + /// Checks if the repository has been validly cloned already. + /// + /// if yes, if not. + public static bool IsPatchDataCloned() + { + // isValid seems to only check for a .git folder, and there are cases where that exists, but not the profile.xml + return File.Exists($"{Core.PatchDataPath}/profile.xml") && Repository.IsValid(Core.PatchDataPath); + } + /// /// Git Pulls patching content from the repository. /// @@ -634,21 +647,25 @@ public static class Profile } /// - /// Runs the Game, works cross platform. + /// Runs the game from a given profile. /// + /// The that should be run. + /// Whether game logging should take place. + /// Environment variables with which the game should be launched. Linux only. public static void RunGame(ProfileXML profile, bool useLogging, string envVars = "") { - //TODO: double check this and clean - // These are used on both windows and linux for game logging - string savePath = OS.IsWindows ? profile.SaveLocation.Replace("%localappdata%", Environment.GetEnvironmentVariable("LOCALAPPDATA")) - : profile.SaveLocation.Replace("~", CrossPlatformOperations.Home); + // These are used on all OS for game logging + string savePath = OS.IsWindows ? Environment.ExpandEnvironmentVariables(profile.SaveLocation) + : profile.SaveLocation.Replace("~", CrossPlatformOperations.Home); DirectoryInfo logDir = new DirectoryInfo($"{savePath}/logs"); - string date = String.Join("-", DateTime.Now.ToString().Split(Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries)); + string date = DateTime.Now.ToString("yyyy.MM.dd HH-mm-ss"); + string logFile = $"{logDir.FullName}/{profile.Name}.txt"; + string gameDirectory = $"{Core.ProfilesPath}/{profile.Name}"; log.Info($"Launching game profile {profile.Name}."); if (OS.IsWindows) { - // Sets the arguments to empty, or to the profiles save path/logs and create time based logs. Creates the folder if necessary. + // Sets the arguments to empty, or to the profiles' logs folder to create time based logs. string arguments = ""; // Game logging @@ -656,143 +673,111 @@ public static class Profile { log.Info($"Performing logging setup for profile {profile.Name}."); + // Check if directory exists, and roll log file over if necessary if (!Directory.Exists(logDir.FullName)) Directory.CreateDirectory(logDir.FullName); - if (File.Exists($"{logDir.FullName}/{profile.Name}.txt")) - HelperMethods.RecursiveRollover($"{logDir.FullName}/{profile.Name}.txt", 5); - - StreamWriter stream = File.AppendText($"{logDir.FullName}/{profile.Name}.txt"); - - stream.WriteLine($"AM2RLauncher {Core.Version} log generated at {date}"); + if (File.Exists(logFile)) + HelperMethods.RecursiveRollover(logFile, 5); + // Write log header to file + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.Append($"AM2RLauncher {Core.Version} log generated at {date}"); if (OS.IsThisRunningFromWine) - stream.WriteLine("Using WINE!"); - - stream.Flush(); + stringBuilder.Append("Using WINE!"); + File.WriteAllText(logFile, stringBuilder.ToString()); - stream.Close(); - - arguments = $"-debugoutput \"{logDir.FullName}/{profile.Name}.txt\" -output \"{logDir.FullName}/{profile.Name}.txt\""; + arguments = $"-debugoutput \"{logFile}\" -output \"{logFile}\""; } - ProcessStartInfo proc = new ProcessStartInfo(); - proc.WorkingDirectory = $"{Core.ProfilesPath}/{profile.Name}"; - proc.FileName = $"{proc.WorkingDirectory}/AM2R.exe"; - proc.Arguments = arguments; + ProcessStartInfo processStartInfo = new ProcessStartInfo + { + WorkingDirectory = gameDirectory, + FileName = $"{gameDirectory}/AM2R.exe", + Arguments = arguments + }; - log.Info($"CWD of Profile is {proc.WorkingDirectory}"); + log.Info($"CWD of Profile is {processStartInfo.WorkingDirectory}"); - using Process p = Process.Start(proc); - Core.SetForegroundWindow(p.MainWindowHandle); - p.WaitForExit(); + using Process process = new Process { StartInfo = processStartInfo }; + process.Start(); + Core.SetForegroundWindow(process.MainWindowHandle); + process.WaitForExit(); } else if (OS.IsLinux) { - ProcessStartInfo startInfo = new ProcessStartInfo(); log.Info($"User does {(String.IsNullOrWhiteSpace(envVars) ? "not" : "")} have custom environment variables set."); - //TODO: make this more readable at one day + // Set environment variables to startInfo if (!String.IsNullOrWhiteSpace(envVars)) { - for (int i = 0; i < envVars.Count(f => f == '='); i++) + // All env vars are of format "myVar=myValue foo=bar" etc., so split them by space + string[] providedVariables = envVars.Split(' '); + foreach (string providedVariable in providedVariables) { - // Env var variable - string variable = envVars.Substring(0, envVars.IndexOf('=')); - envVars = envVars.Replace($"{variable}=", ""); - - // This thing here is the value parser. Since values are sometimes in quotes, i need to compensate for them. - int valueSubstringLength; - if (envVars[0] != '"') // If value is not embedded in "", check if there are spaces left. If yes, get the index of the space, if not that was the last - { - if (envVars.IndexOf(' ') >= 0) - valueSubstringLength = envVars.IndexOf(' ') + 1; - else - valueSubstringLength = envVars.Length; - } - else // If value is embedded in "", check if there are spaces after the "". if yes, get index of that, if not that was the last - { - int secondQuoteIndex = envVars.IndexOf('"', envVars.IndexOf('"') + 1); - if (envVars.IndexOf(' ', secondQuoteIndex) >= 0) - valueSubstringLength = envVars.IndexOf(' ', secondQuoteIndex) + 1; - else - valueSubstringLength = envVars.Length; - } - // Env var value - string value = envVars.Substring(0, valueSubstringLength); - envVars = envVars.Substring(value.Length); + // After that, we split by '=' where the left is the variable and right is the value + // If there is a malformed env var, this will throw. + string[] variableAsArray = providedVariable.Split('='); + string variable = variableAsArray[0]; + string value = variableAsArray[1]; log.Info($"Adding user variable \"{variable}\" with value \"{value}\""); startInfo.EnvironmentVariables[variable] = value; } } - // If we're supposed to log profiles, add events that track those and append them to this var. otherwise keep it null - string terminalOutput = null; - startInfo.UseShellExecute = false; - startInfo.WorkingDirectory = $"{Core.ProfilesPath}/{profile.Name}"; - startInfo.FileName = $"{startInfo.WorkingDirectory}/AM2R.AppImage"; + startInfo.WorkingDirectory = gameDirectory; + startInfo.FileName = $"{gameDirectory}/AM2R.AppImage"; log.Info($"CWD of Profile is {startInfo.WorkingDirectory}"); - log.Debug("Launching game with following variables: "); - foreach (System.Collections.DictionaryEntry item in startInfo.EnvironmentVariables) + if (log.IsDebugEnabled) { - log.Debug($"Key: \"{item.Key}\" Value: \"{item.Value}\""); + log.Debug("Launching game with following variables: "); + foreach (System.Collections.DictionaryEntry item in startInfo.EnvironmentVariables) + log.Debug($"Key: \"{item.Key}\" Value: \"{item.Value}\""); } - using (Process p = new Process()) - { - p.StartInfo = startInfo; - if (useLogging) - { - p.StartInfo.RedirectStandardOutput = true; - p.OutputDataReceived += (_, e) => { terminalOutput += $"{e.Data}\n"; }; - - p.StartInfo.RedirectStandardError = true; - p.ErrorDataReceived += (_, e) => { terminalOutput += $"{e.Data}\n"; }; - } - - p.Start(); - - if (useLogging) - { - p.BeginOutputReadLine(); - p.BeginErrorReadLine(); - } - - p.WaitForExit(); - } + using Process process = new Process(); + process.StartInfo = startInfo; - if (terminalOutput != null) + // Game logging + if (useLogging) { - log.Info($"Performed logging setup for profile {profile.Name}."); + log.Info($"Performing logging setup for profile {profile.Name}."); + // Check if directory exists, and roll log file over if necessary if (!Directory.Exists(logDir.FullName)) Directory.CreateDirectory(logDir.FullName); - if (File.Exists($"{logDir.FullName}/{profile.Name}.txt")) - HelperMethods.RecursiveRollover($"{logDir.FullName}/{profile.Name}.txt", 5); - - StreamWriter stream = File.AppendText($"{logDir.FullName}/{profile.Name}.txt"); - - // Write general info - stream.WriteLine($"AM2RLauncher {Core.Version} log generated at {date}"); + if (File.Exists(logFile)) + HelperMethods.RecursiveRollover(logFile, 5); - // Write what was in the terminal - stream.WriteLine(terminalOutput); + // Write log header to file + File.WriteAllText(logFile, $"AM2RLauncher {Core.Version} log generated at {date}"); - stream.Flush(); + // Append STDOUT and STDERR to file if we get anything. + // Should this maybe be done synchronously? + process.StartInfo.RedirectStandardOutput = true; + process.OutputDataReceived += (_, e) => { File.AppendAllText(logFile, $"{e.Data}\n"); }; - stream.Close(); + process.StartInfo.RedirectStandardError = true; + process.ErrorDataReceived += (_, e) => { File.AppendAllText(logFile, $"{e.Data}\n"); }; } + process.Start(); + if (useLogging) + { + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + } + process.WaitForExit(); } else if (OS.IsMac) { - // Sets the arguments to only open the game, or append the profiles save path/logs and create time based logs. Creates the folder if necessary. + // Sets the arguments to only open, or to the profiles' logs folder to create time based logs. string arguments = "AM2R.app -W"; // Game logging @@ -800,47 +785,35 @@ public static class Profile { log.Info($"Performing logging setup for profile {profile.Name}."); + // Check if directory exists, and roll log file over if necessary if (!Directory.Exists(logDir.FullName)) Directory.CreateDirectory(logDir.FullName); - if (File.Exists($"{logDir.FullName}/{profile.Name}.txt")) - HelperMethods.RecursiveRollover($"{logDir.FullName}/{profile.Name}.txt", 5); - - StreamWriter stream = File.AppendText($"{logDir.FullName}/{profile.Name}.txt"); - - stream.WriteLine($"AM2RLauncher {Core.Version} log generated at {date}"); - - stream.Flush(); + if (File.Exists(logFile)) + HelperMethods.RecursiveRollover(logFile, 5); - stream.Close(); + // Write log header to file + File.WriteAllText(logFile, $"AM2RLauncher {Core.Version} log generated at {date}"); - arguments += $" --stdout \"{logDir.FullName}/{profile.Name}.txt\" --stderr \"{logDir.FullName}/{profile.Name}.txt\""; + arguments += $" --stdout \"{logFile}\" --stderr \"{logFile}\""; } - ProcessStartInfo proc = new ProcessStartInfo(); - - proc.WorkingDirectory = $"{Core.ProfilesPath}/{profile.Name}"; - proc.FileName = "open"; - proc.Arguments = arguments; + ProcessStartInfo processStartInfo = new ProcessStartInfo + { + WorkingDirectory = gameDirectory, + FileName = "open", + Arguments = arguments + }; - log.Info($"CWD of Profile is {proc.WorkingDirectory}"); + log.Info($"CWD of Profile is {processStartInfo.WorkingDirectory}"); - using Process p = Process.Start(proc); - p?.WaitForExit(); + using Process process = new Process { StartInfo = processStartInfo }; + process.Start(); + process.WaitForExit(); } else log.Error($"{OS.Name} cannot run games!"); log.Info($"Profile {profile.Name} process exited."); } - - /// - /// Checks if the repository has been validly cloned already. - /// - /// if yes, if not. - public static bool IsPatchDataCloned() - { - // isValid seems to only check for a .git folder, and there are cases where that exists, but not the profile.xml - return File.Exists($"{Core.PatchDataPath}/profile.xml") && Repository.IsValid(Core.PatchDataPath); - } } \ No newline at end of file