Some cleanup

pull/32/head
Miepee 5 years ago
parent 398a44e92d
commit 8f6ef8212c

@ -9,12 +9,12 @@ namespace AM2RLauncher.Gtk;
/// <summary> /// <summary>
/// The main class for the GTK project. /// The main class for the GTK project.
/// </summary> /// </summary>
static class MainClass internal static class MainClass
{ {
/// <summary> /// <summary>
/// The logger for <see cref="MainForm"/>, used to write any caught exceptions. /// The logger for <see cref="MainForm"/>, used to write any caught exceptions.
/// </summary> /// </summary>
private static readonly ILog Log = LogManager.GetLogger(typeof(MainForm)); private static readonly ILog log = LogManager.GetLogger(typeof(MainForm));
/// <summary> /// <summary>
/// The main method for the GTK project. /// The main method for the GTK project.
/// </summary> /// </summary>
@ -36,14 +36,14 @@ static class MainClass
try try
{ {
Application GTKLauncher = new Application(Eto.Platforms.Gtk); Application gtkLauncher = new Application(Eto.Platforms.Gtk);
LauncherUpdater.Main(); LauncherUpdater.Main();
GTKLauncher.UnhandledException += GTKLauncher_UnhandledException; gtkLauncher.UnhandledException += GTKLauncher_UnhandledException;
GTKLauncher.Run(new MainForm()); gtkLauncher.Run(new MainForm());
} }
catch (Exception e) catch (Exception e)
{ {
Log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.StackTrace); log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.StackTrace);
Console.WriteLine(Language.Text.UnhandledException + "\n" + e.Message + "\n*****Stack Trace*****\n\n" + e.StackTrace); Console.WriteLine(Language.Text.UnhandledException + "\n" + e.Message + "\n*****Stack Trace*****\n\n" + e.StackTrace);
Console.WriteLine("Check the logs at " + launcherDataPath + " for more info!"); Console.WriteLine("Check the logs at " + launcherDataPath + " for more info!");
} }
@ -54,7 +54,7 @@ static class MainClass
/// </summary> /// </summary>
private static void GTKLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e) private static void GTKLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e)
{ {
Log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.ExceptionObject); log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.ExceptionObject);
Application.Instance.Invoke(() => Application.Instance.Invoke(() =>
{ {
MessageBox.Show(Language.Text.UnhandledException + "\n*****Stack Trace*****\n\n" + e.ExceptionObject, "GTK", MessageBoxType.Error); MessageBox.Show(Language.Text.UnhandledException + "\n*****Stack Trace*****\n\n" + e.ExceptionObject, "GTK", MessageBoxType.Error);
@ -100,5 +100,4 @@ static class MainClass
return Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); return Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
} }
} }

@ -2,6 +2,7 @@
using log4net; using log4net;
using log4net.Config; using log4net.Config;
using System; using System;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
namespace AM2RLauncher.Mac; namespace AM2RLauncher.Mac;
@ -9,12 +10,12 @@ namespace AM2RLauncher.Mac;
/// <summary> /// <summary>
/// The main class for the Mac project. /// The main class for the Mac project.
/// </summary> /// </summary>
static class MainClass internal static class MainClass
{ {
/// <summary> /// <summary>
/// The logger for <see cref="MainForm"/>, used to write any caught exceptions. /// The logger for <see cref="MainForm"/>, used to write any caught exceptions.
/// </summary> /// </summary>
private static readonly ILog Log = LogManager.GetLogger(typeof(MainForm)); private static readonly ILog log = LogManager.GetLogger(typeof(MainForm));
/// <summary> /// <summary>
/// The main method for the Mac project. /// The main method for the Mac project.
@ -44,7 +45,7 @@ static class MainClass
} }
catch (Exception e) catch (Exception e)
{ {
Log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.StackTrace); log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.StackTrace);
Console.WriteLine(Language.Text.UnhandledException + "\n" + e.Message + "\n*****Stack Trace*****\n\n" + e.StackTrace); Console.WriteLine(Language.Text.UnhandledException + "\n" + e.Message + "\n*****Stack Trace*****\n\n" + e.StackTrace);
Console.WriteLine("Check the logs at " + launcherDataPath + " for more info!"); Console.WriteLine("Check the logs at " + launcherDataPath + " for more info!");
} }
@ -55,7 +56,7 @@ static class MainClass
/// </summary> /// </summary>
private static void MacLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e) private static void MacLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e)
{ {
Log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.ExceptionObject); log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.ExceptionObject);
Application.Instance.Invoke(() => Application.Instance.Invoke(() =>
{ {
MessageBox.Show(Language.Text.UnhandledException + "\n*****Stack Trace*****\n\n" + e.ExceptionObject, "Mac", MessageBoxType.Error); MessageBox.Show(Language.Text.UnhandledException + "\n*****Stack Trace*****\n\n" + e.ExceptionObject, "Mac", MessageBoxType.Error);
@ -63,8 +64,23 @@ static class MainClass
} }
// This is a duplicate of CrossPlatformOperations.GenerateCurrentPath, because trying to invoke that would cause a crash due to currentPlatform not being initialized. // This is a duplicate of CrossPlatformOperations.GenerateCurrentPath, because trying to invoke that would cause a crash due to currentPlatform not being initialized.
[return: NotNull]
private static string GenerateCurrentPath() private static string GenerateCurrentPath()
{ {
string am2rLauncherDataEnvVar = Environment.GetEnvironmentVariable("AM2RLAUNCHERDATA");
if (!String.IsNullOrWhiteSpace(am2rLauncherDataEnvVar))
{
try
{
// This will create the directories recursively if they don't exist
Directory.CreateDirectory(am2rLauncherDataEnvVar);
// Our env var is now set and directories exist
return am2rLauncherDataEnvVar;
}
catch { }
}
string nixHome = Environment.GetEnvironmentVariable("HOME"); string nixHome = Environment.GetEnvironmentVariable("HOME");
//Mac has the Path at HOME/Library/AM2RLauncher //Mac has the Path at HOME/Library/AM2RLauncher
string macPath = nixHome + "/Library/AM2RLauncher"; string macPath = nixHome + "/Library/AM2RLauncher";

@ -10,12 +10,12 @@ namespace AM2RLauncher.Wpf;
/// <summary> /// <summary>
/// The main class for the WinForms project. /// The main class for the WinForms project.
/// </summary> /// </summary>
static class MainClass internal static class MainClass
{ {
/// <summary> /// <summary>
/// The logger for <see cref="MainForm"/>, used to write any caught exceptions. /// The logger for <see cref="MainForm"/>, used to write any caught exceptions.
/// </summary> /// </summary>
private static readonly ILog Log = LogManager.GetLogger(typeof(MainForm)); private static readonly ILog log = LogManager.GetLogger(typeof(MainForm));
/// <summary> /// <summary>
/// The main method for the WinForms project. /// The main method for the WinForms project.
/// </summary> /// </summary>
@ -45,7 +45,7 @@ static class MainClass
} }
catch (Exception e) catch (Exception e)
{ {
Log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.StackTrace); log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.StackTrace);
System.Windows.Forms.MessageBox.Show(Language.Text.UnhandledException + "\n" + e.Message + "\n*****Stack Trace*****\n\n" + e.StackTrace, "Microsoft .NET Framework", System.Windows.Forms.MessageBox.Show(Language.Text.UnhandledException + "\n" + e.Message + "\n*****Stack Trace*****\n\n" + e.StackTrace, "Microsoft .NET Framework",
System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error); System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
} }
@ -56,7 +56,7 @@ static class MainClass
/// </summary> /// </summary>
private static void WinLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e) private static void WinLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e)
{ {
Log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.ExceptionObject); log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.ExceptionObject);
MessageBox.Show(Language.Text.UnhandledException + "\n*****Stack Trace*****\n\n" + e.ExceptionObject, "Microsoft .NET Framework", MessageBoxType.Error); MessageBox.Show(Language.Text.UnhandledException + "\n*****Stack Trace*****\n\n" + e.ExceptionObject, "Microsoft .NET Framework", MessageBoxType.Error);
} }
@ -65,20 +65,22 @@ static class MainClass
{ {
// First, we check if the user has a custom AM2RLAUNCHERDATA env var // First, we check if the user has a custom AM2RLAUNCHERDATA env var
string am2rLauncherDataEnvVar = Environment.GetEnvironmentVariable("AM2RLAUNCHERDATA"); string am2rLauncherDataEnvVar = Environment.GetEnvironmentVariable("AM2RLAUNCHERDATA");
if (!String.IsNullOrWhiteSpace(am2rLauncherDataEnvVar)) // Windows has the path where the exe is located as default
string defaultPath = Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
if (String.IsNullOrWhiteSpace(am2rLauncherDataEnvVar))
return defaultPath;
try
{ {
try // This will create the directories recursively if they don't exist
{ Directory.CreateDirectory(am2rLauncherDataEnvVar);
// This will create the directories recursively if they don't exist
Directory.CreateDirectory(am2rLauncherDataEnvVar);
// Our env var is now set and directories exist // Our env var is now set and directories exist
return am2rLauncherDataEnvVar; return am2rLauncherDataEnvVar;
} }
catch { } catch
{
return defaultPath;
} }
// Windows has the path where the exe is located as default
return Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
} }
} }

