From a14589baaf344233e537f7c43a31b837433861e6 Mon Sep 17 00:00:00 2001
From: Miepee <38186597+Miepee@users.noreply.github.com>
Date: Tue, 28 Dec 2021 12:33:30 +0100
Subject: [PATCH] introduce am2rlauncherdata env var, use xdg env vars, minor
cleanup
- Config file on linux is not hardcoded to `~/.config/AM2RLauncher` anymore, and will now respect `$XDG_CONFIG_HOME`
- Data folder on linux is not hardcoded to `~/.local/share/AM2RLauncher` anymore. Instead it will now listen for `$AM2RLAUNCHERDATA` (on win too!) and respect `$XDG_DATA_HOME`
Profile Logging has been kept as is, until I figure out if AM2R on linux respects `$XDG_CONFIG_HOME`.
---
.../AM2RLauncher/CrossPlatformOperations.cs | 122 ++++++++++++++++--
AM2RLauncher/AM2RLauncher/LauncherUpdater.cs | 1 -
.../AM2RLauncher/MainForm/MainForm.Events.cs | 26 ++--
.../AM2RLauncher/MainForm/MainForm.Methods.cs | 15 ++-
README.md | 13 +-
5 files changed, 144 insertions(+), 33 deletions(-)
diff --git a/AM2RLauncher/AM2RLauncher/CrossPlatformOperations.cs b/AM2RLauncher/AM2RLauncher/CrossPlatformOperations.cs
index f8a8360..fbd3b52 100644
--- a/AM2RLauncher/AM2RLauncher/CrossPlatformOperations.cs
+++ b/AM2RLauncher/AM2RLauncher/CrossPlatformOperations.cs
@@ -8,6 +8,7 @@ using System.IO;
using System.Text;
using System.Reflection;
using System.Text.RegularExpressions;
+using log4net;
namespace AM2RLauncher
{
@@ -16,15 +17,20 @@ namespace AM2RLauncher
///
class CrossPlatformOperations
{
+ ///
+ /// The logger for , used to write any caught exceptions.
+ ///
+ private static readonly ILog log = LogManager.GetLogger(typeof(MainForm));
+
///
/// Gets the current platform.
///
readonly private static Platform currentPlatform = Platform.Instance;
///
- /// Current Path where the Launcher is located. For Linux this is redirected to ~/.local/share/AM2RLauncher, in order to be more compliant to the XDG Base Directory Specification.
+ /// Current Path where the Launcher is located. For more info, check .
///
- static readonly public string CURRENTPATH = currentPlatform.IsWinForms ? Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) : Environment.GetEnvironmentVariable("HOME") + "/.local/share/AM2RLauncher";
+ static readonly public string CURRENTPATH = GenerateCurrentPath();
///
/// Name of the Launcher executable.
@@ -75,10 +81,11 @@ namespace AM2RLauncher
}
if (currentPlatform.IsGtk)
{
- //config for nix systems will be saved in .config/AM2RLauncher
+ //config for nix systems will be saved in XDG_CONFIG_HOME/AM2RLauncher (or if empty, ~/.config)
string homePath = Environment.GetEnvironmentVariable("HOME");
- string launcherConfigPath = homePath + "/.config/AM2RLauncher/";
- string launcherConfigFilePath = launcherConfigPath + "config.xml";
+ string xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
+ string launcherConfigPath = (String.IsNullOrWhiteSpace(xdgConfigHome) ? (homePath + "/.config") : xdgConfigHome) + "/AM2RLauncher";
+ string launcherConfigFilePath = launcherConfigPath + "/config.xml";
XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML();
//if folder doesn't exist, create it and the config file
@@ -122,10 +129,11 @@ namespace AM2RLauncher
}
else if (currentPlatform.IsGtk)
{
- //config for nix systems will be saved in .config/AM2RLauncher
+ //config for nix systems will be saved in XDG_CONFIG_HOME/AM2RLauncher (or if empty, ~/.config)
string homePath = Environment.GetEnvironmentVariable("HOME");
- string launcherConfigPath = homePath + "/.config/AM2RLauncher/";
- string launcherConfigFilePath = launcherConfigPath + "config.xml";
+ string xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
+ string launcherConfigPath = (String.IsNullOrWhiteSpace(xdgConfigHome) ? (homePath + "/.config") : xdgConfigHome) + "/AM2RLauncher";
+ string launcherConfigFilePath = launcherConfigPath + "/config.xml";
XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML();
//if folder doesn't exist, create it and the config file
@@ -170,9 +178,11 @@ namespace AM2RLauncher
}
else if(currentPlatform.IsGtk)
{
+ //config for nix systems will be saved in XDG_CONFIG_HOME/AM2RLauncher (or if empty, ~/.config)
string homePath = Environment.GetEnvironmentVariable("HOME");
- string launcherConfigPath = homePath + "/.config/AM2RLauncher/";
- string launcherConfigFilePath = launcherConfigPath + "config.xml";
+ string xdgConfigHome = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
+ string launcherConfigPath = (String.IsNullOrWhiteSpace(xdgConfigHome) ? (homePath + "/.config") : xdgConfigHome) + "/AM2RLauncher";
+ string launcherConfigFilePath = launcherConfigPath + "/config.xml";
XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML();
//for some reason deserializing and saving back again works, not exactly sure why, but I'll take it
@@ -200,9 +210,14 @@ namespace AM2RLauncher
public static void OpenFolder(string path)
{
// We have to replace forward slashes with backslashes here on windows because explorer.exe is picky...
- string realPath = currentPlatform.IsWinForms ? Environment.ExpandEnvironmentVariables(path).Replace("/", "\\") : path.Replace("~", Environment.GetEnvironmentVariable("HOME"));
+ // And on Nix systems, we want to replace ~ with its corresponding env var
+ string realPath = currentPlatform.IsWinForms ? Environment.ExpandEnvironmentVariables(path).Replace("/", "\\")
+ : path.Replace("~", Environment.GetEnvironmentVariable("HOME"));
if (!Directory.Exists(realPath))
+ {
+ log.Info(realPath + " did not exist and was created");
Directory.CreateDirectory(realPath);
+ }
//needs quotes otherwise paths with space wont open
if (currentPlatform.IsWinForms)
@@ -214,15 +229,21 @@ namespace AM2RLauncher
}
///
- /// Opens and selects it in a file explorer.
+ /// Opens and selects it in a file explorer.
+ /// Only selects on Windows, on Linux it just opens the folder. Does nothing if file doesn't exist.
///
/// Path to open.
public static void OpenFolderAndSelectFile(string path)
{
// We have to replace forward slashes with backslashes here on windows because explorer.exe is picky...
- string realPath = currentPlatform.IsWinForms ? Environment.ExpandEnvironmentVariables(path).Replace("/", "\\") : path.Replace("~", Environment.GetEnvironmentVariable("HOME"));
+ // And on nix systems, we want to replace ~ with its corresponding env var
+ string realPath = currentPlatform.IsWinForms ? Environment.ExpandEnvironmentVariables(path).Replace("/", "\\")
+ : path.Replace("~", Environment.GetEnvironmentVariable("HOME"));
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 (currentPlatform.IsWinForms)
@@ -326,7 +347,8 @@ namespace AM2RLauncher
if (original == output)
output = output += "_";
- string arguments = "-f -d -s \"" + original.Replace(CURRENTPATH + "/","") + "\" \"" + patch.Replace(CURRENTPATH + "/", "") + "\" \"" + output.Replace(CURRENTPATH + "/", "") + "\"";
+ string arguments = "-f -d -s \"" + original.Replace(CURRENTPATH + "/","") + "\" \"" + patch.Replace(CURRENTPATH + "/", "")
+ + "\" \"" + output.Replace(CURRENTPATH + "/", "") + "\"";
if (currentPlatform.IsWinForms)
{
@@ -368,5 +390,77 @@ namespace AM2RLauncher
File.Move(output, originalOutput);
}
}
+
+ ///
+ /// Figures out what the AM2RLauncher's should be.
+ /// Determination is as follows:
+ ///
+ /// - AM2RLAUNCHERDATA environment variable is read and folders are recursively generated.
+ /// - The current OS is checked. For Windows, the path where the executable is located will be returned.
+ /// For Linux, $XDG_DATA_HOME/AM2RLauncher will be returned.
+ /// Should $XDG_DATA_HOME be empty, it will default to $HOME/.local/share.
+ /// - The path where the executable is located will be returned.
+ ///
+ /// Should any errors occur, it falls down to the next step.
+ ///
+ ///
+ 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.");
+ }
+ }
+
+ if (currentPlatform.IsWinForms)
+ {
+ log.Info("Using default Windows CurrentPath.");
+ // Windows has the path where the exe is located as default
+ return Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
+ }
+ else if (currentPlatform.IsGtk)
+ {
+ // First check if XDG_DATA_HOME is set, if not we'll use ~/.local/share
+ string xdgDataHome = Environment.GetEnvironmentVariable("XDG_DATA_HOME");
+ if (string.IsNullOrWhiteSpace(xdgDataHome))
+ {
+ log.Info("Using default Linux CurrentPath.");
+ xdgDataHome = Environment.GetEnvironmentVariable("HOME") + "/.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.");
+ }
+ }
+
+ log.Info("Something went wrong, falling back to the default CurrentPath.");
+ return Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
+ }
}
}
diff --git a/AM2RLauncher/AM2RLauncher/LauncherUpdater.cs b/AM2RLauncher/AM2RLauncher/LauncherUpdater.cs
index f3b789e..a16e86a 100644
--- a/AM2RLauncher/AM2RLauncher/LauncherUpdater.cs
+++ b/AM2RLauncher/AM2RLauncher/LauncherUpdater.cs
@@ -180,7 +180,6 @@ namespace AM2RLauncher
// for windows, the actual application is in "AM2RLauncher.dll". Which means, we need to update the lib folder as well.
if (currentPlatform.IsWinForms && Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/lib"))
{
- // Directory.Move(CrossPlatformOperations.CURRENTPATH + "/lib", CrossPlatformOperations.CURRENTPATH + "/oldLib");
// So, because Windows behavior is dumb...
// Rename all files in lib to *.bak
diff --git a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Events.cs b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Events.cs
index bd5de57..d0179e3 100644
--- a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Events.cs
+++ b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Events.cs
@@ -577,12 +577,13 @@ namespace AM2RLauncher
{
string currentOS = "";
if (Platform.IsWinForms) currentOS = "Windows";
- else if (Platform.IsGtk) currentOS = "Linux"; ///teeeeechnically, windows users and macos users could run GTK applications as well, so this would prolly need clarification.
+ else if (Platform.IsGtk) currentOS = "Linux"; ///teeeeechnically, any OS could run GTK applications as well but it'd break a lot and is thus unsupported.
log.Error("Mod is for " + profile.OperatingSystem + " while current OS is " + Platform + ". Cancelling mod import.");
- MessageBox.Show(Language.Text.ModIsForWrongOS.Replace("$NAME",profile.Name).Replace("$OS",profile.OperatingSystem).Replace("$CURRENTOS",currentOS), Language.Text.ErrorWindowTitle, MessageBoxType.Error);
+ MessageBox.Show(Language.Text.ModIsForWrongOS.Replace("$NAME",profile.Name).Replace("$OS",profile.OperatingSystem).Replace("$CURRENTOS",currentOS),
+ Language.Text.ErrorWindowTitle, MessageBoxType.Error);
HelperMethods.DeleteDirectory(modsDir + "/" + extractedName);
return;
}
@@ -621,7 +622,8 @@ namespace AM2RLauncher
{
if (IsProfileIndexValid())
{
- log.Info("User opened the profile directory for profile " + profileList[settingsProfileDropDown.SelectedIndex].Name + ", which is " + profileList[settingsProfileDropDown.SelectedIndex].SaveLocation);
+ log.Info("User opened the profile directory for profile " + profileList[settingsProfileDropDown.SelectedIndex].Name +
+ ", which is " + profileList[settingsProfileDropDown.SelectedIndex].SaveLocation);
CrossPlatformOperations.OpenFolder(CrossPlatformOperations.CURRENTPATH + "/Profiles/" + profileList[settingsProfileDropDown.SelectedIndex].Name);
}
}
@@ -758,7 +760,8 @@ namespace AM2RLauncher
profileVersionLabel.Text = Language.Text.VersionLabel + " " + profileList[profileDropDown.SelectedIndex].Version;
CrossPlatformOperations.WriteToConfig("ProfileIndex", profileIndex + ""); //Loj, tell me a better way to do this
- if (profileDropDown.SelectedIndex != 0 && (profileList[profileDropDown.SelectedIndex].SaveLocation == "%localappdata%/AM2R" || profileList[profileDropDown.SelectedIndex].SaveLocation == "default"))
+ if (profileDropDown.SelectedIndex != 0 && (profileList[profileDropDown.SelectedIndex].SaveLocation == "%localappdata%/AM2R" ||
+ profileList[profileDropDown.SelectedIndex].SaveLocation == "default"))
saveWarningLabel.Visible = true;
else
saveWarningLabel.Visible = false;
@@ -798,8 +801,9 @@ namespace AM2RLauncher
bool enabled = (bool)customMirrorCheck.Checked;
customMirrorTextBox.Enabled = enabled;
mirrorDropDown.Enabled = !enabled;
+ // Not sure why the dropdown menu needs this hack, but the textBox does not.
if (Platform.IsWinForms)
- mirrorDropDown.TextColor = mirrorDropDown.Enabled ? colGreen : colInactive; // Not sure why the dropdown menu needs this hack, but the textBox does not.
+ mirrorDropDown.TextColor = mirrorDropDown.Enabled ? colGreen : colInactive;
mirrorLabel.TextColor = !enabled ? colGreen : colInactive;
// Create warning dialog when enabling
@@ -901,7 +905,8 @@ namespace AM2RLauncher
ProfileXML profile = profileList[settingsProfileDropDown.SelectedIndex];
log.Info("User is attempting to delete profile " + profile.Name + ".");
- DialogResult result = MessageBox.Show(Language.Text.DeleteModWarning.Replace("$NAME", profile.Name), Language.Text.WarningWindowTitle, MessageBoxButtons.OKCancel, MessageBoxType.Warning, MessageBoxDefaultButton.Cancel);
+ DialogResult result = MessageBox.Show(Language.Text.DeleteModWarning.Replace("$NAME", profile.Name), Language.Text.WarningWindowTitle,
+ MessageBoxButtons.OKCancel, MessageBoxType.Warning, MessageBoxDefaultButton.Cancel);
if (result == DialogResult.Ok)
{
@@ -980,7 +985,8 @@ namespace AM2RLauncher
if (profileList.Where(p => p.Name == profile.Name).FirstOrDefault() != null || Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/Profiles/" + profile.Name))
{
// Mod is already installed, so we can update!
- DialogResult result = MessageBox.Show(Language.Text.UpdateModWarning.Replace("$NAME", currentProfile.Name), Language.Text.WarningWindowTitle, MessageBoxButtons.OKCancel, MessageBoxType.Warning, MessageBoxDefaultButton.Cancel);
+ DialogResult result = MessageBox.Show(Language.Text.UpdateModWarning.Replace("$NAME", currentProfile.Name), Language.Text.WarningWindowTitle,
+ MessageBoxButtons.OKCancel, MessageBoxType.Warning, MessageBoxDefaultButton.Cancel);
if (result == DialogResult.Ok)
{
@@ -1003,7 +1009,8 @@ namespace AM2RLauncher
// Cancel the operation!
// Show message to tell user that mod could not be found, install this separately
log.Error("Mod is not installed! Cancelling mod update.");
- MessageBox.Show(Language.Text.UpdateModButtonWrongMod.Replace("$NAME", currentProfile.Name).Replace("$SELECT", profile.Name), Language.Text.WarningWindowTitle, MessageBoxButtons.OK);
+ MessageBox.Show(Language.Text.UpdateModButtonWrongMod.Replace("$NAME", currentProfile.Name).Replace("$SELECT", profile.Name),
+ Language.Text.WarningWindowTitle, MessageBoxButtons.OK);
abort = true;
}
@@ -1042,7 +1049,8 @@ namespace AM2RLauncher
if (updateState == UpdateState.Downloading)
{
- var result = MessageBox.Show(Language.Text.CloseOnCloningText, Language.Text.WarningWindowTitle, MessageBoxButtons.YesNo, MessageBoxType.Warning, MessageBoxDefaultButton.No);
+ var result = MessageBox.Show(Language.Text.CloseOnCloningText, Language.Text.WarningWindowTitle, MessageBoxButtons.YesNo,
+ MessageBoxType.Warning, MessageBoxDefaultButton.No);
if (result == DialogResult.No)
{
e.Cancel = true;
diff --git a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Methods.cs b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Methods.cs
index eee581a..3ce346f 100644
--- a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Methods.cs
+++ b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Methods.cs
@@ -58,7 +58,7 @@ namespace AM2RLauncher
private bool IsProfileInstalled(ProfileXML profile)
{
if (Platform.IsWinForms) return File.Exists(CrossPlatformOperations.CURRENTPATH + "/Profiles/" + profile.Name + "/AM2R.exe");
- if (Platform.IsGtk) return File.Exists(CrossPlatformOperations.CURRENTPATH + "/Profiles/" + profile.Name + "/AM2R.AppImage"); //should we check for .AppRun as well?
+ if (Platform.IsGtk) return File.Exists(CrossPlatformOperations.CURRENTPATH + "/Profiles/" + profile.Name + "/AM2R.AppImage");
return false;
}
@@ -304,18 +304,18 @@ namespace AM2RLauncher
string datawin = null, exe = null;
- if(Platform.IsWinForms)
+ if (Platform.IsWinForms)
{
datawin = "data.win";
exe = "AM2R.exe";
}
- else if(Platform.IsGtk)
+ else if (Platform.IsGtk)
{
datawin = "game.unx";
// use the exe name based on the desktop file in the appimage, rather than hardcoding it.
string desktopContents = File.ReadAllText(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/AM2R.AppDir/AM2R.desktop");
exe = Regex.Match(desktopContents, @"(?<=Exec=).*").Value;
- log.Info("According to AppImage desktop file, use \"" + exe + "\" as game name.");
+ log.Info("According to AppImage desktop file, using \"" + exe + "\" as game name.");
}
log.Info("Attempting to patch in " + profilePath);
@@ -353,7 +353,7 @@ namespace AM2RLauncher
// Applied patch
if (Platform.IsWinForms) UpdateProgressBar(66);
- else if (Platform.IsGtk) UpdateProgressBar(44);//linux will take a bit longer, due to appimage creation
+ else if (Platform.IsGtk) UpdateProgressBar(44); //linux will take a bit longer, due to appimage creation
log.Info("xdelta patch(es) applied.");
// Install new datafiles
@@ -418,7 +418,7 @@ namespace AM2RLauncher
log.Info("Successfully installed profile " + profile.Name + ".");
// This is just for visuals because the average windows end user will ask why it doesn't go to the end otherwise.
- if(Platform.IsWinForms)
+ if (Platform.IsWinForms)
Thread.Sleep(1000);
}
@@ -644,7 +644,8 @@ namespace AM2RLauncher
ProfileXML profile = profileList[profileIndex.Value];
// These are used on both windows and linux for game logging
- string savePath = Platform.IsWinForms ? profile.SaveLocation.Replace("%localappdata%", Environment.GetEnvironmentVariable("LOCALAPPDATA")) : profile.SaveLocation.Replace("~", Environment.GetEnvironmentVariable("HOME"));
+ string savePath = Platform.IsWinForms ? profile.SaveLocation.Replace("%localappdata%", Environment.GetEnvironmentVariable("LOCALAPPDATA"))
+ : profile.SaveLocation.Replace("~", Environment.GetEnvironmentVariable("HOME"));
DirectoryInfo logDir = new DirectoryInfo(savePath + "/logs");
string date = string.Join("-", DateTime.Now.ToString().Split(Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries));
diff --git a/README.md b/README.md
index 9c5f621..515cc9b 100644
--- a/README.md
+++ b/README.md
@@ -16,12 +16,15 @@ Linux needs the following dependencies installed:
- `openssl`
- `fuse2`
+As well as these dependencies to run AM2R:
+- libopenal1:i386
+- libpulse0:i386
+
Optionally, for APK creation any Java runtime is needed.
### Arch Linux
On Arch Linux, you can install these by running this:
-(Multilib repositories are required, instructions on how to enable them can be found [here](https://wiki.archlinux.org/title/Official_repositories#Enabling_multilib))
-
+(Multilib repositories are required, instructions on how to enable them can be found [here](https://wiki.archlinux.org/title/Official_repositories#Enabling_multilib))
`sudo pacman -S --needed dotnet-runtime fuse2 gtk3 libappindicator-gtk3 openssl webkit2gtk xdelta3 lib32-openal lib32-libpulse jre-openjdk`
For other distros, refer to your local package manager for instructions.
@@ -31,6 +34,12 @@ Downloads can be found at the [Release Page](https://github.com/AM2R-Community-D
Alternatively, for Arch Linux users an [AUR Package](https://aur.archlinux.org/packages/am2rlauncher/) also exist. Install it with `makepkg -si` or use your favourite AUR helper.
+## Configuration and Data Files
+The AM2RLauncher stores its files in the following places:
+ - On Windows, it stores the config file to the `AM2RLauncher.exe.config` next to the binary, and its data files in the same folder as the binary.
+ - On Linux, it stores the config file to `$XDG_CONFIG_HOME/AM2RLauncher` and its data files to `$XDG_DATA_HOME/AM2RLauncher` (which are defaulting back to `~/.config` and `~/.local/share` respectively).
+The AM2RLauncher data can get quite big, so if you wish to change where it stores it, you can do so with the `AM2RLAUNCHERDATA` environment variable (i.e `AM2RLAUNCHERDATA="D:\MyLauncherData"` or `AM2RLAUNCHERDATA="/mnt/bigDrive/launcherData"`).
+
# Compiling Instructions:
## Dependencies
For compiling for Windows .Net Framework 4.8 SDK is needed. For Linux .Net Core 5.0 SDK is needed.