Initial mac support

pull/30/head
Miepee 5 years ago
parent 8b3623ae07
commit 0b11bc6536

3
.gitignore vendored

@ -352,3 +352,6 @@ MigrationBackup/
# Visual Studio Code folder
.vscode/
# Mac's DS_Stores
.DS_Store

@ -56,7 +56,10 @@ namespace AM2RLauncher.Gtk
private static void GTKLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e)
{
log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.ExceptionObject.ToString());
MessageBox.Show(Language.Text.UnhandledException + "\n*****Stack Trace*****\n\n" + e.ExceptionObject.ToString(), "GTK", MessageBoxType.Error);
Application.Instance.Invoke(new Action(() =>
{
MessageBox.Show(Language.Text.UnhandledException + "\n*****Stack Trace*****\n\n" + e.ExceptionObject.ToString(), "GTK", MessageBoxType.Error);
}));
}
// This is a duplicate of CrossPlatformOperations.GenerateCurrentPath, because trying to invoke that would cause a crash due to currentPlatform not being initialized.

@ -0,0 +1,43 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RuntimeIdentifiers>osx-x64</RuntimeIdentifiers>
<RollForward>LatestMajor</RollForward>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AM2RLauncher\AM2RLauncher.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Eto.Platform.Mac64" Version="2.6.0" />
</ItemGroup>
<ItemGroup>
<Compile Update="Properties\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<None Remove="Info.plist" />
<None Remove="Icon.icns" />
</ItemGroup>
<ItemGroup>
<BundleResource Include="Icon.icns" />
<BundleResource Include="Info.plist" />
</ItemGroup>
</Project>

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleName</key>
<string>AM2RLauncher</string>
<key>CFBundleIdentifier</key>
<string>com.example.AM2RLauncher</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>LSMinimumSystemVersion</key>
<string>10.12</string>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>NSHumanReadableCopyright</key>
<string></string>
<key>CFBundleIconFile</key>
<string>Icon.icns</string>
</dict>
</plist>

@ -0,0 +1,83 @@
using Eto.Forms;
using log4net;
using log4net.Config;
using System;
using System.IO;
using System.Reflection;
namespace AM2RLauncher.Mac
{
/// <summary>
/// The main class for the Mac project.
/// </summary>
class MainClass
{
/// <summary>
/// The logger for <see cref="MainForm"/>, used to write any caught exceptions.
/// </summary>
private static readonly ILog log = LogManager.GetLogger(typeof(MainForm));
/// <summary>
/// The main method for the Mac project.
/// </summary>
[STAThread]
public static void Main(string[] args)
{
string launcherDataPath = GenerateCurrentPath();
// Make sure first, ~/.local/share/AM2RLauncher exists
if (!Directory.Exists(launcherDataPath))
Directory.CreateDirectory(launcherDataPath);
// Now, see if log4netConfig exists, if not write it again.
if (!File.Exists(launcherDataPath + "/log4net.config"))
File.WriteAllText(launcherDataPath + "/log4net.config", Properties.Resources.log4netContents.Replace("${DATADIR}", launcherDataPath));
// Configure logger
XmlConfigurator.Configure(new FileInfo(launcherDataPath + "/log4net.config"));
try
{
Application MacLauncher = new Application(Eto.Platforms.Mac64);
LauncherUpdater.Main();
MacLauncher.UnhandledException += MacLauncher_UnhandledException;
MacLauncher.Run(new MainForm());
}
catch (Exception e)
{
log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.StackTrace.ToString());
Console.WriteLine(Language.Text.UnhandledException + "\n" + e.Message + "\n*****Stack Trace*****\n\n" + e.StackTrace.ToString());
Console.WriteLine("Check the logs at " + launcherDataPath + " for more info!");
}
//new Application(Eto.Platforms.Mac64).Run(new MainForm());
}
/// <summary>
/// This method gets fired when an unhandled excpetion occurs in <see cref="MainForm"/>.
/// </summary>
private static void MacLauncher_UnhandledException(object sender, Eto.UnhandledExceptionEventArgs e)
{
log.Error("An unhandled exception has occurred: \n*****Stack Trace*****\n\n" + e.ExceptionObject.ToString());
/*Application.Instance.Invoke(new Action(() =>
{
MessageBox.Show(Language.Text.UnhandledException + "\n*****Stack Trace*****\n\n" + e.ExceptionObject.ToString(), "Mac", MessageBoxType.Error);
}));*/
}
// This is a duplicate of CrossPlatformOperations.GenerateCurrentPath, because trying to invoke that would cause a crash due to currentPlatform not being initialized.
private static string GenerateCurrentPath()
{
string NIXHOME = Environment.GetEnvironmentVariable("HOME");
//Mac has the Path at HOME/Library/AM2RLauncher
string macPath = NIXHOME + "/Library/AM2RLauncher";
try
{
Directory.CreateDirectory(macPath);
return macPath;
}
catch { }
return Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
}
}
}