@ -1,5 +1,4 @@
using AM2RLauncher.Core; using AM2RLauncher.Core;
using Eto;
using Eto.Forms; using Eto.Forms;
using log4net; using log4net;
using System; using System;
@ -21,41 +20,41 @@ namespace AM2RLauncher
public const string VERSION = Core.Core.VERSION; public const string VERSION = Core.Core.VERSION;
/// <summary>The Path of the oldConfig. Only gets used Windows-only</summary> /// <summary>The Path of the oldConfig. Only gets used Windows-only</summary>
private static readonly string OldConfigPath = CrossPlatformOperations.CURRENTPATH + "/" + CrossPlatformOperations.LAUNCHERNAME + ".oldCfg"; private static readonly string oldConfigPath = CrossPlatformOperations.CURRENTPATH + "/" + CrossPlatformOperations.LAUNCHERNAME + ".oldCfg";
/// <summary>The actual Path where the executable is stored, only used for updating.</summary> /// <summary>The actual Path where the executable is stored, only used for updating.</summary>
//TODO: for mac, this reports the path of the mac runner, not the actual .app //TODO: for mac, this reports the path of the mac runner, not the actual .app
private static readonly string UpdatePath = OS.IsWindows ? CrossPlatformOperations.CURRENTPATH : Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory); private static readonly string updatePath = OS.IsWindows ? CrossPlatformOperations.CURRENTPATH : Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
//TODO: Mac support for this in general //TODO: Mac support for this in general
/// <summary> /// <summary>
/// Our log object, that handles logging the current execution to a file. /// Our log object, that handles logging the current execution to a file.
/// </summary> /// </summary>
private static readonly ILog Log = LogManager.GetLogger(typeof(MainForm)); private static readonly ILog log = LogManager.GetLogger(typeof(MainForm));
/// <summary> /// <summary>
/// Performs the entire AM2RLauncher update procedure. /// Performs the entire AM2RLauncher update procedure.
/// </summary> /// </summary>
public static void Main() public static void Main()
{ {
Log.Info("Running update check..."); log.Info("Running update check...");
// Update section // Update section
// Clean old files that have been left // Clean old files that have been left
if (File.Exists(CrossPlatformOperations.CURRENTPATH + "/AM2RLauncher.bak")) if (File.Exists(CrossPlatformOperations.CURRENTPATH + "/AM2RLauncher.bak"))
{ {
Log.Info("AM2RLauncher.bak detected. Removing file."); log.Info("AM2RLauncher.bak detected. Removing file.");
File.Delete(CrossPlatformOperations.CURRENTPATH + "/AM2RLauncher.bak"); File.Delete(CrossPlatformOperations.CURRENTPATH + "/AM2RLauncher.bak");
} }
if (OS.IsWindows && File.Exists(OldConfigPath)) if (OS.IsWindows && File.Exists(oldConfigPath))
{ {
Log.Info(CrossPlatformOperations.LAUNCHERNAME + ".oldCfg detected. Removing file."); log.Info(CrossPlatformOperations.LAUNCHERNAME + ".oldCfg detected. Removing file.");
File.Delete(OldConfigPath); File.Delete(oldConfigPath);
} }
if (OS.IsWindows && Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/oldLib")) if (OS.IsWindows && Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/oldLib"))
{ {
Log.Info("Old lib folder detected, removing folder."); log.Info("Old lib folder detected, removing folder.");
Directory.Delete(CrossPlatformOperations.CURRENTPATH + "/oldLib", true); Directory.Delete(CrossPlatformOperations.CURRENTPATH + "/oldLib", true);
} }
@ -80,11 +79,11 @@ namespace AM2RLauncher
} }
// Check settings if autoUpdateLauncher is set to true // Check settings if autoUpdateLauncher is set to true
bool autoUpdate = bool.Parse(CrossPlatformOperations.ReadFromConfig("AutoUpdateLauncher")); bool autoUpdate = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("AutoUpdateLauncher"));
if (autoUpdate) if (autoUpdate)
{ {
Log.Info("AutoUpdate Launcher set to true!"); log.Info("AutoUpdate Launcher set to true!");
// This is supposed to fix the updater throwing an exception on windows 7 and earlier(?) // This is supposed to fix the updater throwing an exception on windows 7 and earlier(?)
// See this for information: https://stackoverflow.com/q/2859790 and https://stackoverflow.com/a/50977774 // See this for information: https://stackoverflow.com/q/2859790 and https://stackoverflow.com/a/50977774
@ -102,7 +101,7 @@ namespace AM2RLauncher
} }
catch (WebException) catch (WebException)
{ {
Log.Error("WebException caught! Displaying MessageBox."); log.Error("WebException caught! Displaying MessageBox.");
MessageBox.Show(Language.Text.NoInternetConnection); MessageBox.Show(Language.Text.NoInternetConnection);
return; return;
} }
@ -116,24 +115,24 @@ namespace AM2RLauncher
for (int i = 0; i < localVersionArray.Length; i++) for (int i = 0; i < localVersionArray.Length; i++)
{ {
int onlineNum = int.Parse(onlineVersionArray[i]); int onlineNum = Int32.Parse(onlineVersionArray[i]);
int localNum = int.Parse(localVersionArray[i]); int localNum = Int32.Parse(localVersionArray[i]);
if (onlineNum > localNum) if (onlineNum > localNum)
{ {
isCurrentVersionOutdated = true; isCurrentVersionOutdated = true;
break; break;
} }
else if (localNum > onlineNum) if (localNum > onlineNum)
break; break;
} }
Log.Info((isCurrentVersionOutdated ? "Updating" : "Not Updating") + " from " + VERSION + " to " + onlineVersion); log.Info((isCurrentVersionOutdated ? "Updating" : "Not Updating") + " from " + VERSION + " to " + onlineVersion);
// No new update, exiting // No new update, exiting
if (!isCurrentVersionOutdated) if (!isCurrentVersionOutdated)
return; return;
Log.Info("Current version (" + VERSION + ") is outdated! Initiating update for version " + onlineVersion + "."); log.Info("Current version (" + VERSION + ") is outdated! Initiating update for version " + onlineVersion + ".");
string tmpUpdatePath = CrossPlatformOperations.CURRENTPATH + "/tmpupdate/"; string tmpUpdatePath = CrossPlatformOperations.CURRENTPATH + "/tmpupdate/";
string zipPath = CrossPlatformOperations.CURRENTPATH + "/launcher.zip"; string zipPath = CrossPlatformOperations.CURRENTPATH + "/launcher.zip";
@ -152,31 +151,31 @@ namespace AM2RLauncher
if (OS.IsWindows) platformSuffix = "_win"; if (OS.IsWindows) platformSuffix = "_win";
else if (OS.IsLinux) platformSuffix = "_lin"; else if (OS.IsLinux) platformSuffix = "_lin";
Log.Info("Downloading https://github.com/AM2R-Community-Developers/AM2RLauncher/releases/latest/download/AM2RLauncher_" + onlineVersion + platformSuffix + ".zip to " + zipPath + "."); log.Info("Downloading https://github.com/AM2R-Community-Developers/AM2RLauncher/releases/latest/download/AM2RLauncher_" + onlineVersion + platformSuffix + ".zip to " + zipPath + ".");
client.DownloadFile("https://github.com/AM2R-Community-Developers/AM2RLauncher/releases/latest/download/AM2RLauncher_" + onlineVersion + platformSuffix + ".zip", zipPath); client.DownloadFile("https://github.com/AM2R-Community-Developers/AM2RLauncher/releases/latest/download/AM2RLauncher_" + onlineVersion + platformSuffix + ".zip", zipPath);
Log.Info("File successfully downloaded."); log.Info("File successfully downloaded.");
} }
} }
catch (UnauthorizedAccessException) catch (UnauthorizedAccessException)
{ {
Log.Error("UnauthorizedAccessException caught! Displaying MessageBox."); log.Error("UnauthorizedAccessException caught! Displaying MessageBox.");
MessageBox.Show(Language.Text.UnauthorizedAccessMessage); MessageBox.Show(Language.Text.UnauthorizedAccessMessage);
return; return;
} }
ZipFile.ExtractToDirectory(zipPath, tmpUpdatePath); ZipFile.ExtractToDirectory(zipPath, tmpUpdatePath);
Log.Info("Updates successfully extracted to " + tmpUpdatePath); log.Info("Updates successfully extracted to " + tmpUpdatePath);
File.Delete(zipPath); File.Delete(zipPath);
File.Move(UpdatePath + "/" + CrossPlatformOperations.LAUNCHERNAME, CrossPlatformOperations.CURRENTPATH + "/AM2RLauncher.bak"); File.Move(updatePath + "/" + CrossPlatformOperations.LAUNCHERNAME, CrossPlatformOperations.CURRENTPATH + "/AM2RLauncher.bak");
if (OS.IsWindows) File.Move(CrossPlatformOperations.LAUNCHERNAME + ".config", CrossPlatformOperations.LAUNCHERNAME + ".oldCfg"); if (OS.IsWindows) File.Move(CrossPlatformOperations.LAUNCHERNAME + ".config", CrossPlatformOperations.LAUNCHERNAME + ".oldCfg");
foreach (var file in new DirectoryInfo(tmpUpdatePath).GetFiles()) foreach (var file in new DirectoryInfo(tmpUpdatePath).GetFiles())
{ {
Log.Info("Moving " + file.FullName + " to " + CrossPlatformOperations.CURRENTPATH + "/" + file.Name); log.Info("Moving " + file.FullName + " to " + CrossPlatformOperations.CURRENTPATH + "/" + file.Name);
File.Copy(file.FullName, UpdatePath + "/" + file.Name, true); File.Copy(file.FullName, updatePath + "/" + file.Name, true);
} }
// For windows, the actual application is in "AM2RLauncher.dll". Which means, we need to update the lib folder as well. // For windows, the actual application is in "AM2RLauncher.dll". Which means, we need to update the lib folder as well.
if (OS.IsWindows && Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/lib")) if (OS.IsWindows && Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/lib"))
@ -207,17 +206,17 @@ namespace AM2RLauncher
CrossPlatformOperations.CopyOldConfigToNewConfig(); CrossPlatformOperations.CopyOldConfigToNewConfig();
Log.Info("Files extracted. Preparing to restart executable..."); log.Info("Files extracted. Preparing to restart executable...");
if (OS.IsLinux) System.Diagnostics.Process.Start("chmod", "+x ./AM2RLauncher.Gtk"); if (OS.IsLinux) System.Diagnostics.Process.Start("chmod", "+x ./AM2RLauncher.Gtk");
System.Diagnostics.Process.Start(UpdatePath + "/" + CrossPlatformOperations.LAUNCHERNAME); System.Diagnostics.Process.Start(updatePath + "/" + CrossPlatformOperations.LAUNCHERNAME);
Environment.Exit(0); Environment.Exit(0);
} }
else else
{ {
Log.Info("AutoUpdate Launcher set to false. Exiting update check."); log.Info("AutoUpdate Launcher set to false. Exiting update check.");
} }
} }
} }
} }

@ -7,7 +7,6 @@ using System.ComponentModel;
using System.IO; using System.IO;
using System.IO.Compression; using System.IO.Compression;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -538,7 +537,7 @@ namespace AM2RLauncher
{ {
Log.Info("User requested to add mod. Requesting user input for new mod .zip..."); Log.Info("User requested to add mod. Requesting user input for new mod .zip...");
ProfileXML addedProfile = null; ProfileXML addedProfile;
OpenFileDialog fileFinder = new OpenFileDialog OpenFileDialog fileFinder = new OpenFileDialog
{ {
@ -721,8 +720,8 @@ namespace AM2RLauncher
private void DrawablePaintEvent(object sender, PaintEventArgs e) private void DrawablePaintEvent(object sender, PaintEventArgs e)
{ {
// Get drawing variables // Get drawing variables
int height = drawable.Height; float height = drawable.Height;
int width = drawable.Width; float width = drawable.Width;
float scaleDivisor = OS.IsWindows ? 955f : 715f; // Magic brute-forced values. Don't ask questions, because we don't have answers >_> float scaleDivisor = OS.IsWindows ? 955f : 715f; // Magic brute-forced values. Don't ask questions, because we don't have answers >_>
// Also, seems like nix systems share the same scaleDivisor. Again, don't ask. // Also, seems like nix systems share the same scaleDivisor. Again, don't ask.
float scale = height / scaleDivisor; float scale = height / scaleDivisor;

@ -1,19 +1,9 @@
using Eto.Forms; using Eto.Forms;
using LibGit2Sharp;
using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
namespace AM2RLauncher namespace AM2RLauncher
{ {
public partial class MainForm : Form public partial class MainForm : Form
{ {
/// <summary> /// <summary>
/// Method that updates <see cref="progressBar"/>. /// Method that updates <see cref="progressBar"/>.
/// </summary> /// </summary>
@ -40,7 +30,6 @@ namespace AM2RLauncher
UpdateProgressBar(value, 0, 100); UpdateProgressBar(value, 0, 100);
} }
/// <summary> /// <summary>
/// Safety check function before accessing <see cref="profileIndex"/>. /// Safety check function before accessing <see cref="profileIndex"/>.
/// </summary> /// </summary>
@ -49,9 +38,5 @@ namespace AM2RLauncher
{ {
return profileIndex != null; return profileIndex != null;
} }
} }
} }

@ -160,13 +160,14 @@ namespace AM2RLauncher
bool enabled = false; bool enabled = false;
switch (updateState) switch (updateState)
{ {
case UpdateState.Download: enabled = false; break; case UpdateState.Download:
case UpdateState.Downloading: enabled = false; break; case UpdateState.Downloading:
case UpdateState.Select11: enabled = false; break; case UpdateState.Select11:
case UpdateState.Install: enabled = true; break; case UpdateState.Installing:
case UpdateState.Installing: enabled = false; break;
case UpdateState.Play: enabled = true; break;
case UpdateState.Playing: enabled = false; break; case UpdateState.Playing: enabled = false; break;
case UpdateState.Install:
case UpdateState.Play: enabled = true; break;
} }
if (apkButtonState == ApkButtonState.Creating) enabled = false; if (apkButtonState == ApkButtonState.Creating) enabled = false;

@ -81,21 +81,20 @@ namespace AM2RLauncher
/// </summary> /// </summary>
private static string currentMirror; private static string currentMirror;
private static bool isInternetThere = Core.Core.isInternetThere; private static bool isInternetThere = Core.Core.IsInternetThere;
private static bool isThisRunningFromWine = Core.Core.isThisRunningFromWine; private static bool isThisRunningFromWine = Core.Core.IsThisRunningFromWine;
private static bool singleInstance;
// This mutex needs to CONTINUE existing for the entire application's lifetime, or else the rest of this won't ever work!
// We're basically using it to key a thread and scan for other instances of that tag.
Mutex mutex = new Mutex(true, "AM2RLauncher", out singleInstance);
public MainForm() public MainForm()
{ {
// Exit if we're already running the AM2RLauncher // Exit if we're already running the AM2RLauncher
// Thanks, StackOverflow! https://stackoverflow.com/questions/184084/how-to-force-c-sharp-net-app-to-run-only-one-instance-in-windows // Thanks, StackOverflow! https://stackoverflow.com/questions/184084/how-to-force-c-sharp-net-app-to-run-only-one-instance-in-windows
bool singleInstance;
// This mutex needs to CONTINUE existing for the entire application's lifetime, or else the rest of this won't ever work!
// We're basically using it to key a thread and scan for other instances of that tag.
Mutex mutex = new Mutex(true, "AM2RLauncher", out singleInstance);
if (!singleInstance) if (!singleInstance)
{ {
// If on Windows, set the original app to the foreground window to prevent confusion // If on Windows, set the original app to the foreground window to prevent confusion
@ -149,7 +148,7 @@ namespace AM2RLauncher
Log.Info("Actual Launcher location: " + Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)); Log.Info("Actual Launcher location: " + Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory));
// Set the language to what User wanted or choose local language // Set the language to what User wanted or choose local language
string userLanguage = Core.CrossPlatformOperations.ReadFromConfig("Language").ToLower(); string userLanguage = CrossPlatformOperations.ReadFromConfig("Language").ToLower();
if (!userLanguage.Equals("default")) if (!userLanguage.Equals("default"))
Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultures(CultureTypes.AllCultures).First(c => c.NativeName.ToLower().Contains(userLanguage)); Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultures(CultureTypes.AllCultures).First(c => c.NativeName.ToLower().Contains(userLanguage));
@ -182,7 +181,7 @@ namespace AM2RLauncher
} }
// Custom splash texts // Custom splash texts
string splash = Core.Splash.GetSplash(); string splash = Splash.GetSplash();
Log.Info("Randomly chosen splash: " + splash); Log.Info("Randomly chosen splash: " + splash);
// Load bitmaps // Load bitmaps
@ -205,7 +204,7 @@ namespace AM2RLauncher
// Create mirror list - eventually this should be platform specific! // Create mirror list - eventually this should be platform specific!
// We do this as a List<Uri> so we can add more dynamically on user input... if necessary. // We do this as a List<Uri> so we can add more dynamically on user input... if necessary.
mirrorList = Core.CrossPlatformOperations.GenerateMirrorList(); mirrorList = CrossPlatformOperations.GenerateMirrorList();
// Create mirror list // Create mirror list
// We do this as a list<listItem> for 1) make this dynamic and 2) make ETO happy // We do this as a list<listItem> for 1) make this dynamic and 2) make ETO happy
@ -224,13 +223,13 @@ namespace AM2RLauncher
Title = "AM2RLauncher " + VERSION + ": " + splash; Title = "AM2RLauncher " + VERSION + ": " + splash;
MinimumSize = new Size(500, 400); MinimumSize = new Size(500, 400);
// TODO: for some reason, this sometimes doesn't work on Linux. Was reported at eto, stays here until its fixed // TODO: for some reason, this sometimes doesn't work on Linux. Was reported at eto, stays here until its fixed
ClientSize = new Size(Int32.Parse(Core.CrossPlatformOperations.ReadFromConfig("Width")), Int32.Parse(Core.CrossPlatformOperations.ReadFromConfig("Height"))); ClientSize = new Size(Int32.Parse(CrossPlatformOperations.ReadFromConfig("Width")), Int32.Parse(CrossPlatformOperations.ReadFromConfig("Height")));
if (ClientSize.Width < 500) if (ClientSize.Width < 500)
ClientSize = new Size(500, ClientSize.Height); ClientSize = new Size(500, ClientSize.Height);
if (ClientSize.Height < 400) if (ClientSize.Height < 400)
ClientSize = new Size(ClientSize.Width, 400); ClientSize = new Size(ClientSize.Width, 400);
Log.Info("Start the launcher with Size: " + ClientSize.Width + ", " + ClientSize.Height); Log.Info("Start the launcher with Size: " + ClientSize.Width + ", " + ClientSize.Height);
if (Boolean.Parse(Core.CrossPlatformOperations.ReadFromConfig("IsMaximized"))) Maximize(); if (Boolean.Parse(CrossPlatformOperations.ReadFromConfig("IsMaximized"))) Maximize();
drawable = new Drawable { BackgroundColor = colBGNoAlpha }; drawable = new Drawable { BackgroundColor = colBGNoAlpha };
@ -450,7 +449,6 @@ namespace AM2RLauncher
{ {
changelogWebView changelogWebView
} }
} }
}; };
@ -541,7 +539,7 @@ namespace AM2RLauncher
languageDropDown.Items.AddRange(languageList); languageDropDown.Items.AddRange(languageList);
var tmpLanguage = Core.CrossPlatformOperations.ReadFromConfig("Language"); var tmpLanguage = CrossPlatformOperations.ReadFromConfig("Language");
languageDropDown.SelectedIndex = tmpLanguage == "Default" ? 0 : languageDropDown.Items.IndexOf(languageDropDown.Items.FirstOrDefault(x => x.Text.Equals(tmpLanguage))); languageDropDown.SelectedIndex = tmpLanguage == "Default" ? 0 : languageDropDown.Items.IndexOf(languageDropDown.Items.FirstOrDefault(x => x.Text.Equals(tmpLanguage)));
if (languageDropDown.SelectedIndex == -1) if (languageDropDown.SelectedIndex == -1)
@ -553,7 +551,7 @@ namespace AM2RLauncher
// autoUpdateAM2R checkbox // autoUpdateAM2R checkbox
autoUpdateAM2RCheck = new CheckBox autoUpdateAM2RCheck = new CheckBox
{ {
Checked = Boolean.Parse(Core.CrossPlatformOperations.ReadFromConfig("AutoUpdateAM2R")), Checked = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("AutoUpdateAM2R")),
Text = Language.Text.AutoUpdateAM2R, Text = Language.Text.AutoUpdateAM2R,
TextColor = colGreen TextColor = colGreen
}; };
@ -562,7 +560,7 @@ namespace AM2RLauncher
// autoUpdateLauncher checkbox // autoUpdateLauncher checkbox
autoUpdateLauncherCheck = new CheckBox autoUpdateLauncherCheck = new CheckBox
{ {
Checked = Boolean.Parse(Core.CrossPlatformOperations.ReadFromConfig("AutoUpdateLauncher")), Checked = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("AutoUpdateLauncher")),
Text = Language.Text.AutoUpdateLauncher, Text = Language.Text.AutoUpdateLauncher,
TextColor = colGreen TextColor = colGreen
}; };
@ -570,7 +568,7 @@ namespace AM2RLauncher
// HQ music, PC // HQ music, PC
hqMusicPCCheck = new CheckBox hqMusicPCCheck = new CheckBox
{ {
Checked = Boolean.Parse(Core.CrossPlatformOperations.ReadFromConfig("MusicHQPC")), Checked = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("MusicHQPC")),
Text = Language.Text.HighQualityPC, Text = Language.Text.HighQualityPC,
TextColor = colGreen TextColor = colGreen
}; };
@ -578,7 +576,7 @@ namespace AM2RLauncher
// HQ music, Android // HQ music, Android
hqMusicAndroidCheck = new CheckBox hqMusicAndroidCheck = new CheckBox
{ {
Checked = Boolean.Parse(Core.CrossPlatformOperations.ReadFromConfig("MusicHQAndroid")), Checked = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("MusicHQAndroid")),
Text = Language.Text.HighQualityAndroid, Text = Language.Text.HighQualityAndroid,
TextColor = colGreen TextColor = colGreen
}; };
@ -639,14 +637,14 @@ namespace AM2RLauncher
// Custom mirror // Custom mirror
customMirrorCheck = new CheckBox customMirrorCheck = new CheckBox
{ {
Checked = Boolean.Parse(Core.CrossPlatformOperations.ReadFromConfig("CustomMirrorEnabled")), Checked = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("CustomMirrorEnabled")),
Text = Language.Text.CustomMirrorCheck, Text = Language.Text.CustomMirrorCheck,
TextColor = colGreen TextColor = colGreen
}; };
customMirrorTextBox = new TextBox customMirrorTextBox = new TextBox
{ {
Text = Core.CrossPlatformOperations.ReadFromConfig("CustomMirrorText"), Text = CrossPlatformOperations.ReadFromConfig("CustomMirrorText"),
BackgroundColor = colBGNoAlpha, BackgroundColor = colBGNoAlpha,
TextColor = colGreen TextColor = colGreen
}; };
@ -985,4 +983,4 @@ namespace AM2RLauncher
#endregion #endregion
} }
} }

