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`.
pull/30/head
Miepee 5 years ago
parent 6cf7cbf2d5
commit a14589baaf

@ -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
/// </summary>
class CrossPlatformOperations
{
/// <summary>
/// The logger for <see cref="MainForm"/>, used to write any caught exceptions.
/// </summary>
private static readonly ILog log = LogManager.GetLogger(typeof(MainForm));
/// <summary>
/// Gets the current platform.
/// </summary>
readonly private static Platform currentPlatform = Platform.Instance;
/// <summary>
/// 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 <see cref="GenerateCurrentPath"/>.
/// </summary>
static readonly public string CURRENTPATH = currentPlatform.IsWinForms ? Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) : Environment.GetEnvironmentVariable("HOME") + "/.local/share/AM2RLauncher";
static readonly public string CURRENTPATH = GenerateCurrentPath();
/// <summary>
/// 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
}
/// <summary>
/// Opens <paramref name="path"/> and selects it in a file explorer.
/// Opens <paramref name="path"/> 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.
/// </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...
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);
}
}
/// <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.");
}
}
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);
}
}
}

@ -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

@ -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;

@ -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));

@ -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.

Loading…
Cancel
Save