@ -0,0 +1,56 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace AM2RLauncher.Mac.Properties {
using System;
using System.Reflection;
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
[System.Diagnostics.DebuggerNonUserCodeAttribute()]
[System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static System.Resources.ResourceManager resourceMan;
private static System.Globalization.CultureInfo resourceCulture;
[System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Resources.ResourceManager ResourceManager {
get {
if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("AM2RLauncher.Mac.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
[System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)]
internal static System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
internal static string log4netContents {
get {
return ResourceManager.GetString("log4netContents", resourceCulture);
}
}
}
}

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="log4netContents" xml:space="preserve">
<value>&lt;log4net&gt;
&lt;root&gt;
&lt;level value="ALL" /&gt;
&lt;appender-ref ref="file" /&gt;
&lt;/root&gt;
&lt;appender name="file" type="log4net.Appender.RollingFileAppender"&gt;
&lt;file value="${DATADIR}/Logs/AM2RLauncher.log" /&gt;
&lt;appendToFile value="true" /&gt;
&lt;rollingStyle value="Once" /&gt;
&lt;maxSizeRollBackups value="7" /&gt;
&lt;maximumFileSize value="3MB" /&gt;
&lt;staticLogFileName value="true" /&gt;
&lt;layout type="log4net.Layout.PatternLayout"&gt;
&lt;conversionPattern value="%date [%thread] %level %logger - %message%newline" /&gt;
&lt;/layout&gt;
&lt;/appender&gt;
&lt;/log4net&gt;</value>
</data>
</root>

@ -9,6 +9,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AM2RLauncher.Gtk", "AM2RLau
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AM2RLauncher.Wpf", "AM2RLauncher.Wpf\AM2RLauncher.Wpf.csproj", "{8A162932-BAA3-429A-84C4-B93A1C85DDE3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AM2RLauncher.Mac", "AM2RLauncher.Mac\AM2RLauncher.Mac.csproj", "{6F240F19-144E-4704-A16A-8FDAC3867EC9}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -27,6 +29,10 @@ Global
{8A162932-BAA3-429A-84C4-B93A1C85DDE3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A162932-BAA3-429A-84C4-B93A1C85DDE3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A162932-BAA3-429A-84C4-B93A1C85DDE3}.Release|Any CPU.Build.0 = Release|Any CPU
{6F240F19-144E-4704-A16A-8FDAC3867EC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6F240F19-144E-4704-A16A-8FDAC3867EC9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F240F19-144E-4704-A16A-8FDAC3867EC9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F240F19-144E-4704-A16A-8FDAC3867EC9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

@ -36,9 +36,9 @@ namespace AM2RLauncher
public static readonly string NIXHOME = Environment.GetEnvironmentVariable("HOME");
/// <summary>
/// Path to the Config folder on *Nix-based systems.
/// Path to the Config folder on Linux-based systems.
/// </summary>
public static readonly string NIXXDGCONFIG = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
public static readonly string LINUXXDGCONFIG = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
/// <summary>
/// Current Path where the Launcher is located. For more info, check <see cref="GenerateCurrentPath"/>.
@ -67,8 +67,18 @@ namespace AM2RLauncher
"https://gitlab.com/am2r-community-developers/AM2R-Autopatcher-Linux.git"
};
}
else if (currentPlatform.IsMac)
{
return new List<string>
{
"https://github.com/Miepee/AM2R-Autopatcher-Mac.git",
"https://github.com/Miepee/AM2R-Autopatcher-Mac.git" //TODO: make mac official, put this on gitlab
};
}
else // Should never occur, but...
{
log.Error(currentPlatform.ID + " has no mirror lists!");
return new List<string>();
}
}
@ -78,8 +88,10 @@ namespace AM2RLauncher
/// </summary>
/// <param name="property">The property to get the value from.</param>
/// <returns>The value from <paramref name="property"/> as a string</returns>
// TODO: how often is launcherconfigpath / launcherconfigfilepath created? maybe create an extra variable for it so we don't generate it every time
public static string ReadFromConfig(string property)
{
log.Info($"Reading {property} from config.");
if (currentPlatform.IsWinForms)
{
// We use the configuration manager in order to read `property` from the app.config and then return it
@ -87,11 +99,16 @@ namespace AM2RLauncher
if (appConfig == null) throw new ArgumentException("The property " + property + " could not be found.");
return appConfig.ConnectionString;
}
if (currentPlatform.IsGtk)
else if (currentPlatform.IsGtk || currentPlatform.IsMac)
{
// Config for nix systems will be saved in XDG_CONFIG_HOME/AM2RLauncher (or if empty, ~/.config)
// Config for linux systems will be saved in XDG_CONFIG_HOME/AM2RLauncher (or if empty, ~/.config)
// Config for mac systems will be saved in ~/Library/Preferences/AM2RLauncher
string homePath = NIXHOME;
string launcherConfigPath = (String.IsNullOrWhiteSpace(NIXXDGCONFIG) ? (homePath + "/.config") : NIXXDGCONFIG) + "/AM2RLauncher";
string launcherConfigPath = "";
if (currentPlatform.IsGtk)
launcherConfigPath = (String.IsNullOrWhiteSpace(LINUXXDGCONFIG) ? (homePath + "/.config") : LINUXXDGCONFIG) + "/AM2RLauncher";
else if (currentPlatform.IsMac)
launcherConfigPath = homePath + "/Library/Preferences/AM2RLauncher";
string launcherConfigFilePath = launcherConfigPath + "/config.xml";
XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML();
@ -111,6 +128,8 @@ namespace AM2RLauncher
// This uses the indexer, which means, we can use the variable in order to get the property. Look at LauncherConfigXML for more info
return launcherConfig[property]?.ToString();
}
else
log.Error(currentPlatform.ID + " has no config to read from!");
return null;
}
@ -121,6 +140,7 @@ namespace AM2RLauncher
/// <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.");
if (currentPlatform.IsWinForms)
{
// We use the configuration manager in order to read from the app.config, change the value and save it
@ -134,11 +154,16 @@ namespace AM2RLauncher
appConfig.Save();
ConfigurationManager.RefreshSection("connectionStrings");
}
else if (currentPlatform.IsGtk)
else if (currentPlatform.IsGtk || currentPlatform.IsMac)
{
// Config for nix systems will be saved in XDG_CONFIG_HOME/AM2RLauncher (or if empty, ~/.config)
// Config for mac systems will be saved in ~/Library/Preferences/AM2RLauncher
string homePath = NIXHOME;
string launcherConfigPath = (String.IsNullOrWhiteSpace(NIXXDGCONFIG) ? (homePath + "/.config") : NIXXDGCONFIG) + "/AM2RLauncher";
string launcherConfigPath = "";
if (currentPlatform.IsGtk)
launcherConfigPath = (String.IsNullOrWhiteSpace(LINUXXDGCONFIG) ? (homePath + "/.config") : LINUXXDGCONFIG) + "/AM2RLauncher";
else if (currentPlatform.IsMac)
launcherConfigPath = homePath + "/Library/Preferences/AM2RLauncher";
string launcherConfigFilePath = launcherConfigPath + "/config.xml";
XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML();
@ -157,6 +182,8 @@ namespace AM2RLauncher
// Serialize back into the file
File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize<XML.LauncherConfigXML>(launcherConfig));
}
else
log.Error(currentPlatform.ID + " has no config to write to!");
}
/// <summary>
@ -182,11 +209,16 @@ namespace AM2RLauncher
File.WriteAllText(newConfigPath, newConfigText);
}
else if (currentPlatform.IsGtk)
else if (currentPlatform.IsGtk || currentPlatform.IsMac)
{
// Config for nix systems will be saved in XDG_CONFIG_HOME/AM2RLauncher (or if empty, ~/.config)
// Config for mac systems will be saved in ~/Library/Preferences/AM2RLauncher
string homePath = NIXHOME;
string launcherConfigPath = (String.IsNullOrWhiteSpace(NIXXDGCONFIG) ? (homePath + "/.config") : NIXXDGCONFIG) + "/AM2RLauncher";
string launcherConfigPath = "";
if (currentPlatform.IsGtk)
launcherConfigPath = (String.IsNullOrWhiteSpace(LINUXXDGCONFIG) ? (homePath + "/.config") : LINUXXDGCONFIG) + "/AM2RLauncher";
else if (currentPlatform.IsMac)
launcherConfigPath = homePath + "/Library/Preferences/AM2RLauncher";
string launcherConfigFilePath = launcherConfigPath + "/config.xml";
XML.LauncherConfigXML launcherConfig = new XML.LauncherConfigXML();
@ -194,6 +226,8 @@ namespace AM2RLauncher
launcherConfig = XML.Serializer.Deserialize<XML.LauncherConfigXML>(File.ReadAllText(launcherConfigFilePath));
File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize<XML.LauncherConfigXML>(launcherConfig));
}
else
log.Error(currentPlatform.ID + " has no config to transfer over!");
}
/// <summary>
@ -206,6 +240,10 @@ namespace AM2RLauncher
Process.Start(url);
else if (currentPlatform.IsGtk)
Process.Start("xdg-open", url);
else if (currentPlatform.IsMac)
Process.Start("open", url);
else
log.Error(currentPlatform.ID + " can't open URLs!");
}
/// <summary>
@ -231,11 +269,15 @@ namespace AM2RLauncher
// Linux only opens the directory bc opening and selecting a file is pain
else if (currentPlatform.IsGtk)
Process.Start("xdg-open", $"\"{realPath}\"");
else if (currentPlatform.IsMac)
Process.Start("open", $"\"{realPath}\"");
else
log.Error(currentPlatform.ID + " can't open folders!");
}
/// <summary>
/// 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.
/// 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)
@ -256,6 +298,10 @@ namespace AM2RLauncher
Process.Start("explorer.exe", $"/select, \"{realPath}\"");
else if (currentPlatform.IsGtk)
Process.Start("xdg-open", $"\"{Path.GetDirectoryName(realPath)}\"");
else if (currentPlatform.IsMac)
Process.Start("open", $"-R \"{realPath}\"");
else
log.Error(currentPlatform.ID + " can't open select files in file explorer!");
}
/// <summary>
@ -272,7 +318,7 @@ namespace AM2RLauncher
process = "cmd.exe";
arguments = "/C java -version";
}
else if (currentPlatform.IsGtk)
else if (currentPlatform.IsGtk || currentPlatform.IsMac)
{
process = "java";
arguments = "-version";
@ -373,7 +419,7 @@ namespace AM2RLauncher
proc.WaitForExit();
}
}
else if (currentPlatform.IsGtk)
else if (currentPlatform.IsGtk || currentPlatform.IsMac)
{
ProcessStartInfo parameters = new ProcessStartInfo
{
@ -405,7 +451,7 @@ namespace AM2RLauncher
proc = "cmd";
javaArgs = "/C java -jar";
}
else if (currentPlatform.IsGtk)
else if (currentPlatform.IsGtk || currentPlatform.IsMac)
{
proc = "java";
javaArgs = "-jar";
@ -497,6 +543,23 @@ namespace AM2RLauncher
log.Error($"There was an error with '{xdgDataHome}'!\n{ex.Message} {ex.StackTrace}. Falling back to defaults.");
}
}
else if (currentPlatform.IsMac)
{
//Mac has the Path at HOME/Library/AM2RLauncher
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(currentPlatform.ID + " has no current path!");
log.Info("Something went wrong, falling back to the default CurrentPath.");
return Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);