@ -2,7 +2,6 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
</PropertyGroup> </PropertyGroup>
@ -12,4 +11,4 @@
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>

@ -1,53 +1,49 @@
using AM2RLauncher.Core.XML; using log4net;
using log4net;
using System; using System;
using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace AM2RLauncher.Core namespace AM2RLauncher.Core;
{
/// <summary>
/// Class that has core stuff that doesn't fit anywhere else
/// </summary>
public static class Core
{
/// <summary> /// <summary>
/// Class that has core stuff that doesn't fit anywhere else /// Our log object, that handles logging the current execution to a file.
/// </summary> /// </summary>
public static class Core public static readonly ILog Log = LogManager.GetLogger(typeof(Core));
{
/// <summary>
/// Our log object, that handles logging the current execution to a file.
/// </summary>
public static readonly ILog Log = LogManager.GetLogger(typeof(Core));
/// <summary>The Version that identifies this current release.</summary> /// <summary>The Version that identifies this current release.</summary>
public const string VERSION = "2.2.0"; public const string VERSION = "2.2.0";
/// <summary> /// <summary>
/// Checks if this is run via WINE. /// Checks if this is run via WINE.
/// </summary> /// </summary>
public static bool isThisRunningFromWine = CheckIfRunFromWINE(); public static readonly bool IsThisRunningFromWine = CheckIfRunFromWINE();
/// <summary> /// <summary>
/// Indicates whether or not we have established an internet connection. /// Indicates whether or not we have established an internet connection.
/// </summary> /// </summary>
public static readonly bool isInternetThere = HelperMethods.IsConnectedToInternet(); public static readonly bool IsInternetThere = HelperMethods.IsConnectedToInternet();
/// <summary> /// <summary>
/// Checks if this is ran from WINE /// Checks if this is ran from WINE
/// </summary> /// </summary>
/// <returns><see langword="true"/> if run from WINE, <see langword="false"/> if not.</returns> /// <returns><see langword="true"/> if run from WINE, <see langword="false"/> if not.</returns>
private static bool CheckIfRunFromWINE() private static bool CheckIfRunFromWINE()
{ {
if (OS.IsWindows && Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software\\Wine") != null) if (OS.IsWindows && Microsoft.Win32.Registry.CurrentUser.OpenSubKey("Software\\Wine") != null)
return true; return true;
return false; return false;
}
/// <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.
/// </summary>
/// <param name="hWnd">Pointer to the process you want to have in the foreground.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
} }
}
/// <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.
/// </summary>
/// <param name="hWnd">Pointer to the process you want to have in the foreground.</param>
/// <returns></returns>
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hWnd);
}

