diff --git a/.gitignore b/.gitignore index fb2c22e..c6244ef 100644 --- a/.gitignore +++ b/.gitignore @@ -352,3 +352,6 @@ MigrationBackup/ # Visual Studio Code folder .vscode/ + +# Mac's DS_Stores +.DS_Store diff --git a/AM2RLauncher/AM2RLauncher.Gtk/Program.cs b/AM2RLauncher/AM2RLauncher.Gtk/Program.cs index c3652c7..8417535 100644 --- a/AM2RLauncher/AM2RLauncher.Gtk/Program.cs +++ b/AM2RLauncher/AM2RLauncher.Gtk/Program.cs @@ -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. diff --git a/AM2RLauncher/AM2RLauncher.Mac/AM2RLauncher.Mac.csproj b/AM2RLauncher/AM2RLauncher.Mac/AM2RLauncher.Mac.csproj new file mode 100644 index 0000000..c95dd8b --- /dev/null +++ b/AM2RLauncher/AM2RLauncher.Mac/AM2RLauncher.Mac.csproj @@ -0,0 +1,43 @@ + + + + WinExe + net6.0 + osx-x64 + LatestMajor + + + + 4 + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + + + + diff --git a/AM2RLauncher/AM2RLauncher.Mac/Icon.icns b/AM2RLauncher/AM2RLauncher.Mac/Icon.icns new file mode 100644 index 0000000..f84187f Binary files /dev/null and b/AM2RLauncher/AM2RLauncher.Mac/Icon.icns differ diff --git a/AM2RLauncher/AM2RLauncher.Mac/Info.plist b/AM2RLauncher/AM2RLauncher.Mac/Info.plist new file mode 100644 index 0000000..130f17d --- /dev/null +++ b/AM2RLauncher/AM2RLauncher.Mac/Info.plist @@ -0,0 +1,20 @@ + + + + + CFBundleName + AM2RLauncher + CFBundleIdentifier + com.example.AM2RLauncher + CFBundleShortVersionString + 1.0 + LSMinimumSystemVersion + 10.12 + CFBundleDevelopmentRegion + en + NSHumanReadableCopyright + + CFBundleIconFile + Icon.icns + + diff --git a/AM2RLauncher/AM2RLauncher.Mac/Program.cs b/AM2RLauncher/AM2RLauncher.Mac/Program.cs new file mode 100644 index 0000000..6cce445 --- /dev/null +++ b/AM2RLauncher/AM2RLauncher.Mac/Program.cs @@ -0,0 +1,83 @@ +using Eto.Forms; +using log4net; +using log4net.Config; +using System; +using System.IO; +using System.Reflection; + +namespace AM2RLauncher.Mac +{ + /// + /// The main class for the Mac project. + /// + class MainClass + { + /// + /// The logger for , used to write any caught exceptions. + /// + private static readonly ILog log = LogManager.GetLogger(typeof(MainForm)); + + /// + /// The main method for the Mac project. + /// + [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()); + } + + /// + /// This method gets fired when an unhandled excpetion occurs in . + /// + 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); + } + } +} diff --git a/AM2RLauncher/AM2RLauncher.Mac/Properties/Resources.Designer.cs b/AM2RLauncher/AM2RLauncher.Mac/Properties/Resources.Designer.cs new file mode 100644 index 0000000..641e315 --- /dev/null +++ b/AM2RLauncher/AM2RLauncher.Mac/Properties/Resources.Designer.cs @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +// +// 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. +// +//------------------------------------------------------------------------------ + +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); + } + } + } +} diff --git a/AM2RLauncher/AM2RLauncher.Mac/Properties/Resources.resx b/AM2RLauncher/AM2RLauncher.Mac/Properties/Resources.resx new file mode 100644 index 0000000..a2cff8e --- /dev/null +++ b/AM2RLauncher/AM2RLauncher.Mac/Properties/Resources.resx @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + <log4net> + <root> + <level value="ALL" /> + <appender-ref ref="file" /> + </root> + <appender name="file" type="log4net.Appender.RollingFileAppender"> + <file value="${DATADIR}/Logs/AM2RLauncher.log" /> + <appendToFile value="true" /> + <rollingStyle value="Once" /> + <maxSizeRollBackups value="7" /> + <maximumFileSize value="3MB" /> + <staticLogFileName value="true" /> + <layout type="log4net.Layout.PatternLayout"> + <conversionPattern value="%date [%thread] %level %logger - %message%newline" /> + </layout> + </appender> +</log4net> + + \ No newline at end of file diff --git a/AM2RLauncher/AM2RLauncher.sln b/AM2RLauncher/AM2RLauncher.sln index 836a33c..e616f49 100644 --- a/AM2RLauncher/AM2RLauncher.sln +++ b/AM2RLauncher/AM2RLauncher.sln @@ -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 diff --git a/AM2RLauncher/AM2RLauncher/CrossPlatformOperations.cs b/AM2RLauncher/AM2RLauncher/CrossPlatformOperations.cs index 5df6d4e..b26daa5 100644 --- a/AM2RLauncher/AM2RLauncher/CrossPlatformOperations.cs +++ b/AM2RLauncher/AM2RLauncher/CrossPlatformOperations.cs @@ -36,9 +36,9 @@ namespace AM2RLauncher public static readonly string NIXHOME = Environment.GetEnvironmentVariable("HOME"); /// - /// Path to the Config folder on *Nix-based systems. + /// Path to the Config folder on Linux-based systems. /// - public static readonly string NIXXDGCONFIG = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); + public static readonly string LINUXXDGCONFIG = Environment.GetEnvironmentVariable("XDG_CONFIG_HOME"); /// /// Current Path where the Launcher is located. For more info, check . @@ -67,8 +67,18 @@ namespace AM2RLauncher "https://gitlab.com/am2r-community-developers/AM2R-Autopatcher-Linux.git" }; } + else if (currentPlatform.IsMac) + { + return new List + { + "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(); } } @@ -78,8 +88,10 @@ namespace AM2RLauncher /// /// The property to get the value from. /// The value from as a string + // 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 /// The value that will be written. 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(launcherConfig)); } + else + log.Error(currentPlatform.ID + " has no config to write to!"); } /// @@ -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(File.ReadAllText(launcherConfigFilePath)); File.WriteAllText(launcherConfigFilePath, XML.Serializer.Serialize(launcherConfig)); } + else + log.Error(currentPlatform.ID + " has no config to transfer over!"); } /// @@ -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!"); } /// @@ -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!"); } /// /// 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. + /// Only selects on Windows and Mac, on Linux it just opens the folder. Does nothing if file doesn't exist. /// /// Path to open. 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!"); } /// @@ -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); diff --git a/AM2RLauncher/AM2RLauncher/LauncherUpdater.cs b/AM2RLauncher/AM2RLauncher/LauncherUpdater.cs index b927332..cbab196 100644 --- a/AM2RLauncher/AM2RLauncher/LauncherUpdater.cs +++ b/AM2RLauncher/AM2RLauncher/LauncherUpdater.cs @@ -25,6 +25,7 @@ namespace AM2RLauncher static readonly private string oldConfigPath = CrossPlatformOperations.CURRENTPATH + "/" + CrossPlatformOperations.LAUNCHERNAME + ".oldCfg"; /// The actual Path where the executable is stored, only used for updating. + //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 diff --git a/AM2RLauncher/AM2RLauncher/MainForm/HelperMethods.cs b/AM2RLauncher/AM2RLauncher/MainForm/HelperMethods.cs index b795e60..629ef47 100644 --- a/AM2RLauncher/AM2RLauncher/MainForm/HelperMethods.cs +++ b/AM2RLauncher/AM2RLauncher/MainForm/HelperMethods.cs @@ -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; } diff --git a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Events.cs b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Events.cs index 13292f3..cc1fe4a 100644 --- a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Events.cs +++ b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Events.cs @@ -561,12 +561,12 @@ namespace AM2RLauncher ProfileXML profile = Serializer.Deserialize(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 /// 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; } + } /// @@ -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 /// Gets called when user selects a different item from and changes accordingly. 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(); + } /// Gets called when user selects a different item from and writes that to the config. diff --git a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Methods.cs b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Methods.cs index ff27642..986cc68 100644 --- a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Methods.cs +++ b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.Methods.cs @@ -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."); } diff --git a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.UI.cs b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.UI.cs index f30f4f1..708c616 100644 --- a/AM2RLauncher/AM2RLauncher/MainForm/MainForm.UI.cs +++ b/AM2RLauncher/AM2RLauncher/MainForm/MainForm.UI.cs @@ -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(); - 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(); + 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(); @@ -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; diff --git a/AM2RLauncher/AM2RLauncher/XML/LauncherConfigXML.cs b/AM2RLauncher/AM2RLauncher/XML/LauncherConfigXML.cs index 3e6309c..31d4268 100644 --- a/AM2RLauncher/AM2RLauncher/XML/LauncherConfigXML.cs +++ b/AM2RLauncher/AM2RLauncher/XML/LauncherConfigXML.cs @@ -49,6 +49,7 @@ namespace AM2RLauncher.XML { get; set; } /// Indicates whether or not to create debug logs of profile. Used for [XmlAttribute("ProfileDebugLog")] + //TODO: WTF WHY!? public string ProfileDebugLog { get; set; } /// Indicates the custom environment variable(s) as text. Used for