@ -25,6 +25,7 @@ namespace AM2RLauncher
static readonly private string oldConfigPath = CrossPlatformOperations.CURRENTPATH + "/" + CrossPlatformOperations.LAUNCHERNAME + ".oldCfg";
/// <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
static readonly private string updatePath = currentPlatform.IsWinForms ? CrossPlatformOperations.CURRENTPATH : Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory);
// Load reference to logger

@ -247,6 +247,7 @@ namespace AM2RLauncher.Helpers
Directory.Delete(tmpPath, true);
// If we didn't exit before, everything is fine
log.Info("AM2R_11 check successful!");
return IsZipAM2R11ReturnCodes.Successful;
}

@ -561,12 +561,12 @@ namespace AM2RLauncher
ProfileXML profile = Serializer.Deserialize<ProfileXML>(File.ReadAllText(modsDir + "/" + extractedName + "/profile.xml"));
// Check if the OS versions match
if ((Platform.IsWinForms && profile.OperatingSystem != "Windows") || (Platform.IsGtk && profile.OperatingSystem != "Linux"))
if ((Platform.IsWinForms && profile.OperatingSystem != "Windows") || (Platform.IsGtk && profile.OperatingSystem != "Linux") || (Platform.IsMac && profile.OperatingSystem != "Mac"))
{
string currentOS = "";
if (Platform.IsWinForms) currentOS = "Windows";
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.
else if (Platform.IsMac) currentOS = "Mac";
log.Error("Mod is for " + profile.OperatingSystem + " while current OS is " + Platform + ". Cancelling mod import.");
@ -633,6 +633,8 @@ namespace AM2RLauncher
/// </summary>
private void SettingsProfileDropDownSelectedIndexChanged(object sender, EventArgs e)
{
//TODO: for some reason, clearing the dropdown triggers this event on mac. Why!?
if (Platform.IsMac && settingsProfileDropDown.SelectedIndex == -1 && settingsProfileDropDown.Items.Count == 0) return;
log.Info("SettingsProfileDropDown.SelectedIndex has been changed to " + settingsProfileDropDown.SelectedIndex + ".");
if (settingsProfileDropDown.SelectedIndex <= 0 || settingsProfileDropDown.Items.Count == 0)
@ -661,6 +663,7 @@ namespace AM2RLauncher
profileNotesTextArea.TextColor = colGreen;
profileNotesTextArea.Text = Language.Text.ProfileNotes + "\n" + profileList[settingsProfileDropDown.SelectedIndex].ProfileNotes;
}
}
/// <summary>
@ -676,6 +679,7 @@ namespace AM2RLauncher
addModButton.Enabled = false;
settingsProfileLabel.TextColor = colInactive;
settingsProfileDropDown.Enabled = false;
profileButton.Enabled = false;
saveButton.Enabled = false;
updateModButton.Enabled = false;
deleteModButton.Enabled = false;
@ -741,6 +745,9 @@ namespace AM2RLauncher
/// <summary>Gets called when user selects a different item from <see cref="profileDropDown"/> and changes <see cref="profileAuthorLabel"/> accordingly.</summary>
private void ProfileDropDownSelectedIndexChanged(object sender, EventArgs e)
{
//TODO: eto bug maybe? for some reason this method shouldnt even get fired when clearing a dropdown...
if (Platform.IsMac && profileDropDown.SelectedIndex == -1 && profileDropDown.Items.Count == 0) return;
profileIndex = profileDropDown.SelectedIndex;
log.Info("profileDropDown.SelectedIndex has been changed to " + profileIndex + ".");
@ -753,7 +760,9 @@ namespace AM2RLauncher
saveWarningLabel.Visible = true;
else
saveWarningLabel.Visible = false;
UpdateStateMachine();
}
/// <summary>Gets called when user selects a different item from <see cref="languageDropDown"/> and writes that to the config.</summary>