@ -7,550 +7,539 @@ using System.IO;
using System.Reflection; using System.Reflection;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
namespace AM2RLauncher.Core namespace AM2RLauncher.Core;
/// <summary>
/// Class that does operations that work cross-platform.
/// </summary>
public static class CrossPlatformOperations
{ {
/// <summary> /// <summary>
/// Class that does operations that work cross-platform. /// The logger for <see cref="Core"/>, used to write any caught exceptions.
/// </summary>
private static readonly ILog log = Core.Log;
/// <summary>
/// Name of the Launcher executable.
/// </summary>
public static readonly string LAUNCHERNAME = AppDomain.CurrentDomain.FriendlyName;
/// <summary>
/// Path to the Home Folder on *Nix-based systems.
/// </summary>
public static readonly string NIXHOME = Environment.GetEnvironmentVariable("HOME");
/// <summary>
/// Path to the Config folder on Linux-based systems.
/// </summary> /// </summary>
public static class CrossPlatformOperations private static readonly string LINUXXDGCONFIG = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
/// <summary>
/// Path to the Config file folder on *nix based systems. <br/>
/// Linux: Will point to XDG_CONFIG_HOME/AM2RLauncher <br/>
/// Mac: Will point to ~/Library/Preferences/AM2RLauncher
/// </summary>
private static readonly string NIXLAUNCHERCONFIGPATH = OS.IsLinux ? (String.IsNullOrWhiteSpace(LINUXXDGCONFIG) ? NIXHOME + "/.config"
: LINUXXDGCONFIG) + "/AM2RLauncher"
: NIXHOME + "/Library/Preferences/AM2RLauncher";
/// <summary>
/// Config file path for *nix based systems. Will be <see cref="NIXLAUNCHERCONFIGPATH"/> + "/config.xml".
/// </summary>
private static readonly string NIXLAUNCHERCONFIGFILEPATH = NIXLAUNCHERCONFIGPATH + "/config.xml";
/// <summary>
/// Current Path where the Launcher is located. For more info, check <see cref="GenerateCurrentPath"/>.
/// </summary>
public static readonly string CURRENTPATH = GenerateCurrentPath();
/// <summary>
/// Generates the mirror list, depending on the current Platform.
/// </summary>
/// <returns>A <see cref="List{String}"/> containing the mirror links.</returns>
public static List<string> GenerateMirrorList()
{ {
/// <summary> if (OS.IsWindows)
/// The logger for <see cref="MainForm"/>, used to write any caught exceptions.
/// </summary>
private static readonly ILog Log = Core.Log;
/// <summary>
/// Name of the Launcher executable.
/// </summary>
public static readonly string LAUNCHERNAME = AppDomain.CurrentDomain.FriendlyName;
/// <summary>
/// Path to the Home Folder on *Nix-based systems.
/// </summary>
public static readonly string NIXHOME = Environment.GetEnvironmentVariable("HOME");
/// <summary>
/// Path to the Config folder on Linux-based systems.
/// </summary>
private static readonly string LINUXXDGCONFIG = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
/// <summary>
/// Path to the Config file folder on *nix based systems. <br/>
/// Linux: Will point to XDG_CONFIG_HOME/AM2RLauncher <br/>
/// Mac: Will point to ~/Library/Preferences/AM2RLauncher
/// </summary>
private static readonly string NIXLAUNCHERCONFIGPATH = OS.IsLinux ? (String.IsNullOrWhiteSpace(LINUXXDGCONFIG) ? NIXHOME + "/.config"
: LINUXXDGCONFIG) + "/AM2RLauncher"
: NIXHOME + "/Library/Preferences/AM2RLauncher";
/// <summary>
/// Config file path for *nix based systems. Will be <see cref="NIXLAUNCHERCONFIGPATH"/> + "/config.xml".
/// </summary>
private static readonly string NIXLAUNCHERCONFIGFILEPATH = NIXLAUNCHERCONFIGPATH + "/config.xml";
/// <summary>
/// Current Path where the Launcher is located. For more info, check <see cref="GenerateCurrentPath"/>.
/// </summary>
public static readonly string CURRENTPATH = GenerateCurrentPath();
/// <summary>
/// Generates the mirror list, depending on the current Platform.
/// </summary>
/// <returns>A <see cref="List{String}"/> containing the mirror links.</returns>
public static List<string> GenerateMirrorList()
{ {
if (OS.IsWindows) return new List<string>
{ {
return new List<string> "https://github.com/AM2R-Community-Developers/AM2R-Autopatcher-Windows.git",
{ "https://gitlab.com/am2r-community-developers/AM2R-Autopatcher-Windows.git"
"https://github.com/AM2R-Community-Developers/AM2R-Autopatcher-Windows.git", };
"https://gitlab.com/am2r-community-developers/AM2R-Autopatcher-Windows.git" }
}; if (OS.IsLinux)
} {
else if (OS.IsLinux) return new List<string>
{ {
return new List<string> "https://github.com/AM2R-Community-Developers/AM2R-Autopatcher-Linux.git",
{ "https://gitlab.com/am2r-community-developers/AM2R-Autopatcher-Linux.git"
"https://github.com/AM2R-Community-Developers/AM2R-Autopatcher-Linux.git", };
"https://gitlab.com/am2r-community-developers/AM2R-Autopatcher-Linux.git" }
}; if (OS.IsMac)
} {
else if (OS.IsMac) return new List<string>
{ {
return new List<string> "https://github.com/Miepee/AM2R-Autopatcher-Mac.git"
{ //TODO: make mac official at some point:tm:, put this on gitlab
"https://github.com/Miepee/AM2R-Autopatcher-Mac.git" };
//TODO: make mac official at some point:tm:, put this on gitlab
};
}
else // Should never occur, but...
{
Log.Error(OS.Name + " has no mirror lists!");
return new List<string>();
}
} }
/// <summary> // Should never occur, but...
/// Reads the Launcher config file on the current Platform and returns the value for <paramref name="property"/>. log.Error(OS.Name + " has no mirror lists!");
/// </summary> return new List<string>();
/// <param name="property">The property to get the value from.</param> }
/// <returns>The value from <paramref name="property"/> as a string</returns>
public static string ReadFromConfig(string property) /// <summary>
/// Reads the Launcher config file on the current Platform and returns the value for <paramref name="property"/>.
/// </summary>
/// <param name="property">The property to get the value from.</param>
/// <returns>The value from <paramref name="property"/> as a string</returns>
public static string ReadFromConfig(string property)
{
log.Info($"Reading {property} from config.");
if (OS.IsWindows)
{
// We use the configuration manager in order to read `property` from the app.config and then return it
ConnectionStringSettings appConfig = ConfigurationManager.ConnectionStrings[property];
if (appConfig == null) throw new ArgumentException("The property " + property + " could not be found.");
return appConfig.ConnectionString;
}
if (OS.IsUnix)
{ {
Log.Info($"Reading {property} from config."); string launcherConfigPath = NIXLAUNCHERCONFIGPATH;
if (OS.IsWindows) string launcherConfigFilePath = launcherConfigPath + "/config.xml";
XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML();
// If folder doesn't exist, create it and the config file
if (!Directory.Exists(launcherConfigPath) || !File.Exists(launcherConfigFilePath))
{ {
// We use the configuration manager in order to read `property` from the app.config and then return it Directory.CreateDirectory(launcherConfigPath);
ConnectionStringSettings appConfig = ConfigurationManager.ConnectionStrings[property]; File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize<XML.LauncherConfigXML>(launcherConfig));
if (appConfig == null) throw new ArgumentException("The property " + property + " could not be found.");
return appConfig.ConnectionString;
} }
else if (OS.IsUnix)
{
string launcherConfigPath = NIXLAUNCHERCONFIGPATH;
string launcherConfigFilePath = launcherConfigPath + "/config.xml";
XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML();
// If folder doesn't exist, create it and the config file // Deserialize the config xml into launcherConfig
if (!Directory.Exists(launcherConfigPath) || !File.Exists(launcherConfigFilePath)) launcherConfig = XML.Serializer.Deserialize<XML.LauncherConfigXML>(File.ReadAllText(launcherConfigFilePath));
{
Directory.CreateDirectory(launcherConfigPath);
File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize<XML.LauncherConfigXML>(launcherConfig));
}
// Deserialize the config xml into launcherConfig // This uses the indexer, which means, we can use the variable in order to get the property. Look at LauncherConfigXML for more info
launcherConfig = XML.Serializer.Deserialize<XML.LauncherConfigXML>(File.ReadAllText(launcherConfigFilePath)); return launcherConfig[property]?.ToString();
}
if (launcherConfig[property] == null) log.Error(OS.Name + " has no config to read from!");
return null; return null;
}
// This uses the indexer, which means, we can use the variable in order to get the property. Look at LauncherConfigXML for more info /// <summary>
return launcherConfig[property]?.ToString(); /// Writes <paramref name="value"/> to <paramref name="property"/> in the Launcher Config file.
} /// </summary>
else /// <param name="property">The property whose value you want to change.</param>
Log.Error(OS.Name + " has no config to read from!"); /// <param name="value">The value that will be written.</param>
return null; public static void WriteToConfig(string property, object value)
{
log.Info($"Writing {value} of type {value.GetType()} to {property} to config.");
if (OS.IsWindows)
{
// We use the configuration manager in order to read from the app.config, change the value and save it
Configuration appConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
if (appConfig == null)
throw new NullReferenceException("Could not find the Config file! Please make sure it exists!");
ConnectionStringsSection connectionStringsSection = (ConnectionStringsSection)appConfig.GetSection("connectionStrings");
if (connectionStringsSection?.ConnectionStrings[property]?.ConnectionString == null)
throw new ArgumentException("The property " + property + " could not be found.");
connectionStringsSection.ConnectionStrings[property].ConnectionString = value.ToString();
appConfig.Save();
ConfigurationManager.RefreshSection("connectionStrings");
} }
else if (OS.IsUnix)
/// <summary>
/// Writes <paramref name="value"/> to <paramref name="property"/> in the Launcher Config file.
/// </summary>
/// <param name="property">The property whose value you want to change.</param>
/// <param name="value">The value that will be written.</param>
public static void WriteToConfig(string property, object value)
{ {
Log.Info($"Writing {value} of type {value.GetType()} to {property} to config."); string launcherConfigPath = NIXLAUNCHERCONFIGPATH;
if (OS.IsWindows) string launcherConfigFilePath = NIXLAUNCHERCONFIGFILEPATH;
{ XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML();
// We use the configuration manager in order to read from the app.config, change the value and save it
Configuration appConfig = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); // If folder doesn't exist, create it and the config file
if (appConfig == null) if (!Directory.Exists(launcherConfigPath) || !File.Exists(launcherConfigFilePath))
throw new NullReferenceException("Could not find the Config file! Please make sure it exists!");
ConnectionStringsSection connectionStringsSection = (ConnectionStringsSection)appConfig.GetSection("connectionStrings");
if (connectionStringsSection?.ConnectionStrings[property]?.ConnectionString == null)
throw new ArgumentException("The property " + property + " could not be found.");
connectionStringsSection.ConnectionStrings[property].ConnectionString = value.ToString();
appConfig.Save();
ConfigurationManager.RefreshSection("connectionStrings");
}
else if (OS.IsUnix)
{ {
string launcherConfigPath = NIXLAUNCHERCONFIGPATH; Directory.CreateDirectory(launcherConfigPath);
string launcherConfigFilePath = NIXLAUNCHERCONFIGFILEPATH;
XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML();
// If folder doesn't exist, create it and the config file
if (!Directory.Exists(launcherConfigPath) || !File.Exists(launcherConfigFilePath))
{
Directory.CreateDirectory(launcherConfigPath);
File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize<XML.LauncherConfigXML>(launcherConfig));
}
// Deserialize the config xml into launcherConfig
launcherConfig = XML.Serializer.Deserialize<XML.LauncherConfigXML>(File.ReadAllText(launcherConfigFilePath));
// Uses indexer. Look at LauncherConfigXML for more info
launcherConfig[property] = value;
// Serialize back into the file
File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize<XML.LauncherConfigXML>(launcherConfig)); File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize<XML.LauncherConfigXML>(launcherConfig));
} }
else // Deserialize the config xml into launcherConfig
Log.Error(OS.Name + " has no config to write to!"); launcherConfig = XML.Serializer.Deserialize<XML.LauncherConfigXML>(File.ReadAllText(launcherConfigFilePath));
// Uses indexer. Look at LauncherConfigXML for more info
launcherConfig[property] = value;
// Serialize back into the file
File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize<XML.LauncherConfigXML>(launcherConfig));
} }
else
log.Error(OS.Name + " has no config to write to!");
}
/// <summary> /// <summary>
/// When a Launcher update occurs that introduces new config properties, this method ensures that the old user config is copied over as much as possible. /// When a Launcher update occurs that introduces new config properties, this method ensures that the old user config is copied over as much as possible.
/// </summary> /// </summary>
public static void CopyOldConfigToNewConfig() public static void CopyOldConfigToNewConfig()
{
if (OS.IsWindows)
{ {
if (OS.IsWindows) string oldConfigPath = LAUNCHERNAME + ".oldCfg";
{ string newConfigPath = LAUNCHERNAME + ".config";
string oldConfigPath = LAUNCHERNAME + ".oldCfg"; string oldConfigText = File.ReadAllText(oldConfigPath);
string newConfigPath = LAUNCHERNAME + ".config"; string newConfigText = File.ReadAllText(newConfigPath);
string oldConfigText = File.ReadAllText(oldConfigPath);
string newConfigText = File.ReadAllText(newConfigPath);
Regex settingRegex = new Regex("<add name=\".*\" />"); Regex settingRegex = new Regex("<add name=\".*\" />");
MatchCollection oldMatch = settingRegex.Matches(oldConfigText); MatchCollection oldMatch = settingRegex.Matches(oldConfigText);
MatchCollection newMatch = settingRegex.Matches(newConfigText); MatchCollection newMatch = settingRegex.Matches(newConfigText);
for (int i = 0; i < oldMatch.Count; i++) for (int i = 0; i < oldMatch.Count; i++)
newConfigText = newConfigText.Replace(newMatch[i].Value, oldMatch[i].Value); newConfigText = newConfigText.Replace(newMatch[i].Value, oldMatch[i].Value);
File.WriteAllText(newConfigPath, newConfigText); File.WriteAllText(newConfigPath, newConfigText);
} }
else if (OS.IsUnix) else if (OS.IsUnix)
{ {
string launcherConfigFilePath = NIXLAUNCHERCONFIGFILEPATH; string launcherConfigFilePath = NIXLAUNCHERCONFIGFILEPATH;
// For some reason deserializing and saving back again works, not exactly sure why, but I'll take it // For some reason deserializing and saving back again works, not exactly sure why, but I'll take it
XML.LauncherConfigXML launcherConfig = XML.Serializer.Deserialize<XML.LauncherConfigXML>(File.ReadAllText(launcherConfigFilePath)); XML.LauncherConfigXML launcherConfig = XML.Serializer.Deserialize<XML.LauncherConfigXML>(File.ReadAllText(launcherConfigFilePath));
File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize<XML.LauncherConfigXML>(launcherConfig)); File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize<XML.LauncherConfigXML>(launcherConfig));
}
else
Log.Error(OS.Name + " has no config to transfer over!");
} }
else
log.Error(OS.Name + " has no config to transfer over!");
}
/// <summary> /// <summary>
/// This open a website cross-platform. /// This open a website cross-platform.
/// </summary> /// </summary>
/// <param name="url">The URL of the website to be opened.</param> /// <param name="url">The URL of the website to be opened.</param>
public static void OpenURL(string url) public static void OpenURL(string url)
{
if (OS.IsWindows)
Process.Start(url);
else if (OS.IsLinux)
Process.Start("xdg-open", url);
else if (OS.IsMac)
Process.Start("open", url);
else
log.Error(OS.Name + " can't open URLs!");
}
/// <summary>
/// Opens <paramref name="path"/> in a file explorer. Creates the directory if it doesn't exist.
/// </summary>
/// <param name="path">Path to open.</param>
public static void OpenFolder(string path)
{
// We have to replace forward slashes with backslashes here on windows because explorer.exe is picky...
// And on Nix systems, we want to replace ~ with its corresponding env var
string realPath = OS.IsWindows ? Environment.ExpandEnvironmentVariables(path).Replace("/", "\\")
: path.Replace("~", NIXHOME);
if (!Directory.Exists(realPath))
{ {
if (OS.IsWindows) log.Info(realPath + " did not exist and was created");
Process.Start(url); Directory.CreateDirectory(realPath);
else if (OS.IsLinux)
Process.Start("xdg-open", url);
else if (OS.IsMac)
Process.Start("open", url);
else
Log.Error(OS.Name + " can't open URLs!");
} }
/// <summary> // Needs quotes otherwise paths with space wont open
/// Opens <paramref name="path"/> in a file explorer. Creates the directory if it doesn't exist. if (OS.IsWindows)
/// </summary> // And we're using explorer.exe to prevent people from stuffing system commands in here wholesale. That would be bad.
/// <param name="path">Path to open.</param> Process.Start("explorer.exe", $"\"{realPath}\"");
public static void OpenFolder(string path) // Linux only opens the directory bc opening and selecting a file is pain
else if (OS.IsLinux)
Process.Start("xdg-open", $"\"{realPath}\"");
else if (OS.IsMac)
Process.Start("open", $"\"{realPath}\"");
else
log.Error(OS.Name + " can't open folders!");
}
/// <summary>
/// Opens <paramref name="path"/> and selects it in a file explorer.
/// Only selects on Windows and Mac, on Linux it just opens the folder. Does nothing if file doesn't exist.
/// </summary>
/// <param name="path">Path to open.</param>
public static void OpenFolderAndSelectFile(string path)
{
// We have to replace forward slashes with backslashes here on windows because explorer.exe is picky...
// And on nix systems, we want to replace ~ with its corresponding env var
string realPath = OS.IsWindows ? Environment.ExpandEnvironmentVariables(path).Replace("/", "\\")
: path.Replace("~", NIXHOME);
if (!File.Exists(realPath))
{ {
// We have to replace forward slashes with backslashes here on windows because explorer.exe is picky... log.Error(realPath + "did not exist, operation to open its folder was cancelled!");
// And on Nix systems, we want to replace ~ with its corresponding env var return;
string realPath = OS.IsWindows ? Environment.ExpandEnvironmentVariables(path).Replace("/", "\\") }
: path.Replace("~", NIXHOME);
if (!Directory.Exists(realPath)) // Needs quotes otherwise paths with spaces wont open
{ if (OS.IsWindows)
Log.Info(realPath + " did not exist and was created"); // And we're using explorer.exe to prevent people from stuffing system commands in here wholesale. That would be bad.
Directory.CreateDirectory(realPath); Process.Start("explorer.exe", $"/select, \"{realPath}\"");
} else if (OS.IsLinux)
Process.Start("xdg-open", $"\"{Path.GetDirectoryName(realPath)}\"");
else if (OS.IsMac)
Process.Start("open", $"-R \"{realPath}\"");
else
log.Error(OS.Name + " can't open select files in file explorer!");
}
// Needs quotes otherwise paths with space wont open /// <summary>
if (OS.IsWindows) /// Checks if command-line Java is installed.
// And we're using explorer.exe to prevent people from stuffing system commands in here wholesale. That would be bad. /// </summary>
Process.Start("explorer.exe", $"\"{realPath}\""); /// <returns><see langword="true"/> if it is installed, <see langword="false"/> if not.</returns>
// Linux only opens the directory bc opening and selecting a file is pain public static bool IsJavaInstalled()
else if (OS.IsLinux) {
Process.Start("xdg-open", $"\"{realPath}\""); string process = "";
else if (OS.IsMac) string arguments = "";
Process.Start("open", $"\"{realPath}\"");
else if (OS.IsWindows)
Log.Error(OS.Name + " can't open folders!"); {
process = "cmd.exe";
arguments = "/C java -version";
}
else if (OS.IsUnix)
{
process = "java";
arguments = "-version";
} }
/// <summary> ProcessStartInfo javaStart = new ProcessStartInfo
/// Opens <paramref name="path"/> and selects it in a file explorer.
/// Only selects on Windows and Mac, on Linux it just opens the folder. Does nothing if file doesn't exist.
/// </summary>
/// <param name="path">Path to open.</param>
public static void OpenFolderAndSelectFile(string path)
{ {
// We have to replace forward slashes with backslashes here on windows because explorer.exe is picky... FileName = process,
// And on nix systems, we want to replace ~ with its corresponding env var Arguments = arguments,
string realPath = OS.IsWindows ? Environment.ExpandEnvironmentVariables(path).Replace("/", "\\") UseShellExecute = false,
: path.Replace("~", NIXHOME); CreateNoWindow = true
if (!File.Exists(realPath)) };
{
Log.Error(realPath + "did not exist, operation to open its folder was cancelled!");
return;
}
// Needs quotes otherwise paths with spaces wont open
if (OS.IsWindows)
// And we're using explorer.exe to prevent people from stuffing system commands in here wholesale. That would be bad.
Process.Start("explorer.exe", $"/select, \"{realPath}\"");
else if (OS.IsLinux)
Process.Start("xdg-open", $"\"{Path.GetDirectoryName(realPath)}\"");
else if (OS.IsMac)
Process.Start("open", $"-R \"{realPath}\"");
else
Log.Error(OS.Name + " can't open select files in file explorer!");
}
/// <summary> Process java = new Process { StartInfo = javaStart };
/// Checks if command-line Java is installed.
/// </summary> // This is primarily for linux, but could be happening on windows as well
/// <returns><see langword="true"/> if it is installed, <see langword="false"/> if not.</returns> try
public static bool IsJavaInstalled()
{ {
string process = ""; java.Start();
string arguments = "";
if (OS.IsWindows) java.WaitForExit();
{ }
process = "cmd.exe"; catch (System.ComponentModel.Win32Exception)
arguments = "/C java -version"; {
} return false;
else if (OS.IsUnix) }
{
process = "java";
arguments = "-version";
}
ProcessStartInfo javaStart = new ProcessStartInfo return java.ExitCode == 0;
{ }
FileName = process,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true
};
/// <summary>
/// Checks if command-line xdelta is installed on non-Windows systems.
/// </summary>
/// <returns><see langword="true"/> if it is installed, <see langword="false"/> if not.</returns>
public static bool CheckIfXdeltaIsInstalled()
{
const string process = "xdelta3";
const string arguments = "-V";
Process java = new Process { StartInfo = javaStart }; ProcessStartInfo xdeltaStart = new ProcessStartInfo
{
FileName = process,
Arguments = arguments,
UseShellExecute = false,
CreateNoWindow = true
};
// This is primarily for linux, but could be happening on windows as well
try
{
java.Start();
java.WaitForExit(); Process xdelta = new Process { StartInfo = xdeltaStart };
}
catch (System.ComponentModel.Win32Exception)
{
return false;
}
return java.ExitCode == 0; try
} {
xdelta.Start();
/// <summary> xdelta.WaitForExit();
/// Checks if command-line xdelta is installed on non-Windows systems. }
/// </summary> catch (System.ComponentModel.Win32Exception)
/// <returns><see langword="true"/> if it is installed, <see langword="false"/> if not.</returns>
public static bool CheckIfXdeltaIsInstalled()
{ {
const string process = "xdelta3"; return false;
const string arguments = "-V"; }
return xdelta.ExitCode == 0;
}
ProcessStartInfo xdeltaStart = new ProcessStartInfo /// <summary>
/// This applies an Xdelta Patch cross-platform.
/// </summary>
/// <param name="original">Full Path to the original file.</param>
/// <param name="patch">Full Path to the Xdelta patch to apply.</param>
/// <param name="output">Full Path to the output file.</param>
public static void ApplyXdeltaPatch(string original, string patch, string output)
{
// For *whatever reason* **sometimes** xdelta patching doesn't work, if output = original. So I'm fixing that here.
string originalOutput = output;
if (original == output)
output += "_";
string arguments = "-f -d -s \"" + original.Replace(CURRENTPATH + "/", "") + "\" \"" + patch.Replace(CURRENTPATH + "/", "")
+ "\" \"" + output.Replace(CURRENTPATH + "/", "") + "\"";
if (OS.IsWindows)
{
// We want some fancy parameters for Windows because the terminal scares end users :(
ProcessStartInfo parameters = new ProcessStartInfo
{ {
FileName = process, FileName = CURRENTPATH + "/PatchData/utilities/xdelta/xdelta3.exe",
Arguments = arguments, WorkingDirectory = CURRENTPATH + "",
UseShellExecute = false, UseShellExecute = false,
CreateNoWindow = true CreateNoWindow = true,
Arguments = arguments
}; };
using Process proc = new Process { StartInfo = parameters };
proc.Start();
proc.WaitForExit();
}
else if (OS.IsUnix)
{
ProcessStartInfo parameters = new ProcessStartInfo
{
FileName = "xdelta3",
Arguments = arguments,
WorkingDirectory = CURRENTPATH
};
Process xdelta = new Process { StartInfo = xdeltaStart }; using Process proc = Process.Start(parameters);
proc?.WaitForExit();
}
try if (originalOutput == output || !File.Exists(output))
{ return;
xdelta.Start();
File.Delete(originalOutput);
File.Move(output, originalOutput);
}
xdelta.WaitForExit(); public static void RunJavaJar(string arguments = null, string workingDirectory = null)
} {
catch (System.ComponentModel.Win32Exception) workingDirectory ??= Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
{ string proc = "",
return false; javaArgs = "";
}
return xdelta.ExitCode == 0; if (OS.IsWindows)
{
proc = "cmd";
javaArgs = "/C java -jar";
}
else if (OS.IsUnix)
{
proc = "java";
javaArgs = "-jar";
} }
/// <summary> ProcessStartInfo jarStart = new ProcessStartInfo
/// This applies an Xdelta Patch cross-platform. {
/// </summary> FileName = proc,
/// <param name="original">Full Path to the original file.</param> Arguments = javaArgs + " " + arguments,
/// <param name="patch">Full Path to the Xdelta patch to apply.</param> WorkingDirectory = workingDirectory,
/// <param name="output">Full Path to the output file.</param> UseShellExecute = false,
public static void ApplyXdeltaPatch(string original, string patch, string output) CreateNoWindow = true
};
Process jarProcess = new Process
{ {
// For *whatever reason* **sometimes** xdelta patching doesn't work, if output = original. So I'm fixing that here. StartInfo = jarStart
string originalOutput = output; };
if (original == output)
output += "_";
string arguments = "-f -d -s \"" + original.Replace(CURRENTPATH + "/", "") + "\" \"" + patch.Replace(CURRENTPATH + "/", "") jarProcess.Start();
+ "\" \"" + output.Replace(CURRENTPATH + "/", "") + "\"";
if (OS.IsWindows) jarProcess.WaitForExit();
{ }
// We want some fancy parameters for Windows because the terminal scares end users :(
ProcessStartInfo parameters = new ProcessStartInfo /// <summary>
{ /// Figures out what the AM2RLauncher's <see cref="CURRENTPATH"/> should be.<br/>
FileName = CURRENTPATH + "/PatchData/utilities/xdelta/xdelta3.exe", /// Determination is as follows:
WorkingDirectory = CURRENTPATH + "", /// <list type="number">
UseShellExecute = false, /// <item><b>$AM2RLAUNCHERDATA</b> environment variable is read and folders are recursively generated.</item>
CreateNoWindow = true, /// <item>The current OS is checked. For Windows, the path where the executable is located will be returned.<br/>
Arguments = arguments /// For Linux, <b>$XDG_DATA_HOME/AM2RLauncher</b> will be returned.
}; /// Should <b>$XDG_DATA_HOME</b> be empty, it will default to <b>$HOME/.local/share</b>.</item>
/// <item>The path where the executable is located will be returned.</item>
using (Process proc = new Process { StartInfo = parameters }) /// </list>
{ /// Should any errors occur, it falls down to the next step.
proc.Start(); /// </summary>
/// <returns></returns>
proc.WaitForExit(); private static string GenerateCurrentPath()
} {
} // First, we check if the user has a custom AM2RLAUNCHERDATA env var
else if (OS.IsUnix) string am2rLauncherDataEnvVar = Environment.GetEnvironmentVariable("AM2RLAUNCHERDATA");
if (!String.IsNullOrWhiteSpace(am2rLauncherDataEnvVar))
{
try
{ {
ProcessStartInfo parameters = new ProcessStartInfo // This will create the directories recursively if they don't exist
{ Directory.CreateDirectory(am2rLauncherDataEnvVar);
FileName = "xdelta3",
Arguments = arguments,
WorkingDirectory = CURRENTPATH
};
using (Process proc = Process.Start(parameters))
{
proc?.WaitForExit();
}
}
if (originalOutput != output && File.Exists(output)) // Our env var is now set and directories exist
log.Info("CurrentPath is set to " + am2rLauncherDataEnvVar);
return am2rLauncherDataEnvVar;
}
catch (Exception ex)
{ {
File.Delete(originalOutput); log.Error($"There was an error with '{am2rLauncherDataEnvVar}'!\n{ex.Message} {ex.StackTrace}. Falling back to defaults.");
File.Move(output, originalOutput);
} }
} }
public static void RunJavaJar(string arguments = null, string workingDirectory = null) if (OS.IsWindows)
{ {
if (workingDirectory == null) log.Info("Using default Windows CurrentPath.");
workingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile); // Windows has the path where the exe is located as default
string proc = "", return Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
javaArgs = ""; }
else if (OS.IsLinux)
if (OS.IsWindows) {
{ // First check if XDG_DATA_HOME is set, if not we'll use ~/.local/share
proc = "cmd"; string xdgDataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
javaArgs = "/C java -jar"; if (String.IsNullOrWhiteSpace(xdgDataHome))
}
else if (OS.IsUnix)
{ {
proc = "java"; log.Info("Using default Linux CurrentPath.");
javaArgs = "-jar"; xdgDataHome = NIXHOME + "/.local/share";
} }
ProcessStartInfo jarStart = new ProcessStartInfo // Add AM2RLauncher to the end of the dataPath
{ xdgDataHome += "/AM2RLauncher";
FileName = proc,
Arguments = javaArgs + " " + arguments,
WorkingDirectory = workingDirectory,
UseShellExecute = false,
CreateNoWindow = true
};
Process jarProcess = new Process try
{ {
StartInfo = jarStart // This will create the directories recursively if they don't exist
}; Directory.CreateDirectory(xdgDataHome);
jarProcess.Start();
jarProcess.WaitForExit(); // Our env var is now set and directories exist
} log.Info("CurrentPath is set to " + xdgDataHome);
return xdgDataHome;
/// <summary>
/// Figures out what the AM2RLauncher's <see cref="CURRENTPATH"/> should be.<br/>
/// Determination is as follows:
/// <list type="number">
/// <item><b>$AM2RLAUNCHERDATA</b> environment variable is read and folders are recursively generated.</item>
/// <item>The current OS is checked. For Windows, the path where the executable is located will be returned.<br/>
/// For Linux, <b>$XDG_DATA_HOME/AM2RLauncher</b> will be returned.
/// Should <b>$XDG_DATA_HOME</b> be empty, it will default to <b>$HOME/.local/share</b>.</item>
/// <item>The path where the executable is located will be returned.</item>
/// </list>
/// Should any errors occur, it falls down to the next step.
/// </summary>
/// <returns></returns>
private static string GenerateCurrentPath()
{
// First, we check if the user has a custom AM2RLAUNCHERDATA env var
string am2rLauncherDataEnvVar = Environment.GetEnvironmentVariable("AM2RLAUNCHERDATA");
if (!String.IsNullOrWhiteSpace(am2rLauncherDataEnvVar))
{
try
{
// This will create the directories recursively if they don't exist
Directory.CreateDirectory(am2rLauncherDataEnvVar);
// Our env var is now set and directories exist
Log.Info("CurrentPath is set to " + am2rLauncherDataEnvVar);
return am2rLauncherDataEnvVar;
}
catch (Exception ex)
{
Log.Error($"There was an error with '{am2rLauncherDataEnvVar}'!\n{ex.Message} {ex.StackTrace}. Falling back to defaults.");
}
} }
catch (Exception ex)
if (OS.IsWindows)
{ {
Log.Info("Using default Windows CurrentPath."); log.Error($"There was an error with '{xdgDataHome}'!\n{ex.Message} {ex.StackTrace}. Falling back to defaults.");
// Windows has the path where the exe is located as default
return Path.GetDirectoryName(Assembly.GetEntryAssembly()?.Location);
} }
else if (OS.IsLinux) }
else if (OS.IsMac)
{
//Mac has the Path at HOME/Library/AM2RLauncher
string macPath = NIXHOME + "/Library/AM2RLauncher";
try
{ {
// First check if XDG_DATA_HOME is set, if not we'll use ~/.local/share Directory.CreateDirectory(macPath);
string xdgDataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME"); log.Info("Using default Mac CurrentPath.");
if (String.IsNullOrWhiteSpace(xdgDataHome)) return macPath;
{
Log.Info("Using default Linux CurrentPath.");
xdgDataHome = NIXHOME + "/.local/share";
}
// Add AM2RLauncher to the end of the dataPath
xdgDataHome += "/AM2RLauncher";
try
{
// This will create the directories recursively if they don't exist
Directory.CreateDirectory(xdgDataHome);
// Our env var is now set and directories exist
Log.Info("CurrentPath is set to " + xdgDataHome);
return xdgDataHome;
}
catch (Exception ex)
{
Log.Error($"There was an error with '{xdgDataHome}'!\n{ex.Message} {ex.StackTrace}. Falling back to defaults.");
}
} }
else if (OS.IsMac) catch (Exception ex)
{ {
//Mac has the Path at HOME/Library/AM2RLauncher log.Error($"There was an error with '{macPath}'!\n{ex.Message} {ex.StackTrace}. Falling back to defaults.");
string macPath = NIXHOME + "/Library/AM2RLauncher";
try
{
Directory.CreateDirectory(macPath);
Log.Info("Using default Mac CurrentPath.");
return macPath;
}
catch (Exception ex)
{
Log.Error($"There was an error with '{macPath}'!\n{ex.Message} {ex.StackTrace}. Falling back to defaults.");
}
} }
else
Log.Error(OS.Name + " has no current path!");
Log.Info("Something went wrong, falling back to the default CurrentPath.");
return Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
} }
else
log.Error(OS.Name + " has no current path!");
log.Info("Something went wrong, falling back to the default CurrentPath.");
return Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
} }
}
}

