Cleanup RunGame and fix a crashing bug

pull/32/head
Miepee 4 years ago
parent 1b6fc27425
commit d50fe6244d

@ -322,7 +322,7 @@ public partial class MainForm
else 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 // 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"; 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; // 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;

@ -43,7 +43,7 @@ public static class Core
public static readonly string ModsPath = CrossPlatformOperations.CurrentPath + "/Mods"; public static readonly string ModsPath = CrossPlatformOperations.CurrentPath + "/Mods";
/// <summary> /// <summary>
/// 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.
/// </summary> /// </summary>
/// <param name="hWnd">Pointer to the process you want to have in the foreground.</param> /// <param name="hWnd">Pointer to the process you want to have in the foreground.</param>
/// <returns></returns> /// <returns></returns>

@ -61,7 +61,6 @@ public static class HelperMethods
} }
/// <summary> /// <summary>
/// This is a custom method, that deletes a Directory. The reason this is used, instead of <see cref="Directory.Delete(string)"/>, /// This is a custom method, that deletes a Directory. The reason this is used, instead of <see cref="Directory.Delete(string)"/>,
/// is because this one sets the attributes of all files to be deletable, while <see cref="Directory.Delete(string)"/> does not do that on it's own. /// is because this one sets the attributes of all files to be deletable, while <see cref="Directory.Delete(string)"/> does not do that on it's own.
@ -113,6 +112,7 @@ public static class HelperMethods
/// </summary> /// </summary>
/// <param name="logFile">The log file to begin the rollover from.</param> /// <param name="logFile">The log file to begin the rollover from.</param>
/// <param name="max">The maximum amount of log files to retain. Default is 9, as that's the highest digit.</param> /// <param name="max">The maximum amount of log files to retain. Default is 9, as that's the highest digit.</param>
//TODO: double check this method
public static void RecursiveRollover(string logFile, int max = 9) public static void RecursiveRollover(string logFile, int max = 9)
{ {
int index = 1; int index = 1;

@ -2,10 +2,13 @@
using log4net; using log4net;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using AM2RLauncherLib.XML; using AM2RLauncherLib.XML;
@ -150,6 +153,16 @@ public static class Profile
return IsZipAM2R11ReturnCodes.Successful; return IsZipAM2R11ReturnCodes.Successful;
} }
/// <summary>
/// Checks if the repository has been validly cloned already.
/// </summary>
/// <returns><see langword="true"/> if yes, <see langword="false"/> if not.</returns>
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);
}
/// <summary> /// <summary>
/// Git Pulls patching content from the repository. /// Git Pulls patching content from the repository.
/// </summary> /// </summary>
@ -634,21 +647,25 @@ public static class Profile
} }
/// <summary> /// <summary>
/// Runs the Game, works cross platform. /// Runs the game from a given profile.
/// </summary> /// </summary>
/// <param name="profile">The <see cref="ProfileXML"/> that should be run.</param>
/// <param name="useLogging">Whether game logging should take place.</param>
/// <param name="envVars">Environment variables with which the game should be launched. Linux only.</param>
public static void RunGame(ProfileXML profile, bool useLogging, string envVars = "") public static void RunGame(ProfileXML profile, bool useLogging, string envVars = "")
{ {
//TODO: double check this and clean // These are used on all OS for game logging
// These are used on both windows and linux for game logging string savePath = OS.IsWindows ? Environment.ExpandEnvironmentVariables(profile.SaveLocation)
string savePath = OS.IsWindows ? profile.SaveLocation.Replace("%localappdata%", Environment.GetEnvironmentVariable("LOCALAPPDATA")) : profile.SaveLocation.Replace("~", CrossPlatformOperations.Home);
: profile.SaveLocation.Replace("~", CrossPlatformOperations.Home);
DirectoryInfo logDir = new DirectoryInfo($"{savePath}/logs"); 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}."); log.Info($"Launching game profile {profile.Name}.");
if (OS.IsWindows) 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 = ""; string arguments = "";
// Game logging // Game logging
@ -656,143 +673,111 @@ public static class Profile
{ {
log.Info($"Performing 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)) if (!Directory.Exists(logDir.FullName))
Directory.CreateDirectory(logDir.FullName); Directory.CreateDirectory(logDir.FullName);
if (File.Exists($"{logDir.FullName}/{profile.Name}.txt")) if (File.Exists(logFile))
HelperMethods.RecursiveRollover($"{logDir.FullName}/{profile.Name}.txt", 5); HelperMethods.RecursiveRollover(logFile, 5);
StreamWriter stream = File.AppendText($"{logDir.FullName}/{profile.Name}.txt");
stream.WriteLine($"AM2RLauncher {Core.Version} log generated at {date}");
// Write log header to file
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append($"AM2RLauncher {Core.Version} log generated at {date}");
if (OS.IsThisRunningFromWine) if (OS.IsThisRunningFromWine)
stream.WriteLine("Using WINE!"); stringBuilder.Append("Using WINE!");
File.WriteAllText(logFile, stringBuilder.ToString());
stream.Flush();
stream.Close(); arguments = $"-debugoutput \"{logFile}\" -output \"{logFile}\"";
arguments = $"-debugoutput \"{logDir.FullName}/{profile.Name}.txt\" -output \"{logDir.FullName}/{profile.Name}.txt\"";
} }
ProcessStartInfo proc = new ProcessStartInfo(); ProcessStartInfo processStartInfo = new ProcessStartInfo
proc.WorkingDirectory = $"{Core.ProfilesPath}/{profile.Name}"; {
proc.FileName = $"{proc.WorkingDirectory}/AM2R.exe"; WorkingDirectory = gameDirectory,
proc.Arguments = arguments; 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); using Process process = new Process { StartInfo = processStartInfo };
Core.SetForegroundWindow(p.MainWindowHandle); process.Start();
p.WaitForExit(); Core.SetForegroundWindow(process.MainWindowHandle);
process.WaitForExit();
} }
else if (OS.IsLinux) else if (OS.IsLinux)
{ {
ProcessStartInfo startInfo = new ProcessStartInfo(); ProcessStartInfo startInfo = new ProcessStartInfo();
log.Info($"User does {(String.IsNullOrWhiteSpace(envVars) ? "not" : "")} have custom environment variables set."); 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)) 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 // After that, we split by '=' where the left is the variable and right is the value
string variable = envVars.Substring(0, envVars.IndexOf('=')); // If there is a malformed env var, this will throw.
envVars = envVars.Replace($"{variable}=", ""); string[] variableAsArray = providedVariable.Split('=');
string variable = variableAsArray[0];
// This thing here is the value parser. Since values are sometimes in quotes, i need to compensate for them. string value = variableAsArray[1];
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);
log.Info($"Adding user variable \"{variable}\" with value \"{value}\""); log.Info($"Adding user variable \"{variable}\" with value \"{value}\"");
startInfo.EnvironmentVariables[variable] = 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.UseShellExecute = false;
startInfo.WorkingDirectory = $"{Core.ProfilesPath}/{profile.Name}"; startInfo.WorkingDirectory = gameDirectory;
startInfo.FileName = $"{startInfo.WorkingDirectory}/AM2R.AppImage"; startInfo.FileName = $"{gameDirectory}/AM2R.AppImage";
log.Info($"CWD of Profile is {startInfo.WorkingDirectory}"); log.Info($"CWD of Profile is {startInfo.WorkingDirectory}");
log.Debug("Launching game with following variables: "); if (log.IsDebugEnabled)
foreach (System.Collections.DictionaryEntry item in startInfo.EnvironmentVariables)
{ {
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()) using Process process = new Process();
{ process.StartInfo = startInfo;
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();
}
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)) if (!Directory.Exists(logDir.FullName))
Directory.CreateDirectory(logDir.FullName); Directory.CreateDirectory(logDir.FullName);
if (File.Exists($"{logDir.FullName}/{profile.Name}.txt")) if (File.Exists(logFile))
HelperMethods.RecursiveRollover($"{logDir.FullName}/{profile.Name}.txt", 5); HelperMethods.RecursiveRollover(logFile, 5);
StreamWriter stream = File.AppendText($"{logDir.FullName}/{profile.Name}.txt");
// Write general info
stream.WriteLine($"AM2RLauncher {Core.Version} log generated at {date}");
// Write what was in the terminal // Write log header to file
stream.WriteLine(terminalOutput); 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) 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"; string arguments = "AM2R.app -W";
// Game logging // Game logging
@ -800,47 +785,35 @@ public static class Profile
{ {
log.Info($"Performing 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)) if (!Directory.Exists(logDir.FullName))
Directory.CreateDirectory(logDir.FullName); Directory.CreateDirectory(logDir.FullName);
if (File.Exists($"{logDir.FullName}/{profile.Name}.txt")) if (File.Exists(logFile))
HelperMethods.RecursiveRollover($"{logDir.FullName}/{profile.Name}.txt", 5); HelperMethods.RecursiveRollover(logFile, 5);
StreamWriter stream = File.AppendText($"{logDir.FullName}/{profile.Name}.txt");
stream.WriteLine($"AM2RLauncher {Core.Version} log generated at {date}");
stream.Flush();
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(); ProcessStartInfo processStartInfo = new ProcessStartInfo
{
proc.WorkingDirectory = $"{Core.ProfilesPath}/{profile.Name}"; WorkingDirectory = gameDirectory,
proc.FileName = "open"; FileName = "open",
proc.Arguments = arguments; Arguments = arguments
};
log.Info($"CWD of Profile is {proc.WorkingDirectory}"); log.Info($"CWD of Profile is {processStartInfo.WorkingDirectory}");
using Process p = Process.Start(proc); using Process process = new Process { StartInfo = processStartInfo };
p?.WaitForExit(); process.Start();
process.WaitForExit();
} }
else else
log.Error($"{OS.Name} cannot run games!"); log.Error($"{OS.Name} cannot run games!");
log.Info($"Profile {profile.Name} process exited."); log.Info($"Profile {profile.Name} process exited.");
} }
/// <summary>
/// Checks if the repository has been validly cloned already.
/// </summary>
/// <returns><see langword="true"/> if yes, <see langword="false"/> if not.</returns>
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);
}
} }
Loading…
Cancel
Save