@ -25,12 +25,16 @@ namespace AM2RLauncher
using (var repo = new Repository(CrossPlatformOperations.CURRENTPATH + "/PatchData"))
{
// Permanently undo commits not pushed to remote
Branch originMaster = repo.Branches["origin/master"];
Branch originMaster = repo.Branches.ToList().Where(b => b.FriendlyName.Contains("origin/master") || b.FriendlyName.Contains("origin/main")).FirstOrDefault();
if (originMaster == null)
{
log.Info("Neither branch 'master' nor branch 'main' could be found! Corrupted or invalid git repo? Deleting PatchData...");
// Directory exists, but seems corrupted, we delete it and prompt the user to download it again.
MessageBox.Show(Language.Text.CorruptPatchData, Language.Text.ErrorWindowTitle, MessageBoxType.Error);
Application.Instance.Invoke(new Action(() =>
{
MessageBox.Show(Language.Text.CorruptPatchData, Language.Text.ErrorWindowTitle, MessageBoxType.Error);
}));
HelperMethods.DeleteDirectory(CrossPlatformOperations.CURRENTPATH + "/PatchData");
throw new UserCancelledException();
}
@ -84,7 +88,10 @@ 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");
else if (Platform.IsGtk) return File.Exists(CrossPlatformOperations.CURRENTPATH + "/Profiles/" + profile.Name + "/AM2R.AppImage");
else if (Platform.IsMac) return Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/Profiles/" + profile.Name + "/AM2R.app");
log.Error(Platform.ID + " can't have profiles installed!");
return false;
}
@ -212,7 +219,7 @@ namespace AM2RLauncher
settingsProfileDropDown.Items.AddRange(profileDropDown.Items);
settingsProfileDropDown.SelectedIndex = profileDropDown.Items.Count != 0 ? 0 : -1;
log.Info("Profiles loaded.");
log.Info("Loaded " + profileList.Count + " profile(s).");
// Refresh the author and version label on the main tab
if (profileList.Count > 0)
@ -233,7 +240,7 @@ namespace AM2RLauncher
log.Info("Installing profile " + profile.Name + "...");
// Check if xdelta is installed on linux, by searching all folders in PATH
if (Platform.IsGtk && !CrossPlatformOperations.CheckIfXdeltaIsInstalled())
if ((Platform.IsGtk || Platform.IsMac) && !CrossPlatformOperations.CheckIfXdeltaIsInstalled())
{
Application.Instance.Invoke(new Action(() =>
{
@ -256,20 +263,35 @@ namespace AM2RLauncher
// This failsafe should NEVER get triggered, but Miepee's broken this too much for me to trust it otherwise.
if (Directory.Exists(profilePath))
Directory.Delete(profilePath, true);
// Create profile directory
Directory.CreateDirectory(profilePath);
// Switch profilePath on Gtk
if (Platform.IsGtk)
{
//TODO: it was just recursively deleted, is this really necessary?
if (Directory.Exists(profilePath + "/assets"))
Directory.Delete(profilePath + "/assets", true);
profilePath += "/assets";
Directory.CreateDirectory(profilePath);
}
else if (Platform.IsMac)
{
// Folder structure for mac is like this:
// am2r.app -> Contents
// -Frameworks (some libs)
// -MacOS (runner)
// -Resources (asset path)
profilePath += "/AM2R.app/Contents";
Directory.CreateDirectory(profilePath);
Directory.CreateDirectory(profilePath + "/MacOS");
Directory.CreateDirectory(profilePath + "/Resources");
profilePath += "/Resources";
log.Info("ProfileInstallstion: Created folder structure.");
}
// Extract 1.1
ZipFile.ExtractToDirectory(CrossPlatformOperations.CURRENTPATH + "/AM2R_11.zip", profilePath);
@ -298,6 +320,15 @@ namespace AM2RLauncher
exe = Regex.Match(desktopContents, @"(?<=Exec=).*").Value;
log.Info("According to AppImage desktop file, using \"" + exe + "\" as game name.");
}
else if (Platform.IsMac)
{
datawin = "game.ios";
exe = "Mac_Runner";
}
else
{
log.Error(Platform.ID + " does not have valid runner / data.win names!");
}
log.Info("Attempting to patch in " + profilePath);
@ -317,24 +348,31 @@ namespace AM2RLauncher
CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/AM2R.exe", dataPath + "/AM2R.xdelta", profilePath + "/" + exe);
}
}
else if (Platform.IsGtk) // YYC and VM look exactly the same on Linux so we're all good here.
else if (Platform.IsGtk || Platform.IsMac) // YYC and VM look exactly the same on Linux and Mac so we're all good here.
{
CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/data.win", dataPath + "/game.xdelta", profilePath + "/" + datawin);
CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/AM2R.exe", dataPath + "/AM2R.xdelta", profilePath + "/" + exe);
// Just in case the resulting file isn't chmoddded...
Process.Start("chmod", "+x \"" + profilePath + "/" + exe + "\"").WaitForExit();
// These are not needed by linux at all, so we delete them
// These are not needed by linux or Mac at all, so we delete them
File.Delete(profilePath + "/data.win");
File.Delete(profilePath + "/AM2R.exe");
File.Delete(profilePath + "/D3DX9_43.dll");
// Move exe one directory out
File.Move(profilePath + "/" + exe, profilePath.Substring(0, profilePath.LastIndexOf("/")) + "/" + exe);
// Move exe one directory out on Linux, move to MacOS folder instead on Mac
if (Platform.IsGtk)
File.Move(profilePath + "/" + exe, profilePath.Substring(0, profilePath.LastIndexOf("/")) + "/" + exe);
else
File.Move(profilePath + "/" + exe, profilePath.Replace("Resources", "MacOS") + "/" + exe);
}
else
{
log.Error(Platform.ID + " does not have patching methods!");
}
// Applied patch
if (Platform.IsWinForms) UpdateProgressBar(66);
if (Platform.IsWinForms || Platform.IsMac) UpdateProgressBar(66);
else if (Platform.IsGtk) UpdateProgressBar(44); // Linux will take a bit longer, due to appimage creation
log.Info("xdelta patch(es) applied.");
@ -388,6 +426,22 @@ namespace AM2RLauncher
if (File.Exists(profilePath + "/AM2R.AppImage")) File.Delete(profilePath + "/AM2R.AppImage");
File.Move(profilePath + "/" + "AM2R-x86_64.AppImage", profilePath + "/AM2R.AppImage");
}
// Mac post-process
else if (Platform.IsMac)
{
// Rename all songs to lowercase
foreach (var file in new DirectoryInfo(profilePath).GetFiles())
if (file.Name.EndsWith(".ogg") && !File.Exists(file.DirectoryName + "/" + file.Name.ToLower()))
File.Move(file.FullName, file.DirectoryName + "/" + file.Name.ToLower());
// Loading custom fonts crashes on Mac, so we delete those
Directory.Delete(profilePath + "/lang/fonts", true);
// Move Frameworks, Info.plist and PkgInfo over
HelperMethods.DirectoryCopy(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/Frameworks", profilePath.Replace("Resources", "Frameworks"));
File.Copy(dataPath + "/Info.plist", profilePath.Replace("Resources", "") + "/Info.plist", true);
File.Copy(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/PkgInfo", profilePath.Replace("Resources", "") + "/PkgInfo", true);
//Put profilePath back to what it was before
profilePath = profilesHomePath + "/" + profile.Name;
}
// Copy profile.xml so we can grab data to compare for updates later!
// tldr; check if we're in PatchData or not
@ -428,7 +482,7 @@ namespace AM2RLauncher
return;
}
// Check if xdelta is installed on linux
if (Platform.IsGtk && !CrossPlatformOperations.CheckIfXdeltaIsInstalled())
if ((Platform.IsGtk || Platform.IsMac) && !CrossPlatformOperations.CheckIfXdeltaIsInstalled())
{
// Message box show needs to be done on main thread
Application.Instance.Invoke(new Action(() =>
@ -672,6 +726,48 @@ namespace AM2RLauncher
}
}
else if (Platform.IsMac)
{
// Sets the arguments to only open the game, or append the profiles save path/logs and create time based logs. Creates the folder if necessary.
string arguments = "AM2R.app -W";
// Game logging
if ((bool)profileDebugLogCheck.Checked)
{
log.Info("Performing logging setup for profile " + profile.Name + ".");
if (!Directory.Exists(logDir.FullName))
Directory.CreateDirectory(logDir.FullName);
if (File.Exists(logDir.FullName + "/" + profile.Name + ".txt"))
HelperMethods.RecursiveRollover(logDir.FullName + "/" + profile.Name + ".txt", 5);
StreamWriter stream = File.AppendText(logDir.FullName + "/" + profile.Name + ".txt");
stream.WriteLine("AM2RLauncher " + VERSION + " log generated at " + date);
stream.Flush();
stream.Close();
arguments += " --stdout \"" + logDir.FullName + "/" + profile.Name + ".txt\" --stderr \"" + logDir.FullName + "/" + profile.Name + ".txt\"";
}
ProcessStartInfo proc = new ProcessStartInfo();
proc.WorkingDirectory = CrossPlatformOperations.CURRENTPATH + "/Profiles/" + profile.Name;
proc.FileName = "open";
proc.Arguments = arguments;
log.Info("CWD of Profile is " + proc.WorkingDirectory);
using (var p = Process.Start(proc))
{
p.WaitForExit();
}
}
else
log.Error(Platform.ID + " cannot run games!");
log.Info("Profile " + profile.Name + " process exited.");
}

@ -127,12 +127,17 @@ namespace AM2RLauncher
// Log distro and version (if it exists)
if (Platform.IsGtk)
{
string osRelease = File.ReadAllText("/etc/os-release");
Regex lineRegex = new Regex(".*=.*");
var results = lineRegex.Matches(osRelease).Cast<Match>();
var version = results.FirstOrDefault(x => x.Value.Contains("VERSION"));
log.Info("Current Distro: " + results.FirstOrDefault(x => x.Value.Contains("NAME")).Value.Substring(5).Replace("\"", "") +
(version == null ? "" : " " + version.Value.Substring(8).Replace("\"", "")));
if (File.Exists("/etc/os-release"))
{
string osRelease = File.ReadAllText("/etc/os-release");
Regex lineRegex = new Regex(".*=.*");
var results = lineRegex.Matches(osRelease).Cast<Match>();
var version = results.FirstOrDefault(x => x.Value.Contains("VERSION"));
log.Info("Current Distro: " + results.FirstOrDefault(x => x.Value.Contains("NAME")).Value.Substring(5).Replace("\"", "") +
(version == null ? "" : " " + version.Value.Substring(8).Replace("\"", "")));
}
else
log.Error("Couldn't determine the currently running distro!");
}
// Set the Current Directory to the path the Launcher is located. Fixes some relative path issues.
@ -162,6 +167,10 @@ namespace AM2RLauncher
Image = am2rIcon
};
// Create menubar with defaults for mac
if (Platform.IsMac)
Menu = new MenuBar { };
// Create array from validCount
profileList = new List<ProfileXML>();
@ -223,6 +232,9 @@ namespace AM2RLauncher
// Drawable paint event
drawable.Paint += DrawablePaintEvent;
// Some systems don't call the paintEvent by default and only do so after actual resizing
if (Platform.IsMac)
LoadComplete += (sender, e) => { Size = new Size(Size.Width + 1, Size.Height); Size = new Size(Size.Width - 1, Size.Height);};
#region MAIN WINDOW
@ -309,10 +321,11 @@ namespace AM2RLauncher
// Yes, we know this looks horrific on GTK. Sorry.
// We're not exactly in a position to rewrite the entire DropDown object as a Drawable child, but if you want to, you're more than welcome!
// Mac gets a default BackgroundColor because it looks waaaaaaay better.
profileDropDown = new DropDown
{
TextColor = colGreen,
BackgroundColor = colBGNoAlpha,
BackgroundColor = Platform.IsWinForms ? colBGNoAlpha : new Color(),
};
// In order to not have conflicting theming, we just always respect the users theme for dropdown on GTK.
if (Platform.IsGtk)
@ -518,7 +531,7 @@ namespace AM2RLauncher
languageDropDown = new DropDown
{
TextColor = colGreen,
BackgroundColor = colBGNoAlpha,
BackgroundColor = Platform.IsWinForms ? colBGNoAlpha : new Color(),
};
if (Platform.IsGtk)
languageDropDown = new DropDown { };
@ -608,7 +621,7 @@ namespace AM2RLauncher
mirrorDropDown = new DropDown
{
TextColor = colGreen,
BackgroundColor = colBGNoAlpha,
BackgroundColor = Platform.IsWinForms ? colBGNoAlpha : new Color(),
};
if (Platform.IsGtk)
mirrorDropDown = new DropDown { };
@ -679,7 +692,7 @@ namespace AM2RLauncher
settingsProfileDropDown = new DropDown
{
TextColor = colGreen,
BackgroundColor = colBGNoAlpha,
BackgroundColor = Platform.IsWinForms ? colBGNoAlpha : new Color(),
};
// In order to not have conflicting theming, we just always respect the users theme for dropdown on GTK.
@ -813,6 +826,7 @@ namespace AM2RLauncher
if (Platform.IsGtk)
customEnvVarTextBox.LostFocus += CustomEnvVarTextBoxLostFocus;
//TODO: These don't work properly on mac? Maybe on other platforms too?
newsWebView.DocumentLoaded += NewsWebViewDocumentLoaded;
changelogWebView.DocumentLoaded += ChangelogWebViewDocumentLoaded;

@ -49,6 +49,7 @@ namespace AM2RLauncher.XML
{ get; set; }
/// <summary>Indicates whether or not to create debug logs of profile. Used for <see cref="MainForm.profileDebugLogCheck"/></summary>
[XmlAttribute("ProfileDebugLog")]
//TODO: WTF WHY!?
public string ProfileDebugLog
{ get; set; }
/// <summary>Indicates the custom environment variable(s) as text. Used for <see cref="MainForm.customEnvVarTextBox"/></summary>

Loading…
Cancel
Save