@ -1,177 +1,163 @@
using LibGit2Sharp; using log4net;
using log4net;
using System; using System;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net; using System.Net;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text;
namespace AM2RLauncher.Core namespace AM2RLauncher.Core;
/// <summary>
/// Class that has various Helper functions. Basically anything that could be used outside the Launcher resides here.
/// </summary>
public static class HelperMethods
{ {
// Load reference to logger
/// <summary> /// <summary>
/// Class that has various Helper functions. Basically anything that could be used outside the Launcher resides here. /// Our log object, that handles logging the current execution to a file.
/// </summary> /// </summary>
public static class HelperMethods private static readonly ILog log = Core.Log;
// Thank you, Microsoft docs: https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories
// Slightly modified by adding overwriteFiles bool, as we need to replace readme, music, etc.
/// <summary>
/// This copies the contents of a specified Directory recursively to another Directory.
/// </summary>
/// <param name="sourceDirName">Full Path to the Directory that will be recursively copied.</param>
/// <param name="destDirName">Full Path to the Directory you want to copy to. If the Directory does not exist, it will be created.</param>
/// <param name="overwriteFiles">Specify if Files should be overwritten or not</param>
/// <param name="copySubDirs">Specify if you want to copy Sub-Directories as well.</param>
public static void DirectoryCopy(string sourceDirName, string destDirName, bool overwriteFiles = true, bool copySubDirs = true)
{ {
// Load reference to logger // Get the subdirectories for the specified directory.
/// <summary> DirectoryInfo dir = new DirectoryInfo(sourceDirName);
/// Our log object, that handles logging the current execution to a file.
/// </summary> if (!dir.Exists)
private static readonly ILog Log = Core.Log;
// Thank you, Microsoft docs: https://docs.microsoft.com/en-us/dotnet/standard/io/how-to-copy-directories
// Slightly modified by adding overwriteFiles bool, as we need to replace readme, music, etc.
/// <summary>
/// This copies the contents of a specified Directory recursively to another Directory.
/// </summary>
/// <param name="sourceDirName">Full Path to the Directory that will be recursively copied.</param>
/// <param name="destDirName">Full Path to the Directory you want to copy to. If the Directory does not exist, it will be created.</param>
/// <param name="overwriteFiles">Specify if Files should be overwritten or not</param>
/// <param name="copySubDirs">Specify if you want to copy Sub-Directories as well.</param>
public static void DirectoryCopy(string sourceDirName, string destDirName, bool overwriteFiles = true, bool copySubDirs = true)
{ {
// Get the subdirectories for the specified directory. throw new DirectoryNotFoundException(
DirectoryInfo dir = new DirectoryInfo(sourceDirName); "Source directory does not exist or could not be found: "
+ sourceDirName);
if (!dir.Exists) }
{
throw new DirectoryNotFoundException( DirectoryInfo[] dirs = dir.GetDirectories();
"Source directory does not exist or could not be found: "
+ sourceDirName);
}
DirectoryInfo[] dirs = dir.GetDirectories();
// If the destination directory doesn't exist, create it.
Directory.CreateDirectory(destDirName);
// Get the files in the directory and copy them to the new location.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string tempPath = Path.Combine(destDirName, file.Name);
file.CopyTo(tempPath, overwriteFiles);
}
// If copying subdirectories, copy them and their contents to new location.
if (!copySubDirs)
return;
foreach (DirectoryInfo subDir in dirs)
{
string tempPath = Path.Combine(destDirName, subDir.Name);
DirectoryCopy(subDir.FullName, tempPath, overwriteFiles);
}
// If the destination directory doesn't exist, create it.
Directory.CreateDirectory(destDirName);
// Get the files in the directory and copy them to the new location.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
{
string tempPath = Path.Combine(destDirName, file.Name);
file.CopyTo(tempPath, overwriteFiles);
} }
// If copying subdirectories, copy them and their contents to new location.
if (!copySubDirs)
return;
/// <summary> foreach (DirectoryInfo subDir in dirs)
/// 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.
/// It's needed, because sometimes there are read-only files being generated, that would normally need more code in order to reset the attributes.<br/>
/// Note, that this method acts recursively.
/// </summary>
/// <param name="targetDir">The directory to delete.</param>
public static void DeleteDirectory(string targetDir)
{ {
if (!Directory.Exists(targetDir)) return; string tempPath = Path.Combine(destDirName, subDir.Name);
DirectoryCopy(subDir.FullName, tempPath, overwriteFiles);
}
}
File.SetAttributes(targetDir, FileAttributes.Normal);
foreach (string file in Directory.GetFiles(targetDir)) /// <summary>
{ /// This is a custom method, that deletes a Directory. The reason this is used, instead of <see cref="Directory.Delete(string)"/>,
File.SetAttributes(file, FileAttributes.Normal); /// 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.
File.Delete(file); /// It's needed, because sometimes there are read-only files being generated, that would normally need more code in order to reset the attributes.<br/>
} /// Note, that this method acts recursively.
/// </summary>
/// <param name="targetDir">The directory to delete.</param>
public static void DeleteDirectory(string targetDir)
{
if (!Directory.Exists(targetDir)) return;
foreach (string dir in Directory.GetDirectories(targetDir)) File.SetAttributes(targetDir, FileAttributes.Normal);
{
DeleteDirectory(dir);
}
Directory.Delete(targetDir, false); foreach (string file in Directory.GetFiles(targetDir))
{
File.SetAttributes(file, FileAttributes.Normal);
File.Delete(file);
} }
/// <summary> foreach (string dir in Directory.GetDirectories(targetDir))
/// Calculates an MD5 hash from a given file.
/// </summary>
/// <param name="filename">Full Path to the file whose MD5 hash is supposed to be calculated.</param>
/// <returns>The MD5 hash as a <see cref="string"/>, empty string if file does not exist.</returns>
public static string CalculateMD5(string filename)
{ {
// Check if File exists first DeleteDirectory(dir);
if (!File.Exists(filename))
return "";
using (var stream = File.OpenRead(filename))
{
using (var md5 = MD5.Create())
{
var hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}
} }
/// <summary> Directory.Delete(targetDir, false);
/// Performs recursive rollover on a set of log files. }
/// </summary>
/// <param name="logFile">The log file to begin the rollover from.</param> /// <summary>
/// <param name="max">The maximum amount of log files to retain. Default is 9, as that's the highest digit.</param> /// Calculates an MD5 hash from a given file.
public static void RecursiveRollover(string logFile, int max = 9) /// </summary>
/// <param name="filename">Full Path to the file whose MD5 hash is supposed to be calculated.</param>
/// <returns>The MD5 hash as a <see cref="string"/>, empty string if file does not exist.</returns>
public static string CalculateMD5(string filename)
{
// Check if File exists first
if (!File.Exists(filename))
return "";
using var stream = File.OpenRead(filename);
using var md5 = MD5.Create();
var hash = md5.ComputeHash(stream);
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
/// <summary>
/// Performs recursive rollover on a set of log files.
/// </summary>
/// <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>
public static void RecursiveRollover(string logFile, int max = 9)
{
int index = 1;
char endChar = logFile[logFile.Length - 1];
string fileName;
// If not the original file, set the new index and get the new fileName.
if (Char.IsNumber(endChar))
{ {
int index = 1; index = Int32.Parse(endChar.ToString()) + 1;
char endChar = logFile[logFile.Length - 1]; fileName = logFile.Remove(logFile.Length - 1) + index;
string fileName;
// If not the original file, set the new index and get the new fileName.
if (char.IsNumber(endChar))
{
index = int.Parse(endChar.ToString()) + 1;
fileName = logFile.Remove(logFile.Length - 1) + index;
}
else // Otherwise, if the original file, just set fileName to log.txt.1.
fileName = logFile + ".1";
// If new name already exists, run the rollover algorithm on it!
if (File.Exists(fileName))
{
RecursiveRollover(fileName, max);
}
//TODO: this can fail if one doesnt have permissions to move or delete the file
// If index is less than max, rename file.
if (index < max)
{
File.Move(logFile, fileName);
}
else // Otherwise, delete the file.
File.Delete(logFile);
} }
else // Otherwise, if the original file, just set fileName to log.txt.1.
fileName = logFile + ".1";
// If new name already exists, run the rollover algorithm on it!
if (File.Exists(fileName))
RecursiveRollover(fileName, max);
//TODO: this can fail if one doesnt have permissions to move or delete the file
// If index is less than max, rename file.
if (index < max)
File.Move(logFile, fileName);
else // Otherwise, delete the file.
File.Delete(logFile);
}
/// <summary> /// <summary>
/// Checks if we currently have an internet connection, by pinging github. /// Checks if we currently have an internet connection, by pinging github.
/// </summary> /// </summary>
/// <returns><see langword="true"/> if we have internet, <see langword="false"/> if not.</returns> /// <returns><see langword="true"/> if we have internet, <see langword="false"/> if not.</returns>
public static bool IsConnectedToInternet() public static bool IsConnectedToInternet()
{
log.Info("Checking internet connection...");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://github.com");
try
{
HttpWebResponse _ = (HttpWebResponse)request.GetResponse();
}
catch (WebException)
{ {
Log.Info("Checking internet connection..."); log.Info("Internet connection failed.");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://github.com"); return false;
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
}
catch (WebException)
{
Log.Info("Internet connection failed.");
return false;
}
Log.Info("Internet connection established!");
return true;
} }
log.Info("Internet connection established!");
return true;
} }
} }

@ -1,53 +1,49 @@
using System; using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Text;
namespace AM2RLauncher.Core namespace AM2RLauncher.Core;
/// <summary>
/// Class that has information about the current running operating system.
/// </summary>
public static class OS
{ {
/// <summary> /// <summary>
/// Class that has information about the current running operating system. /// Determines if the current OS is Windows.
/// </summary> /// </summary>
public class OS public static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
{
/// <summary>
/// Determines if the current OS is Windows.
/// </summary>
public static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
/// <summary> /// <summary>
/// Determines if the current OS is Linux. /// Determines if the current OS is Linux.
/// </summary> /// </summary>
public static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux); public static readonly bool IsLinux = RuntimeInformation.IsOSPlatform(OSPlatform.Linux);
/// <summary> /// <summary>
/// Determines if the current OS is Mac. /// Determines if the current OS is Mac.
/// </summary> /// </summary>
public static readonly bool IsMac = RuntimeInformation.IsOSPlatform(OSPlatform.OSX); public static readonly bool IsMac = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
/// <summary> /// <summary>
/// Determines if the current OS is a unix based system (Mac or Linux). /// Determines if the current OS is a unix based system (Mac or Linux).
/// </summary> /// </summary>
public static readonly bool IsUnix = IsLinux || IsMac; public static readonly bool IsUnix = IsLinux || IsMac;
/// <summary> /// <summary>
/// Gets a string representation of the current OS. /// Gets a string representation of the current OS.
/// </summary> /// </summary>
public static readonly string Name = DetermineOSName(); public static readonly string Name = DetermineOsName();
/// <summary> /// <summary>
/// Generates a string representation of the current OS /// Generates a string representation of the current OS
/// </summary> /// </summary>
private static string DetermineOSName() private static string DetermineOsName()
{ {
if (IsWindows) if (IsWindows)
return "Windows"; return "Windows";
else if (IsLinux) if (IsLinux)
return "Linux"; return "Linux";
else if (IsMac) if (IsMac)
return "Mac"; return "Mac";
else
return "Unknown OS"; return "Unknown OS";
}
} }
} }

File diff suppressed because it is too large Load Diff

@ -1,131 +1,130 @@
using System; using System;
using System.Linq; using System.Linq;
namespace AM2RLauncher.Core namespace AM2RLauncher.Core;
/// <summary>
/// Class only for providing Splashes
/// </summary>
public static class Splash
{ {
/// <summary> /// <summary>
/// Class only for providing Splashes /// Cross-Platform splash strings
/// </summary> /// </summary>
public static class Splash private static readonly string[] generalSplash =
{ {
/// <summary> "The real Ridley is the friends we made along the way.",
/// Cross-Platform splash strings "Now with 100% more Septoggs!",
/// </summary> "Now with 100% more Blob Throwers!",
private static readonly string[] generalSplash = "Speedrun THIS, I dare you.",
{ "The broken pipe is a lie.",
"The real Ridley is the friends we made along the way.", "Overcommitting to April Fool's since 2018.",
"Now with 100% more Septoggs!", "Also try Metroid II: Return of Samus!",
"Now with 100% more Blob Throwers!", "Also try Metroid: Samus Returns!",
"Speedrun THIS, I dare you.", "Also try Prime 2D!",
"The broken pipe is a lie.", "Trust me, it's an 'unintentional feature.'",
"Overcommitting to April Fool's since 2018.", "Coming soon to a PC near you!",
"Also try Metroid II: Return of Samus!", "This ain't your parents' Metroid 2!",
"Also try Metroid: Samus Returns!", "Now setting boss pattern to S P E E N",
"Also try Prime 2D!", "You S P E E N me right round, baby!",
"Trust me, it's an 'unintentional feature.'", "Wait, I thought Metroid was a guy!",
"Coming soon to a PC near you!", "jellyfish is helping yes",
"This ain't your parents' Metroid 2!", "Why can't Metroid crawl?",
"Now setting boss pattern to S P E E N", "When in doubt, blame GameMaker.",
"You S P E E N me right round, baby!", "It just works™",
"Wait, I thought Metroid was a guy!", "Reject C++, return to ABSTRACTION",
"jellyfish is helping yes", "C# is just C++++ with Windows support.",
"Why can't Metroid crawl?", "Use of the Launcher has now been authorized!",
"When in doubt, blame GameMaker.", "Oh? So you're patching me?",
"It just works™", "I, Gawron Giovana, have a dream.",
"Reject C++, return to ABSTRACTION", "Oooh weee ooo, I look just like an Omega",
"C# is just C++++ with Windows support.", "AH THE MOLE",
"Use of the Launcher has now been authorized!", "S P I D E R B A L L",
"Oh? So you're patching me?", "Fun is infinite with Community-Developers Inc.",
"I, Gawron Giovana, have a dream.", "You may only proceed if you wear baggy pants.",
"Oooh weee ooo, I look just like an Omega", "Did you know that games take time to develop?"
"AH THE MOLE", };
"S P I D E R B A L L",
"Fun is infinite with Community-Developers Inc.",
"You may only proceed if you wear baggy pants.",
"Did you know that games take time to develop?"
};
/// <summary> /// <summary>
/// Windows only splash strings /// Windows only splash strings
/// </summary> /// </summary>
private static readonly string[] windowsSplash = private static readonly string[] windowsSplash =
{ {
":(", ":(",
"All your machine are belong to MS" "All your machine are belong to MS"
}; };
/// <summary> /// <summary>
/// Linux only splash strings /// Linux only splash strings
/// </summary> /// </summary>
private static readonly string[] linuxSplash = private static readonly string[] linuxSplash =
{ {
"Sorry this is ugly, but at least it works.", "Sorry this is ugly, but at least it works.",
"GTK + QT = 💣", "GTK + QT = 💣",
"I hope you use Arch, btw", "I hope you use Arch, btw",
"All your Ubuntu are belong to Arch btw.", "All your Ubuntu are belong to Arch btw.",
"Help, how do I quit vim!?", "Help, how do I quit vim!?",
"The bloat isn't our fault, YoYo Games forced 1GB dependencies!", "The bloat isn't our fault, YoYo Games forced 1GB dependencies!",
"On second thought, maybe the bloat is our fault...", "On second thought, maybe the bloat is our fault...",
"The quieter you are, the more Metroids you can hear.", "The quieter you are, the more Metroids you can hear.",
"What you are referring to as AM2R, is in fact, GNU/AM2R.", "What you are referring to as AM2R, is in fact, GNU/AM2R.",
"GNOME be gone!", "GNOME be gone!",
"Go compile your own girlfriend. This one doesn't count, she's out of your league.", "Go compile your own girlfriend. This one doesn't count, she's out of your league.",
"Year 3115, NVIDIA still doesn't support Wayland.", "Year 3115, NVIDIA still doesn't support Wayland.",
"Was a mouse really that expensive, i3 users!?", "Was a mouse really that expensive, i3 users!?",
"Imagine using non-free software.", "Imagine using non-free software.",
"What if... we used a non-FOSS OS? Haha just kidding... Unless?", "What if... we used a non-FOSS OS? Haha just kidding... Unless?",
"🐧", "🐧",
"You are already patched 🐸🕶", "You are already patched 🐸🕶",
"Yes, do as I say!", "Yes, do as I say!",
"Did you find the penguin yet?" "Did you find the penguin yet?"
}; };
/// <summary> /// <summary>
/// Mac only splash strings /// Mac only splash strings
/// </summary> /// </summary>
private static readonly string[] macSplash = private static readonly string[] macSplash =
{ {
"Does this even work?", "Does this even work?",
"You weren't supposed to do that!", "You weren't supposed to do that!",
"Another Mac 2 Remake", "Another Mac 2 Remake",
"This shouldn't even be possible", "This shouldn't even be possible",
"How long until this breaks?", "How long until this breaks?",
"Have fun trying to convince modders to support this!" "Have fun trying to convince modders to support this!"
}; };
/// <summary> /// <summary>
/// Combined splash strings /// Combined splash strings
/// </summary> /// </summary>
private static readonly string[] combinedSplash = combineSplashes(); private static readonly string[] combinedSplash = CombineSplashes();
/// <summary> /// <summary>
/// Get a random splash string, according to the current OS. /// Get a random splash string, according to the current OS.
/// </summary> /// </summary>
/// <returns>The randomly chosen splash as a <see cref="string"/>.</returns> /// <returns>The randomly chosen splash as a <see cref="string"/>.</returns>
public static string GetSplash() public static string GetSplash()
{ {
Random rng = new Random(); Random rng = new Random();
string splashString = combinedSplash[rng.Next(0, combinedSplash.Length)]; string splashString = combinedSplash[rng.Next(0, combinedSplash.Length)];
return splashString; return splashString;
} }
/// <summary> /// <summary>
/// Creates a string srray which is <see cref="generalSplash"/> concatonated with the splash array for the current OS /// Creates a string srray which is <see cref="generalSplash"/> concatonated with the splash array for the current OS
/// </summary> /// </summary>
/// <returns>A string array where <see cref="generalSplash"/> and the splash array for the current OS have been concatonated.</returns> /// <returns>A string array where <see cref="generalSplash"/> and the splash array for the current OS have been concatonated.</returns>
private static string[] combineSplashes() private static string[] CombineSplashes()
{ {
string[] totalSplashes; string[] totalSplashes;
if (OS.IsWindows) if (OS.IsWindows)
totalSplashes = generalSplash.Concat(windowsSplash).ToArray(); totalSplashes = generalSplash.Concat(windowsSplash).ToArray();
else if (OS.IsLinux) else if (OS.IsLinux)
totalSplashes = generalSplash.Concat(linuxSplash).ToArray(); totalSplashes = generalSplash.Concat(linuxSplash).ToArray();
else if (OS.IsMac) else if (OS.IsMac)
totalSplashes = generalSplash.Concat(macSplash).ToArray(); totalSplashes = generalSplash.Concat(macSplash).ToArray();
else else
totalSplashes = generalSplash; totalSplashes = generalSplash;
return totalSplashes; return totalSplashes;
}
} }
} }

@ -2,153 +2,152 @@
using System.Linq; using System.Linq;
using System.Xml.Serialization; using System.Xml.Serialization;
namespace AM2RLauncher.Core.XML namespace AM2RLauncher.Core.XML;
/// <summary>
/// Class that handles how the Launcher settings are saved as XML. Only affects Linux
/// </summary>
[Serializable]
[XmlRoot("settings")]
public class LauncherConfigXML
{ {
/// <summary>Indicates wether or not to auto-update the Launcher. Used for <see cref="MainForm.autoUpdateAM2RCheck"/></summary>
[XmlAttribute("AutoUpdateAM2R")]
public bool AutoUpdateAM2R
{ get; set; }
/// <summary>Indicates wether or not to auto-update the Launcher. Used for <see cref="MainForm.autoUpdateLauncherCheck"/></summary>
[XmlAttribute("AutoUpdateLauncher")]
public bool AutoUpdateLauncher
{ get; set; }
/// <summary>Indicates the Language of the Launcher. Used for <see cref="MainForm.languageDropDown"/></summary>
[XmlAttribute("Language")]
public string Language
{ get; set; }
/// <summary>Indicates wether or not to use High-quality music when patching to PC. Used for <see cref="MainForm.hqMusicPCCheck"/></summary>
[XmlAttribute("MusicHQPC")]
public bool MusicHQPC
{ get; set; }
/// <summary>Indicates wether or not to use High-quality music when patching to Android. Used for <see cref="MainForm.hqMusicAndroidCheck"/></summary>
[XmlAttribute("MusicHQAndroid")]
public bool MusicHQAndroid
{ get; set; }
/// <summary>Indicates the index for <see cref="MainForm.mirrorDropDown"/>.</summary>
[XmlAttribute("MirrorIndex")]
public int MirrorIndex
{ get; set; }
/// <summary>Indicates the index for <see cref="MainForm.profileDropDown"/>.</summary>
[XmlAttribute("ProfileIndex")]
public string ProfileIndex
{ get; set; }
/// <summary>Indicates wether or not to have custom mirrors enabled. Used for <see cref="MainForm.customMirrorCheck"/></summary>
[XmlAttribute("CustomMirrorEnabled")]
public bool CustomMirrorEnabled
{ get; set; }
/// <summary>Indicates the custom mirror as a text. Used for <see cref="MainForm.customMirrorTextBox"/></summary>
[XmlAttribute("CustomMirrorText")]
public string CustomMirrorText
{ get; set; }
/// <summary>Indicates whether or not to create debug logs of profile. Used for <see cref="MainForm.profileDebugLogCheck"/></summary>
[XmlAttribute("ProfileDebugLog")]
public bool ProfileDebugLog
{ get; set; }
/// <summary>Indicates the custom environment variable(s) as text. Used for <see cref="MainForm.customEnvVarTextBox"/></summary>
[XmlAttribute("CustomEnvVar")]
public string CustomEnvVar
{ get; set; }
/// <summary>Indicates the Width of the Launcher.</summary>
[XmlAttribute("Width")]
public int Width
{ get; set; }
/// <summary>Indicates the Height of the Launcher.</summary>
[XmlAttribute("Height")]
public int Height
{ get; set; }
/// <summary>Indicates wether or not the Launcher is maximized or not.</summary>
[XmlAttribute("IsMaximized")]
public bool IsMaximized
{ get; set; }
// Huge help from James for all of this!!!
// Here's a short explanation of this. Basically, what we do is create an indexer. This makes it possible for this class to be indexed, like this LauncherConfigXML[i]
// The indexer here has a get and set "submethod". And both use Reflection to get the current class as something that can be used with lambda values
// So for get we just lambda the properties and search for something that can be read and has the same name as the input property. After that, we just get the value and return it as a string
// Set is basiaclly the same, but instead of returning it, we set the prtoperty value, which would be followed after the `='.
// So LauncherConfigXML[property] = hellWorld would set the value of `property` to `hellWorld`
/// <summary> /// <summary>
/// Class that handles how the Launcher settings are saved as XML. Only affects Linux /// An Indexer for <see cref="ProfileXML"/>. Not to be used directly, use <see cref="CrossPlatformOperations.WriteToConfig(string, object)"/>
/// or <see cref="CrossPlatformOperations.ReadFromConfig(string)"/> instead!
/// </summary> /// </summary>
[Serializable] /// <param name="property">The property to get or set.</param>
[XmlRoot("settings")] /// <returns>The value of <paramref name="property"/> as an <see cref="object"/> if used as a get, <see cref="Void"/> if used as a set.</returns>
public class LauncherConfigXML public object this[string property]
{ {
/// <summary>Indicates wether or not to auto-update the Launcher. Used for <see cref="MainForm.autoUpdateAM2RCheck"/></summary> get
[XmlAttribute("AutoUpdateAM2R")] {
public bool AutoUpdateAM2R // This is gonna throw an exception, if the property can't be found. because of null.GetValue(this)
{ get; set; } return typeof(LauncherConfigXML).GetProperties().First(p => p.CanRead && p.Name == property).GetValue(this).ToString();
/// <summary>Indicates wether or not to auto-update the Launcher. Used for <see cref="MainForm.autoUpdateLauncherCheck"/></summary> }
[XmlAttribute("AutoUpdateLauncher")] set
public bool AutoUpdateLauncher {
{ get; set; } typeof(LauncherConfigXML).GetProperties().First(p => p.CanWrite && p.Name == property).SetValue(this, value);
/// <summary>Indicates the Language of the Launcher. Used for <see cref="MainForm.languageDropDown"/></summary> }
[XmlAttribute("Language")] }
public string Language
{ get; set; }
/// <summary>Indicates wether or not to use High-quality music when patching to PC. Used for <see cref="MainForm.hqMusicPCCheck"/></summary>
[XmlAttribute("MusicHQPC")]
public bool MusicHQPC
{ get; set; }
/// <summary>Indicates wether or not to use High-quality music when patching to Android. Used for <see cref="MainForm.hqMusicAndroidCheck"/></summary>
[XmlAttribute("MusicHQAndroid")]
public bool MusicHQAndroid
{ get; set; }
/// <summary>Indicates the index for <see cref="MainForm.mirrorDropDown"/>.</summary>
[XmlAttribute("MirrorIndex")]
public int MirrorIndex
{ get; set; }
/// <summary>Indicates the index for <see cref="MainForm.profileDropDown"/>.</summary>
[XmlAttribute("ProfileIndex")]
public string ProfileIndex
{ get; set; }
/// <summary>Indicates wether or not to have custom mirrors enabled. Used for <see cref="MainForm.customMirrorCheck"/></summary>
[XmlAttribute("CustomMirrorEnabled")]
public bool CustomMirrorEnabled
{ get; set; }
/// <summary>Indicates the custom mirror as a text. Used for <see cref="MainForm.customMirrorTextBox"/></summary>
[XmlAttribute("CustomMirrorText")]
public string CustomMirrorText
{ get; set; }
/// <summary>Indicates whether or not to create debug logs of profile. Used for <see cref="MainForm.profileDebugLogCheck"/></summary>
[XmlAttribute("ProfileDebugLog")]
public bool ProfileDebugLog
{ get; set; }
/// <summary>Indicates the custom environment variable(s) as text. Used for <see cref="MainForm.customEnvVarTextBox"/></summary>
[XmlAttribute("CustomEnvVar")]
public string CustomEnvVar
{ get; set; }
/// <summary>Indicates the Width of the Launcher.</summary>
[XmlAttribute("Width")]
public int Width
{ get; set; }
/// <summary>Indicates the Height of the Launcher.</summary>
[XmlAttribute("Height")]
public int Height
{ get; set; }
/// <summary>Indicates wether or not the Launcher is maximized or not.</summary>
[XmlAttribute("IsMaximized")]
public bool IsMaximized
{ get; set; }
// Huge help from James for all of this!!!
// Here's a short explanation of this. Basically, what we do is create an indexer. This makes it possible for this class to be indexed, like this LauncherConfigXML[i]
// The indexer here has a get and set "submethod". And both use Reflection to get the current class as something that can be used with lambda values
// So for get we just lambda the properties and search for something that can be read and has the same name as the input property. After that, we just get the value and return it as a string
// Set is basiaclly the same, but instead of returning it, we set the prtoperty value, which would be followed after the `='.
// So LauncherConfigXML[property] = hellWorld would set the value of `property` to `hellWorld`
/// <summary>
/// An Indexer for <see cref="ProfileXML"/>. Not to be used directly, use <see cref="CrossPlatformOperations.WriteToConfig(string, object)"/>
/// or <see cref="CrossPlatformOperations.ReadFromConfig(string)"/> instead!
/// </summary>
/// <param name="property">The property to get or set.</param>
/// <returns>The value of <paramref name="property"/> as an <see cref="object"/> if used as a get, <see cref="Void"/> if used as a set.</returns>
public object this[string property]
{
get
{
// This is gonna throw an exception, if the property can't be found. because of null.GetValue(this)
return typeof(LauncherConfigXML).GetProperties().First(p => p.CanRead && p.Name == property).GetValue(this).ToString();
}
set
{
typeof(LauncherConfigXML).GetProperties().First(p => p.CanWrite && p.Name == property).SetValue(this, value);
}
}
/// <summary> /// <summary>
/// Creates a <see cref="LauncherConfigXML"/> with a default set of attributes. /// Creates a <see cref="LauncherConfigXML"/> with a default set of attributes.
/// </summary> /// </summary>
public LauncherConfigXML() public LauncherConfigXML()
{ {
AutoUpdateAM2R = true; AutoUpdateAM2R = true;
AutoUpdateLauncher = true; AutoUpdateLauncher = true;
Language = "Default"; Language = "Default";
MusicHQPC = true; MusicHQPC = true;
MusicHQAndroid = false; MusicHQAndroid = false;
ProfileIndex = "null"; ProfileIndex = "null";
MirrorIndex = 0; MirrorIndex = 0;
CustomMirrorEnabled = false; CustomMirrorEnabled = false;
CustomMirrorText = ""; CustomMirrorText = "";
ProfileDebugLog = true; ProfileDebugLog = true;
CustomEnvVar = ""; CustomEnvVar = "";
Width = 600; Width = 600;
Height = 600; Height = 600;
IsMaximized = false; IsMaximized = false;
} }
/// <summary> /// <summary>
/// Creates a <see cref="LauncherConfigXML"/> with custom attributes. /// Creates a <see cref="LauncherConfigXML"/> with custom attributes.
/// </summary> /// </summary>
/// <param name="autoUpdateAM2R">Paramater that indicates if <see cref="MainForm.autoUpdateAM2RCheck"/> is enabled or not.</param> /// <param name="autoUpdateAM2R">Paramater that indicates if <see cref="MainForm.autoUpdateAM2RCheck"/> is enabled or not.</param>
/// <param name="autoUpdateLauncher">Paramater that indicates if <see cref="MainForm.autoUpdateLauncherCheck"/> is enabled or not.</param> /// <param name="autoUpdateLauncher">Paramater that indicates if <see cref="MainForm.autoUpdateLauncherCheck"/> is enabled or not.</param>
/// <param name="language">Parameter that indicates the language of the launcher.</param> /// <param name="language">Parameter that indicates the language of the launcher.</param>
/// <param name="musicHQPC">Parameter that indicates if <see cref="MainForm.hqMusicPCCheck"/> is enabled or not.</param> /// <param name="musicHQPC">Parameter that indicates if <see cref="MainForm.hqMusicPCCheck"/> is enabled or not.</param>
/// <param name="musicHQAndroid">Parameter that indicates if <see cref="MainForm.hqMusicAndroidCheck"/> is enabled or not.</param> /// <param name="musicHQAndroid">Parameter that indicates if <see cref="MainForm.hqMusicAndroidCheck"/> is enabled or not.</param>
/// <param name="profileIndex">Parameter that saves the index of the selected profile of <see cref="MainForm.profileDropDown"/>.</param> /// <param name="profileIndex">Parameter that saves the index of the selected profile of <see cref="MainForm.profileDropDown"/>.</param>
/// <param name="mirrorIndex">Parameter that saves the index of the selected mirror in <see cref="MainForm.mirrorDropDown"/>.</param> /// <param name="mirrorIndex">Parameter that saves the index of the selected mirror in <see cref="MainForm.mirrorDropDown"/>.</param>
/// <param name="customEnvVar">Parameter that saves custom Environment variables that will be used on Linux for launching a game.</param> /// <param name="customEnvVar">Parameter that saves custom Environment variables that will be used on Linux for launching a game.</param>
/// <param name="customMirrorEnabled">Parameter that indicates if <see cref="MainForm.customMirrorCheck"/> is enabled or not.</param> /// <param name="customMirrorEnabled">Parameter that indicates if <see cref="MainForm.customMirrorCheck"/> is enabled or not.</param>
/// <param name="profileDebugLog">Parameter that indicates if <see cref="MainForm.profileDebugLogCheck"/> is enabled or not.</param> /// <param name="profileDebugLog">Parameter that indicates if <see cref="MainForm.profileDebugLogCheck"/> is enabled or not.</param>
/// <param name="customMirrorText">Parameter that's used for <see cref="MainForm.customMirrorTextBox"/>.</param> /// <param name="customMirrorText">Parameter that's used for <see cref="MainForm.customMirrorTextBox"/>.</param>
/// <param name="width">Parameter that indicates the width of <see cref="MainForm"/>.</param> /// <param name="width">Parameter that indicates the width of <see cref="MainForm"/>.</param>
/// <param name="height">Parameter that indicates the height of <see cref="MainForm"/>.</param> /// <param name="height">Parameter that indicates the height of <see cref="MainForm"/>.</param>
/// <param name="isMaximized">Parameter that indicates if <see cref="MainForm"/> has been set to fullscreen or not.</param> /// <param name="isMaximized">Parameter that indicates if <see cref="MainForm"/> has been set to fullscreen or not.</param>
public LauncherConfigXML(bool autoUpdateAM2R, bool autoUpdateLauncher, string language, bool musicHQPC, bool musicHQAndroid, public LauncherConfigXML(bool autoUpdateAM2R, bool autoUpdateLauncher, string language, bool musicHQPC, bool musicHQAndroid,
string profileIndex, int mirrorIndex, bool profileDebugLog, string customEnvVar, bool customMirrorEnabled, string profileIndex, int mirrorIndex, bool profileDebugLog, string customEnvVar, bool customMirrorEnabled,
string customMirrorText, int width, int height, bool isMaximized) string customMirrorText, int width, int height, bool isMaximized)
{ {
AutoUpdateAM2R = autoUpdateAM2R; AutoUpdateAM2R = autoUpdateAM2R;
AutoUpdateLauncher = autoUpdateLauncher; AutoUpdateLauncher = autoUpdateLauncher;
Language = language; Language = language;
MusicHQPC = musicHQPC; MusicHQPC = musicHQPC;
MusicHQAndroid = musicHQAndroid; MusicHQAndroid = musicHQAndroid;
ProfileIndex = profileIndex; ProfileIndex = profileIndex;
MirrorIndex = mirrorIndex; MirrorIndex = mirrorIndex;
CustomMirrorEnabled = customMirrorEnabled; CustomMirrorEnabled = customMirrorEnabled;
CustomMirrorText = customMirrorText; CustomMirrorText = customMirrorText;
ProfileDebugLog = profileDebugLog; ProfileDebugLog = profileDebugLog;
CustomEnvVar = customEnvVar; CustomEnvVar = customEnvVar;
Width = width; Width = width;
Height = height; Height = height;
IsMaximized = isMaximized; IsMaximized = isMaximized;
}
} }
} }

@ -1,99 +1,98 @@
using System; using System;
using System.Xml.Serialization; using System.Xml.Serialization;
namespace AM2RLauncher.Core.XML namespace AM2RLauncher.Core.XML;
/// <summary>
/// Class that handles how the mod settings are saved as XML.
/// </summary>
[Serializable]
[XmlRoot("message")]
public class ProfileXML
{ {
/// <summary>Indicates the Operating system the mod was made for.</summary>
[XmlAttribute("OperatingSystem")]
public string OperatingSystem
{ get; set; }
/// <summary>Indicates the xml version the mod was made in.</summary>
[XmlAttribute("XMLVersion")]
public int XMLVersion
{ get; set; }
/// <summary>Indicates the version of the mod.</summary>
[XmlAttribute("Version")]
public string Version
{ get; set; }
/// <summary>Indicates the mod's name.</summary>
[XmlAttribute("Name")]
public string Name
{ get; set; }
/// <summary>Indicates the mod's author.</summary>
[XmlAttribute("Author")]
public string Author
{ get; set; }
/// <summary>Indicates wether or not the mod uses custom music.</summary>
[XmlAttribute("UsesCustomMusic")]
public bool UsesCustomMusic
{ get; set; }
/// <summary>Indicates the save location of the mod.</summary>
[XmlAttribute("SaveLocation")]
public string SaveLocation
{ get; set; }
/// <summary>Indicates wether or not the mod supports Android.</summary>
[XmlAttribute("SupportsAndroid")]
public bool SupportsAndroid
{ get; set; }
/// <summary>Indicates wether or not the mod was compiled with YYC.</summary>
[XmlAttribute("UsesYYC")]
public bool UsesYYC
{ get; set; }
/// <summary>Indicates if the mod is installable. This is only <see langword="false"/> for archival community updates mods.</summary>
[XmlAttribute("Installable")]
public bool Installable
{ get; set; }
/// <summary>Indicates any notes that the mod author deemed worthy to share about his mod.</summary>
[XmlAttribute("ProfileNotes")]
public string ProfileNotes
{ get; set; }
/// <summary>This gets calculated at runtime, by <see cref="MainForm.LoadProfiles"/>. Indicates where the install data for the mod is stored.</summary>
[XmlIgnore]
public string DataPath
{ get; set; }
/// <summary>Creates a <see cref="ProfileXML"/> with a default set of attributes.</summary>
public ProfileXML()
{
Installable = true;
}
/// <summary> /// <summary>
/// Class that handles how the mod settings are saved as XML. /// Creates a <see cref="ProfileXML"/> with a custom set of attributes.
/// </summary> /// </summary>
[Serializable] /// <param name="operatingSystem">The operating system the mod was made on.</param>
[XmlRoot("message")] /// <param name="xmlVersion">The xml version the mod was created with.</param>
public class ProfileXML /// <param name="version">The version of the mod.</param>
/// <param name="name">The mod name.</param>
/// <param name="author">The mod author.</param>
/// <param name="usesCustomMusic">Wether or not the mod uses custom music.</param>
/// <param name="saveLocation">The save location of the mod.</param>
/// <param name="android">Wether or not the mod works for android.</param>
/// <param name="usesYYC">Wether or not the mod was made with YYC.</param>
/// <param name="installable">Wether or not the mod is installable.</param>
/// <param name="profileNotes">The notes of the mod.</param>
public ProfileXML(string operatingSystem, int xmlVersion, string version, string name, string author,
bool usesCustomMusic, string saveLocation, bool android, bool usesYYC,
bool installable, string profileNotes)
{ {
/// <summary>Indicates the Operating system the mod was made for.</summary> OperatingSystem = operatingSystem;
[XmlAttribute("OperatingSystem")] XMLVersion = xmlVersion;
public string OperatingSystem Version = version;
{ get; set; } Name = name;
/// <summary>Indicates the xml version the mod was made in.</summary> Author = author;
[XmlAttribute("XMLVersion")] UsesCustomMusic = usesCustomMusic;
public int XMLVersion SaveLocation = saveLocation;
{ get; set; } SupportsAndroid = android;
/// <summary>Indicates the version of the mod.</summary> UsesYYC = usesYYC;
[XmlAttribute("Version")] Installable = installable;
public string Version ProfileNotes = profileNotes;
{ get; set; }
/// <summary>Indicates the mod's name.</summary>
[XmlAttribute("Name")]
public string Name
{ get; set; }
/// <summary>Indicates the mod's author.</summary>
[XmlAttribute("Author")]
public string Author
{ get; set; }
/// <summary>Indicates wether or not the mod uses custom music.</summary>
[XmlAttribute("UsesCustomMusic")]
public bool UsesCustomMusic
{ get; set; }
/// <summary>Indicates the save location of the mod.</summary>
[XmlAttribute("SaveLocation")]
public string SaveLocation
{ get; set; }
/// <summary>Indicates wether or not the mod supports Android.</summary>
[XmlAttribute("SupportsAndroid")]
public bool SupportsAndroid
{ get; set; }
/// <summary>Indicates wether or not the mod was compiled with YYC.</summary>
[XmlAttribute("UsesYYC")]
public bool UsesYYC
{ get; set; }
/// <summary>Indicates if the mod is installable. This is only <see langword="false"/> for archival community updates mods.</summary>
[XmlAttribute("Installable")]
public bool Installable
{ get; set; }
/// <summary>Indicates any notes that the mod author deemed worthy to share about his mod.</summary>
[XmlAttribute("ProfileNotes")]
public string ProfileNotes
{ get; set; }
/// <summary>This gets calculated at runtime, by <see cref="MainForm.LoadProfiles"/>. Indicates where the install data for the mod is stored.</summary>
[XmlIgnore]
public string DataPath
{ get; set; }
/// <summary>Creates a <see cref="ProfileXML"/> with a default set of attributes.</summary>
public ProfileXML()
{
Installable = true;
}
/// <summary>
/// Creates a <see cref="ProfileXML"/> with a custom set of attributes.
/// </summary>
/// <param name="operatingSystem">The operating system the mod was made on.</param>
/// <param name="xmlVersion">The xml version the mod was created with.</param>
/// <param name="version">The version of the mod.</param>
/// <param name="name">The mod name.</param>
/// <param name="author">The mod author.</param>
/// <param name="usesCustomMusic">Wether or not the mod uses custom music.</param>
/// <param name="saveLocation">The save location of the mod.</param>
/// <param name="android">Wether or not the mod works for android.</param>
/// <param name="usesYYC">Wether or not the mod was made with YYC.</param>
/// <param name="installable">Wether or not the mod is installable.</param>
/// <param name="profileNotes">The notes of the mod.</param>
public ProfileXML(string operatingSystem, int xmlVersion, string version, string name, string author,
bool usesCustomMusic, string saveLocation, bool android, bool usesYYC,
bool installable, string profileNotes)
{
OperatingSystem = operatingSystem;
XMLVersion = xmlVersion;
Version = version;
Name = name;
Author = author;
UsesCustomMusic = usesCustomMusic;
SaveLocation = saveLocation;
SupportsAndroid = android;
UsesYYC = usesYYC;
Installable = installable;
ProfileNotes = profileNotes;
}
} }
} }

@ -2,51 +2,48 @@
using System.IO; using System.IO;
using System.Text; using System.Text;
namespace AM2RLauncher.Core.XML namespace AM2RLauncher.Core.XML;
/// <summary>
/// The <c>Serializer</c> class, that serializes to and deserializes from XML files.
/// </summary>
public static class Serializer
{ {
/// <summary> /// <summary>
/// The <c>Serializer</c> class, that serializes to and deserializes from XML files. /// Serializes <paramref name="item"/> as a <typeparamref name="T"/> to XML.
/// </summary> /// </summary>
public static class Serializer /// <typeparam name="T">The class to serialize to.</typeparam>
/// <param name="item">The object that will be serialized.</param>
/// <returns>The serialized XML as a <see cref="string"/>.</returns>
public static string Serialize<T>(object item)
{ {
/// <summary> Type t = typeof(T);
/// Serializes <paramref name="item"/> as a <typeparamref name="T"/> to XML. MemoryStream memStream = new MemoryStream();
/// </summary> System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(t);
/// <typeparam name="T">The class to serialize to.</typeparam>
/// <param name="item">The object that will be serialized.</param>
/// <returns>The serialized XML as a <see cref="string"/>.</returns>
public static string Serialize<T>(object item)
{
Type t = typeof(T);
MemoryStream memStream = new MemoryStream();
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(t);
serializer.Serialize(memStream, item); serializer.Serialize(memStream, item);
string xml = Encoding.UTF8.GetString(memStream.ToArray()); string xml = Encoding.UTF8.GetString(memStream.ToArray());
memStream.Flush(); memStream.Flush();
memStream.Close(); memStream.Close();
memStream.Dispose(); memStream.Dispose();
memStream = null; memStream = null;
return xml; return xml;
} }
/// <summary> /// <summary>
/// Deserialize <paramref name="xmlString"/> into an object of class <typeparamref name="T"/> that can be assigned. /// Deserialize <paramref name="xmlString"/> into an object of class <typeparamref name="T"/> that can be assigned.
/// </summary> /// </summary>
/// <typeparam name="T">The class that <paramref name="xmlString"/> will be deserialized to.</typeparam> /// <typeparam name="T">The class that <paramref name="xmlString"/> will be deserialized to.</typeparam>
/// <param name="xmlString">An XML <see cref="string"/> that will be deserialized.</param> /// <param name="xmlString">An XML <see cref="string"/> that will be deserialized.</param>
/// <returns>A deserialized object of class <typeparamref name="T"/> from <paramref name="xmlString"/>.</returns> /// <returns>A deserialized object of class <typeparamref name="T"/> from <paramref name="xmlString"/>.</returns>
public static T Deserialize<T>(string xmlString) public static T Deserialize<T>(string xmlString)
{ {
Type t = typeof(T); Type t = typeof(T);
using (MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlString))) using MemoryStream memStream = new MemoryStream(Encoding.UTF8.GetBytes(xmlString));
{ System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(t);
System.Xml.Serialization.XmlSerializer serializer = new System.Xml.Serialization.XmlSerializer(t); return (T)serializer.Deserialize(memStream);
return (T)serializer.Deserialize(memStream);
}
}
} }
} }
Loading…
Cancel
Save