/// <summary> /// If the <see cref="customMirrorTextBox"/> has lost focus, we set its text as the new <see cref="currentMirror"/>. /// </summary> private void CustomMirrorTextBoxLostFocus(object sender, EventArgs e) { // Check first, if the text is a valid git repo Regex gitURLRegex = new Regex("https://.*\\.git"); string mirrorText = customMirrorTextBox.Text; if (!gitURLRegex.IsMatch(mirrorText)) { log.Info("User used " + mirrorText + " as a custom Mirror, didn't pass git validation test."); MessageBox.Show(this, HelperMethods.GetText(Text.InvalidGitURL, mirrorText), Text.ErrorWindowTitle, MessageBoxType.Error); return; } currentMirror = mirrorText; CrossPlatformOperations.WriteToConfig("CustomMirrorText", currentMirror); log.Info("Overwriting mirror in gitconfig."); // Check if the gitConfig exists, if yes regex the gitURL, and replace it with the new current Mirror. string gitConfigPath = CrossPlatformOperations.CURRENTPATH + "/PatchData/.git/config"; if (!File.Exists(gitConfigPath)) { return; } string gitConfig = File.ReadAllText(gitConfigPath); Match match = gitURLRegex.Match(gitConfig); gitConfig = gitConfig.Replace(match.Value, currentMirror); File.WriteAllText(gitConfigPath, gitConfig); log.Info("Custom Mirror has been set to " + currentMirror + "."); }
/// <summary>Gets called when user selects a different item from <see cref="mirrorDropDown"/>. /// It then writes that to the config, and if <see cref="updateState"/> is not <see cref="PlayButtonState.Downloading"/> /// it also overwrites the upstream URL in .git/config.</summary> private void MirrorDropDownSelectedIndexChanged(object sender, EventArgs e) { currentMirror = mirrorList[mirrorDropDown.SelectedIndex]; log.Info("Current mirror has been set to " + currentMirror + "."); CrossPlatformOperations.WriteToConfig("MirrorIndex", mirrorDropDown.SelectedIndex); // Don't overwrite the git config while we download!!! if (updateState == PlayButtonState.Downloading) { return; } log.Info("Overwriting mirror in gitconfig."); // Check if the gitConfig exists, if yes regex the gitURL, and replace it with the new current Mirror. string gitConfigPath = CrossPlatformOperations.CURRENTPATH + "/PatchData/.git/config"; if (!File.Exists(gitConfigPath)) { return; } string gitConfig = File.ReadAllText(gitConfigPath); Regex gitURLRegex = new Regex("https://.*\\.git"); Match match = gitURLRegex.Match(gitConfig); gitConfig = gitConfig.Replace(match.Value, currentMirror); File.WriteAllText(gitConfigPath, gitConfig); }
/// <summary> /// This opens the save directory for the current profile. /// </summary> private void SaveButtonClickEvent(object sender, EventArgs e) { if (!IsProfileIndexValid()) { return; } ProfileXML profile = profileList[modSettingsProfileDropDown.SelectedIndex]; log.Info("User opened the save directory for profile " + profile.Name + ", which is " + profile.SaveLocation); CrossPlatformOperations.OpenFolder(profile.SaveLocation); }
/// <summary> /// Gets called when user tries to close <see cref="MainForm"/>. This does a few things:<br/> /// 1) Writes the Width, Height, the check if <see cref="MainForm"/> is currently maximized and the ProfileIndex to the Config<br/> /// 2) Checks if current <see cref="updateState"/> is <see cref="PlayButtonState.Downloading"/>. If yes, it creates a Warning to the end user. /// </summary> private void MainformClosing(object sender, CancelEventArgs e) { log.Info("Attempting to close MainForm!"); CrossPlatformOperations.WriteToConfig("Width", ClientSize.Width); CrossPlatformOperations.WriteToConfig("Height", ClientSize.Height); CrossPlatformOperations.WriteToConfig("IsMaximized", this.WindowState == WindowState.Maximized); CrossPlatformOperations.WriteToConfig("ProfileIndex", profileIndex.ToString()); switch (updateState) { // If we're currently still downloading, ask first if user really wants to close and cancel the event if necessary case PlayButtonState.Downloading: { var result = MessageBox.Show(this, Text.CloseOnCloningText, Text.WarningWindowTitle, MessageBoxButtons.YesNo, MessageBoxType.Warning, MessageBoxDefaultButton.No); if (result == DialogResult.No) { e.Cancel = true; } else { isGitProcessGettingCancelled = true; } // We don't need to delete any folders here, the cancelled gitClone will do that automatically for us :) break; } // We can't close during installing, so we cancel the event. case PlayButtonState.Installing: { MessageBox.Show(this, Text.CloseOnInstallingText, Text.WarningWindowTitle, MessageBoxButtons.OK, MessageBoxType.Warning); e.Cancel = true; break; } } // This needs to be made invisible, otherwise a tray indicator will be visible (on linux?) that clicking crashes the application //TODO: this sounds like an eto bug. check if this can get reproduced. trayIndicator.Visible = false; if (e.Cancel) { log.Info("Cancelled MainForm closing event during UpdateState." + updateState + "."); } else { log.Info("Successfully closed MainForm. Exiting main thread."); } }
/// <summary> /// Does stuff, depending on the current state of <see cref="apkButtonState"/>. /// </summary> private async void ApkButtonClickEvent(object sender, EventArgs e) { // If we're currently creating something, exit if (apkButtonState == ApkButtonState.Creating) { return; } // Check for java, exit safely with a warning if not found! if (!CrossPlatformOperations.IsJavaInstalled()) { MessageBox.Show(this, Text.JavaNotFound, Text.WarningWindowTitle, MessageBoxButtons.OK); SetApkButtonState(ApkButtonState.Create); UpdateStateMachine(); log.Error("Java not found! Aborting Android APK creation."); return; } // Check if xdelta is installed on unix, exit with a warning if not. if (OS.IsUnix && !CrossPlatformOperations.CheckIfXdeltaIsInstalled()) { MessageBox.Show(this, Text.XdeltaNotFound, Text.WarningWindowTitle, MessageBoxButtons.OK); SetApkButtonState(ApkButtonState.Create); UpdateStateMachine(); log.Error("Xdelta not found. Aborting Android APK creation..."); return; } UpdateStateMachine(); if (apkButtonState == ApkButtonState.Create) { SetApkButtonState(ApkButtonState.Creating); UpdateStateMachine(); progressBar.Visible = true; bool useHqMusic = hqMusicAndroidCheck.Checked.Value; var progressIndicator = new Progress <int>(UpdateProgressBar); await Task.Run(() => Profile.CreateAPK(profileList[profileIndex.Value], useHqMusic, progressIndicator)); SetApkButtonState(ApkButtonState.Create); progressBar.Visible = false; UpdateStateMachine(); } }
/// <summary>Gets called when <see cref="customMirrorCheck"/> gets clicked, displays a warning <see cref="MessageBox"/> /// and enables <see cref="customMirrorTextBox"/> accordingly.</summary> private void CustomMirrorCheckChanged(object sender, EventArgs e) { log.Info("Use Custom Mirror option has been set to " + customMirrorCheck.Checked + "."); CrossPlatformOperations.WriteToConfig("CustomMirrorEnabled", (bool)customMirrorCheck.Checked); EnableMirrorControlsAccordingly(); // Create warning dialog when enabling if ((bool)customMirrorCheck.Checked) { MessageBox.Show(this, Text.WarningWindowText, Text.WarningWindowTitle, MessageBoxType.Warning); currentMirror = customMirrorTextBox.Text; } else { // Revert mirror to selected index in mirror dropdown currentMirror = mirrorList[mirrorDropDown.SelectedIndex]; } }
/// <summary> /// Installs <paramref name="profile"/>. /// </summary> /// <param name="profile"><see cref="ProfileXML"/> to be installed.</param> /// <param name="useHqMusic">Wether to patch this with high quality music or not.</param> /// <param name="progress">Provides the current progress of this method.</param> public static void InstallProfile(ProfileXML profile, bool useHqMusic, IProgress <int> progress) { log.Info("Installing profile " + profile.Name + "..."); string profilesHomePath = CrossPlatformOperations.CURRENTPATH + "/Profiles"; string profilePath = profilesHomePath + "/" + profile.Name; // Failsafe for Profiles directory if (!Directory.Exists(profilesHomePath)) { Directory.CreateDirectory(profilesHomePath); } // 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 (OS.IsLinux) { profilePath += "/assets"; Directory.CreateDirectory(profilePath); } else if (OS.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("ProfileInstallation: Created folder structure."); } // Extract 1.1 ZipFile.ExtractToDirectory(CrossPlatformOperations.CURRENTPATH + "/AM2R_11.zip", profilePath); // Extracted 1.1 progress.Report(33); log.Info("Profile folder created and AM2R_11.zip extracted."); // Set local datapath for installation files var dataPath = CrossPlatformOperations.CURRENTPATH + profile.DataPath; string datawin = null, exe = null; if (OS.IsWindows) { datawin = "data.win"; exe = "AM2R.exe"; } else if (OS.IsLinux) { 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, using \"" + exe + "\" as game name."); } else if (OS.IsMac) { datawin = "game.ios"; exe = "Mac_Runner"; } else { log.Error(OS.Name + " does not have valid runner / data.win names!"); } log.Info("Attempting to patch in " + profilePath); if (OS.IsWindows) { // Patch game executable if (profile.UsesYYC) { CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/data.win", dataPath + "/AM2R.xdelta", profilePath + "/" + exe); // Delete 1.1's data.win, we don't need it anymore! File.Delete(profilePath + "/data.win"); } else { CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/data.win", dataPath + "/data.xdelta", profilePath + "/" + datawin); CrossPlatformOperations.ApplyXdeltaPatch(profilePath + "/AM2R.exe", dataPath + "/AM2R.xdelta", profilePath + "/" + exe); } } else if (OS.IsUnix) // 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 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 on Linux, move to MacOS folder instead on Mac if (OS.IsLinux) { File.Move(profilePath + "/" + exe, profilePath.Substring(0, profilePath.LastIndexOf("/")) + "/" + exe); } else { File.Move(profilePath + "/" + exe, profilePath.Replace("Resources", "MacOS") + "/" + exe); } } else { log.Error(OS.Name + " does not have patching methods!"); } // Applied patch if (OS.IsWindows || OS.IsMac) { progress.Report(66); } else if (OS.IsLinux) { progress.Report(44); // Linux will take a bit longer, due to appimage creation } log.Info("xdelta patch(es) applied."); // Install new datafiles HelperMethods.DirectoryCopy(dataPath + "/files_to_copy", profilePath); // HQ music if (!profile.UsesCustomMusic && useHqMusic) { HelperMethods.DirectoryCopy(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/HDR_HQ_in-game_music", profilePath); } // Linux post-process if (OS.IsLinux) { string assetsPath = profilePath; profilePath = profilePath.Substring(0, profilePath.LastIndexOf("/")); // Rename all songs to lowercase foreach (var file in new DirectoryInfo(assetsPath).GetFiles()) { if (file.Name.EndsWith(".ogg") && !File.Exists(file.DirectoryName + "/" + file.Name.ToLower())) { File.Move(file.FullName, file.DirectoryName + "/" + file.Name.ToLower()); } } // Copy AppImage template to here HelperMethods.DirectoryCopy(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/AM2R.AppDir", profilePath + "/AM2R.AppDir/"); // Safety checks, in case the folders don't exist Directory.CreateDirectory(profilePath + "/AM2R.AppDir/usr/bin/"); Directory.CreateDirectory(profilePath + "/AM2R.AppDir/usr/bin/assets/"); // Copy game assets to the appimageDir HelperMethods.DirectoryCopy(assetsPath, profilePath + "/AM2R.AppDir/usr/bin/assets/"); File.Copy(profilePath + "/" + exe, profilePath + "/AM2R.AppDir/usr/bin/" + exe); progress.Report(66); log.Info("Gtk-specific formatting finished."); // Temp save the currentWorkingDirectory and console.error, change it to profilePath and null, call the script, and change it back. string workingDir = Directory.GetCurrentDirectory(); TextWriter cliError = Console.Error; Directory.SetCurrentDirectory(profilePath); Console.SetError(new StreamWriter(Stream.Null)); Environment.SetEnvironmentVariable("ARCH", "x86_64"); Process.Start(CrossPlatformOperations.CURRENTPATH + "/PatchData/utilities/appimagetool-x86_64.AppImage", "-n AM2R.AppDir")?.WaitForExit(); Directory.SetCurrentDirectory(workingDir); Console.SetError(cliError); // Clean files Directory.Delete(profilePath + "/AM2R.AppDir", true); Directory.Delete(assetsPath, true); File.Delete(profilePath + "/" + exe); 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 (OS.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 if they exist if (Directory.Exists(profilePath + "/lang/fonts")) { 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 if (new DirectoryInfo(dataPath).Parent?.Name == "PatchData") { File.Copy(dataPath + "/../profile.xml", profilePath + "/profile.xml"); } else { File.Copy(dataPath + "/profile.xml", profilePath + "/profile.xml"); } // Installed datafiles progress.Report(100); log.Info("Successfully installed profile " + profile.Name + "."); }
/// <summary> /// If <see cref="customEnvVarTextBox"/> has lost focus, we write its text to the config. /// </summary> private void CustomEnvVarTextBoxLostFocus(object sender, EventArgs e) { log.Info("Custom Environment variables have been set to \"" + customEnvVarTextBox.Text + "\"."); CrossPlatformOperations.WriteToConfig("CustomEnvVar", customEnvVarTextBox.Text); }
/// <summary> /// Loads valid profile entries and reloads the necessary UI components. /// </summary> private void LoadProfilesAndAdjustLists() { // Reset loaded profiles profileDropDown.Items.Clear(); profileList.Clear(); profileIndex = null; // Load the profileList profileList = Profile.LoadProfiles(); // Add profile names to the profileDropDown foreach (ProfileXML profile in profileList) { // Archive version notes if (!profile.Installable) { if (profile.Name.Contains("Community Updates")) { profile.ProfileNotes = Text.ArchiveNotesCommunityUpdates; } else { profile.ProfileNotes = Text.ArchiveNotesMods + "\n\n" + profile.ProfileNotes; } } profileDropDown.Items.Add(profile.Name); } // Read the value from the config string profIndexString = CrossPlatformOperations.ReadFromConfig("ProfileIndex"); // Check if either no profile was found or the setting says that the last current profile didn't exist if (profileDropDown.Items.Count == 0) { profileIndex = null; } else { // We know that profiles exist at this point, so we're going to point it to 0 instead so the following code doesn't fail if (profIndexString == "null") { profIndexString = "0"; } // We parse from the settings, and check if profiles got deleted from the last time the launcher has been selected. if yes, we revert the last selection to 0; int intParseResult = Int32.Parse(profIndexString); profileIndex = intParseResult; if (profileIndex >= profileDropDown.Items.Count) { profileIndex = 0; } profileDropDown.SelectedIndex = profileIndex.Value; } // Update stored profiles in the Profile Settings tab modSettingsProfileDropDown.Items.Clear(); modSettingsProfileDropDown.Items.AddRange(profileDropDown.Items); modSettingsProfileDropDown.SelectedIndex = profileDropDown.Items.Count != 0 ? 0 : -1; // Refresh the author and version label on the main tab if (profileList.Count > 0) { profileAuthorLabel.Text = Text.Author + " " + profileList[profileDropDown.SelectedIndex].Author; profileVersionLabel.Text = Text.VersionLabel + " " + profileList[profileDropDown.SelectedIndex].Version; } log.Info("Reloading UI components after loading successful."); UpdateStateMachine(); }
/// <summary>Gets called when <see cref="hqMusicAndroidCheck"/> gets clicked and writes its new value to the config.</summary> private void HqMusicAndroidCheckChanged(object sender, EventArgs e) { log.Info("Android HQ Music option has been changed to " + hqMusicAndroidCheck.Checked); CrossPlatformOperations.WriteToConfig("MusicHQAndroid", hqMusicAndroidCheck.Checked); }
/// <summary>Gets called when <see cref="autoUpdateLauncherCheck"/> gets clicked and writes its new value to the config.</summary> private void AutoUpdateLauncherCheckChanged(object sender, EventArgs e) { log.Info("Auto Update Launcher has been set to " + autoUpdateAM2RCheck.Checked + "."); CrossPlatformOperations.WriteToConfig("AutoUpdateLauncher", (bool)autoUpdateAM2RCheck.Checked); }
/// <summary>Gets called when user selects a different item from <see cref="languageDropDown"/> and writes that to the config.</summary> private void LanguageDropDownSelectedIndexChanged(object sender, EventArgs e) { log.Info("languageDropDown.SelectedIndex has been changed to " + languageDropDown.SelectedIndex + "."); CrossPlatformOperations.WriteToConfig("Language", languageDropDown.SelectedIndex == 0 ? "Default" : languageDropDown.Items[languageDropDown.SelectedIndex].Text); }
/// <summary> /// Does a bunch of stuff, depending on the current state of <see cref="updateState"/>. /// </summary> private async void PlayButtonClickEvent(object sender, EventArgs e) { // State Check UpdateStateMachine(); // Check if 1.1 is installed by forcing invalidation Profile.Is11Installed(true); switch (updateState) { #region Download case PlayButtonState.Download: log.Info("Attempting to clone repository " + currentMirror + "..."); bool successful = true; // Update playButton states SetPlayButtonState(PlayButtonState.Downloading); // Enable progressBar progressBar.Visible = true; progressLabel.Visible = true; progressBar.Value = 0; // Set up progressBar update method var c = new CloneOptions { OnTransferProgress = TransferProgressHandlerMethod }; // Everything after this is on a different thread, so the rest of the launcher isn't locked up. try { if (Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/PatchData")) { log.Info("PatchData directory already exists, cleaning up..."); HelperMethods.DeleteDirectory(CrossPlatformOperations.CURRENTPATH + "/PatchData"); } await Task.Run(() => Repository.Clone(currentMirror, CrossPlatformOperations.CURRENTPATH + "/PatchData", c)); } catch (UserCancelledException) { // We deliberately cancelled this! successful = false; } catch (LibGit2SharpException ex) // This is for any exceptions from libgit { // Libgit2sharp error messages are always in english! if (ex.Message.ToLower().Contains("failed to send request") || ex.Message.ToLower().Contains("connection with the server was terminated") || ex.Message.ToLower().Contains("failed to resolve address")) { log.Error("Internet connection dropped while attempting to clone repository" + currentMirror + "!"); MessageBox.Show(this, Text.InternetConnectionDrop, Text.WarningWindowTitle, MessageBoxType.Warning); } else { log.Error("LibGit2SharpException: " + ex.Message + "\n*****Stack Trace*****\n\n" + ex.StackTrace); MessageBox.Show(this, ex.Message + "\n*****Stack Trace*****\n\n" + ex.StackTrace, Text.ErrorWindowTitle, MessageBoxType.Error); if (Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/PatchData")) { HelperMethods.DeleteDirectory(CrossPlatformOperations.CURRENTPATH + "/PatchData"); } } successful = false; } catch (Exception ex) // This is if somehow any other exception might get thrown as well. { log.Error(ex.Message + "\n*****Stack Trace*****\n\n" + ex.StackTrace); MessageBox.Show(this, ex.Message + "\n*****Stack Trace*****\n\n" + ex.StackTrace, Text.ErrorWindowTitle, MessageBoxType.Error); if (Directory.Exists(CrossPlatformOperations.CURRENTPATH + " / PatchData")) { HelperMethods.DeleteDirectory(CrossPlatformOperations.CURRENTPATH + "/PatchData"); } successful = false; } log.Info("Repository clone attempt finished " + (successful ? "successfully." : "unsuccessfully.")); currentGitObject = 0; // Reset progressBar after clone is finished progressLabel.Visible = false; progressLabel.Text = ""; progressBar.Visible = false; progressBar.Value = 0; // Just need to switch this to anything that isn't an "active" state so SetUpdateState() actually does something SetPlayButtonState(PlayButtonState.Install); // This needs to be run BEFORE the state check so that the Mod Settings tab doesn't weird out LoadProfilesAndAdjustLists(); // Do a state check UpdateStateMachine(); break; #endregion #region Downloading case PlayButtonState.Downloading: var result = MessageBox.Show(this, Text.CloseOnCloningText, Text.WarningWindowTitle, MessageBoxButtons.YesNo, MessageBoxType.Warning, MessageBoxDefaultButton.No); if (result != DialogResult.Yes) { return; } log.Info("User cancelled download!"); isGitProcessGettingCancelled = true; // We don't need to delete any folders here, the cancelled gitClone will do that automatically for us :) // But we should probably wait a bit before proceeding, since cleanup can take a while Thread.Sleep(1000); isGitProcessGettingCancelled = false; break; #endregion #region Select11 case PlayButtonState.Select11: log.Info("Requesting user input for AM2R_11.zip..."); OpenFileDialog fileFinder = GetSingleZipDialog(Text.Select11FileDialog); if (fileFinder.ShowDialog(this) != DialogResult.Ok) { log.Info("User cancelled the selection."); return; } // Default filename is whitespace if (String.IsNullOrWhiteSpace(fileFinder.FileName)) { log.Error("User did not supply valid input. Cancelling import."); return; } // If either a directory was selected or the file somehow went missing, cancel if (!File.Exists(fileFinder.FileName)) { log.Error("Selected AM2R_11.zip file not found! Cancelling import."); break; } IsZipAM2R11ReturnCodes errorCode = Profile.CheckIfZipIsAM2R11(fileFinder.FileName); if (errorCode != IsZipAM2R11ReturnCodes.Successful) { log.Error("User tried to input invalid AM2R_11.zip file (" + errorCode + "). Cancelling import."); MessageBox.Show(this, Text.ZipIsNotAM2R11 + "\n\nError Code: " + errorCode, Text.ErrorWindowTitle, MessageBoxType.Error); return; } // We check if it exists first, because someone coughDRUIDcough might've copied it into here while on the showDialog if (fileFinder.FileName != CrossPlatformOperations.CURRENTPATH + "/AM2R_11.zip") { File.Copy(fileFinder.FileName, CrossPlatformOperations.CURRENTPATH + "/AM2R_11.zip"); } log.Info("AM2R_11.zip successfully imported."); UpdateStateMachine(); break; #endregion #region Install case PlayButtonState.Install: progressBar.Visible = true; progressBar.Value = 0; SetPlayButtonState(PlayButtonState.Installing); // Make sure the main interface state machines properly UpdateApkState(); UpdateProfileState(); // If the file cannot be launched due to anti-virus shenanigans or any other reason, we try catch here try { // Check if xdelta is installed on unix and exit if not if (OS.IsUnix && !CrossPlatformOperations.CheckIfXdeltaIsInstalled()) { MessageBox.Show(this, Text.XdeltaNotFound, Text.WarningWindowTitle, MessageBoxButtons.OK); SetPlayButtonState(PlayButtonState.Install); UpdateStateMachine(); log.Error("Xdelta not found. Aborting installing a profile..."); return; } var progressIndicator = new Progress <int>(UpdateProgressBar); bool useHqMusic = hqMusicPCCheck.Checked.Value; await Task.Run(() => Profile.InstallProfile(profileList[profileIndex.Value], useHqMusic, progressIndicator)); // This is just for visuals because the average windows end user will ask why it doesn't go to the end otherwise. if (OS.IsWindows) { Thread.Sleep(500); } } catch (Exception ex) { log.Error(ex.Message + "\n*****Stack Trace*****\n\n" + ex.StackTrace); MessageBox.Show(this, ex.Message + "\n*****Stack Trace*****\n\n" + ex.StackTrace, Text.ErrorWindowTitle, MessageBoxType.Error); } progressBar.Visible = false; progressBar.Value = 0; // Just need to switch this to anything that isn't an "active" state so SetUpdateState() actually does something SetPlayButtonState(PlayButtonState.Play); UpdateStateMachine(); break; #endregion #region Play case PlayButtonState.Play: if (!IsProfileIndexValid()) { return; } ProfileXML profile = profileList[profileIndex.Value]; Visible = false; SetPlayButtonState(PlayButtonState.Playing); // Make sure the main interface state machines properly UpdateApkState(); UpdateProfileState(); this.ShowInTaskbar = false; trayIndicator.Visible = true; WindowState windowStateBeforeLaunching = this.WindowState; Minimize(); string envVarText = customEnvVarTextBox?.Text; bool createDebugLogs = profileDebugLogCheck.Checked.Value; await Task.Run(() => Profile.RunGame(profile, createDebugLogs, envVarText)); this.ShowInTaskbar = true; trayIndicator.Visible = false; Show(); BringToFront(); Visible = true; WindowState = windowStateBeforeLaunching; SetPlayButtonState(PlayButtonState.Play); UpdateStateMachine(); break; #endregion default: throw new NotImplementedException("Encountered invalid update state: " + updateState + "!"); } }
/// <summary> /// Creates an APK of the selected <paramref name="profile"/>. /// </summary> /// <param name="profile"><see cref="ProfileXML"/> to be compiled into an APK.</param> /// <param name="useHqMusic">Wether to create the APK with high quality music or not.</param> /// <param name="progress">Provides the current progress of this method.</param> public static void CreateAPK(ProfileXML profile, bool useHqMusic, IProgress <int> progress) { // Overall safety check just in case of bad situations if (!profile.SupportsAndroid) { progress.Report(100); return; } log.Info("Creating Android APK for profile " + profile.Name + "."); // Create working dir after some cleanup string apktoolPath = CrossPlatformOperations.CURRENTPATH + "/PatchData/utilities/android/apktool.jar", uberPath = CrossPlatformOperations.CURRENTPATH + "/PatchData/utilities/android/uber-apk-signer.jar", tempDir = new DirectoryInfo(CrossPlatformOperations.CURRENTPATH + "/temp").FullName, dataPath = CrossPlatformOperations.CURRENTPATH + profile.DataPath; if (Directory.Exists(tempDir)) { Directory.Delete(tempDir, true); } Directory.CreateDirectory(tempDir); log.Info("Cleanup, variables, and working directory created."); progress.Report(14); // Decompile AM2RWrapper.apk CrossPlatformOperations.RunJavaJar("\"" + apktoolPath + "\" d \"" + dataPath + "/android/AM2RWrapper.apk\"", tempDir); log.Info("AM2RWrapper decompiled."); progress.Report(28); // Add datafiles: 1.1, new datafiles, hq music, am2r.ini string workingDir = tempDir + "/AM2RWrapper/assets"; ZipFile.ExtractToDirectory(CrossPlatformOperations.CURRENTPATH + "/AM2R_11.zip", workingDir); HelperMethods.DirectoryCopy(dataPath + "/files_to_copy", workingDir); if (useHqMusic) { HelperMethods.DirectoryCopy(CrossPlatformOperations.CURRENTPATH + "/PatchData/data/HDR_HQ_in-game_music", workingDir); } // Yes, I'm aware this is dumb. If you've got any better ideas for how to copy a seemingly randomly named .ini from this folder to the APK, please let me know. foreach (FileInfo file in new DirectoryInfo(dataPath).GetFiles().Where(f => f.Name.EndsWith("ini"))) { File.Copy(file.FullName, workingDir + "/" + file.Name); } log.Info("AM2R_11.zip extracted and datafiles copied into AM2RWrapper."); progress.Report(42); // Patch data.win to game.droid CrossPlatformOperations.ApplyXdeltaPatch(workingDir + "/data.win", dataPath + "/droid.xdelta", workingDir + "/game.droid"); log.Info("game.droid successfully patched."); progress.Report(56); // Delete unnecessary files File.Delete(workingDir + "/AM2R.exe"); File.Delete(workingDir + "/D3DX9_43.dll"); File.Delete(workingDir + "/explanations.txt"); File.Delete(workingDir + "/modifiers.ini"); File.Delete(workingDir + "/readme.txt"); File.Delete(workingDir + "/data.win"); Directory.Delete(workingDir + "/mods", true); Directory.Delete(workingDir + "/lang/headers", true); if (OS.IsLinux) { File.Delete(workingDir + "/icon.png"); } // Modify apktool.yml to NOT compress ogg files string apktoolText = File.ReadAllText(workingDir + "/../apktool.yml"); apktoolText = apktoolText.Replace("doNotCompress:", "doNotCompress:\n- ogg"); File.WriteAllText(workingDir + "/../apktool.yml", apktoolText); log.Info("Unnecessary files removed, apktool.yml modified to prevent ogg compression."); progress.Report(70); // Rebuild APK CrossPlatformOperations.RunJavaJar("\"" + apktoolPath + "\" b AM2RWrapper -o \"" + profile.Name + ".apk\"", tempDir); log.Info("AM2RWrapper rebuilt into " + profile.Name + ".apk."); progress.Report(84); // Debug-sign APK CrossPlatformOperations.RunJavaJar("\"" + uberPath + "\" -a \"" + profile.Name + ".apk\"", tempDir); // Extra file cleanup File.Copy(tempDir + "/" + profile.Name + "-aligned-debugSigned.apk", CrossPlatformOperations.CURRENTPATH + "/" + profile.Name + ".apk", true); log.Info(profile.Name + ".apk signed and moved to " + CrossPlatformOperations.CURRENTPATH + "/" + profile.Name + ".apk."); HelperMethods.DeleteDirectory(tempDir); // Done progress.Report(100); log.Info("Successfully created Android APK for profile " + profile.Name + "."); CrossPlatformOperations.OpenFolderAndSelectFile(CrossPlatformOperations.CURRENTPATH + "/" + profile.Name + ".apk"); }
/// <summary> /// Gets called when <see cref="profileDebugLogCheck"/> gets clicked, and writes it's new value to the config. /// </summary> private void ProfileDebugLogCheckedChanged(object sender, EventArgs e) { log.Info("Create Game Debug Logs option has been set to " + profileDebugLogCheck.Checked + "."); CrossPlatformOperations.WriteToConfig("ProfileDebugLog", profileDebugLogCheck.Checked); }
public MainForm() { // Exit if we're already running the AM2RLauncher // Thanks, StackOverflow! https://stackoverflow.com/questions/184084/how-to-force-c-sharp-net-app-to-run-only-one-instance-in-windows if (!singleInstance) { // If on Windows, set the original app to the foreground window to prevent confusion if (OS.IsWindows) { Process current = Process.GetCurrentProcess(); Process process = Process.GetProcessesByName(current.ProcessName).First(p => p.Id == current.Id); if (process != null) { Core.Core.SetForegroundWindow(process.MainWindowHandle); } } Environment.Exit(0); } log.Info("Mutex check passed. Entering main thread."); log.Info("Current Launcher Version: " + VERSION); log.Info("Current Platform-ID is: " + Platform.ID); log.Info("Current OS is: " + OS.Name); // Set the Current Directory to the path the Launcher is located. Fixes some relative path issues. Environment.CurrentDirectory = CrossPlatformOperations.CURRENTPATH; log.Info("Set Launcher CWD to " + Environment.CurrentDirectory); // But log actual folder location nonetheless log.Info("Actual Launcher location: " + Path.GetDirectoryName(AppDomain.CurrentDomain.BaseDirectory)); // Set the language to what User wanted or choose local language string userLanguage = CrossPlatformOperations.ReadFromConfig("Language").ToLower(); if (!userLanguage.Equals("default")) { Thread.CurrentThread.CurrentUICulture = CultureInfo.GetCultures(CultureTypes.AllCultures).First(c => c.NativeName.ToLower().Contains(userLanguage)); } log.Info("Language has been set to: " + Thread.CurrentThread.CurrentUICulture.EnglishName); #region VARIABLE INITIALIZATION log.Info("Beginning UI initialization..."); Bitmap am2rIcon = new Bitmap(AM2RLauncher.Properties.Resources.AM2RIcon); // System tray indicator ButtonMenuItem showButton = new ButtonMenuItem { Text = Text.TrayButtonShow }; trayIndicator = new TrayIndicator { Menu = new ContextMenu(showButton), Title = "AM2RLauncher", Visible = false, Image = am2rIcon }; // Create menubar with defaults for mac if (OS.IsMac) { Menu = new MenuBar(); } // Create array from validCount profileList = new List <ProfileXML>(); //TODO: whenever profileDropDown gets rewritten to use a datastore, scrap this profileNames = new List <ListItem>(); foreach (var profile in profileList) { profileNames.Add(profile.Name); } // Custom splash texts string splash = Splash.GetSplash(); log.Info("Randomly chosen splash: " + splash); Font smallButtonFont = new Font(SystemFont.Default, 10); // Create mirror list - eventually this should be platform specific! // We do this as a List<Uri> so we can add more dynamically on user input... if necessary. mirrorList = CrossPlatformOperations.GenerateMirrorList(); // Create mirror list // We do this as a list<listItem> for 1) make this dynamic and 2) make ETO happy mirrorDescriptionList = new List <ListItem>(); // Add each entry dynamically instead of harcoding it to two. If we have neither a github or gitlab mirror, we use the mirror itself as text foreach (var mirror in mirrorList) { string text = mirror; if (text.Contains("github.com")) { text = Text.MirrorGithubText; } else if (text.Contains("gitlab.com")) { text = Text.MirrorGitlabText; } mirrorDescriptionList.Add(new ListItem() { Key = mirror, Text = text }); } #endregion Icon = new Icon(1f, am2rIcon); Title = "AM2RLauncher " + VERSION + ": " + splash; MinimumSize = new Size(500, 400); // TODO: for some reason, this sometimes doesn't work on Linux. Was reported at eto, stays here until its fixed ClientSize = new Size(Int32.Parse(CrossPlatformOperations.ReadFromConfig("Width")), Int32.Parse(CrossPlatformOperations.ReadFromConfig("Height"))); if (ClientSize.Width < 500) { ClientSize = new Size(500, ClientSize.Height); } if (ClientSize.Height < 400) { ClientSize = new Size(ClientSize.Width, 400); } log.Info("Start the launcher with Size: " + ClientSize.Width + ", " + ClientSize.Height); if (Boolean.Parse(CrossPlatformOperations.ReadFromConfig("IsMaximized"))) { Maximize(); } drawable = new Drawable { BackgroundColor = colBGNoAlpha }; // Drawable paint event drawable.Paint += DrawablePaintEvent; // Some systems don't call the paintEvent by default and only do so after actual resizing if (OS.IsMac) { LoadComplete += (sender, e) => { Size = new Size(Size.Width + 1, Size.Height); Size = new Size(Size.Width - 1, Size.Height); } } ; #region MAIN WINDOW // Center buttons/interface panel var centerInterface = new DynamicLayout(); // PLAY button playButton = new ColorButton { ToolTip = "", BackgroundColorHover = colBGHover, Height = 40, Width = 250, TextColor = colGreen, TextColorDisabled = colInactive, BackgroundColor = colBG, FrameColor = colGreen, FrameColorDisabled = colInactive }; UpdateStateMachine(); SetPlayButtonState(updateState); centerInterface.AddRow(playButton); // 2px spacer between playButton and apkButton (Windows only) if (OS.IsWindows) { centerInterface.AddRow(new Label { BackgroundColor = colBG, Height = 2 }); } // APK button apkButton = new ColorButton { Text = Text.CreateAPK, Height = 40, Width = 250, TextColor = colGreen, BackgroundColor = colBG, FrameColor = colGreen, BackgroundColorHover = colBGHover }; centerInterface.AddRow(apkButton); progressBar = new ProgressBar { Visible = false, Height = 15 }; // 4px spacer between APK button and progressBar (Windows only) if (OS.IsWindows) { centerInterface.AddRow(new Label { BackgroundColor = colBG, Height = 4 }); } centerInterface.AddRow(progressBar); progressLabel = new Label { BackgroundColor = colBG, Height = 15, Text = "", TextColor = colGreen, Visible = false }; centerInterface.AddRow(progressLabel); // 3px spacer between progressBar and profile label (Windows only) if (OS.IsWindows) { centerInterface.AddRow(new Label { BackgroundColor = colBG, Height = 3 }); } profileLabel = new Label { BackgroundColor = colBG, Height = 15, Text = Text.CurrentProfile, TextColor = colGreen }; centerInterface.AddRow(profileLabel); // Profiles dropdown // 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 = OS.IsWindows ? colBGNoAlpha : new Color() }; // In order to not have conflicting theming, we just always respect the users theme for dropdown on GTK. if (OS.IsLinux) { profileDropDown = new DropDown(); } profileDropDown.Items.AddRange(profileNames); // It's actually more comfortable if it's outside, because of GTK shenanigans centerInterface.AddRow(profileDropDown); // Profiles label profileAuthorLabel = new Label { BackgroundColor = colBG, Height = 16, Text = Text.Author + " ", TextColor = colGreen }; centerInterface.AddRow(profileAuthorLabel); profileVersionLabel = new Label { BackgroundColor = colBG, Height = 16, Text = Text.VersionLabel + " ", TextColor = colGreen }; centerInterface.AddRow(profileVersionLabel); saveWarningLabel = new Label { Visible = false, BackgroundColor = colBG, Width = 20, Height = 55, Text = Text.SaveLocationWarning, TextColor = colRed }; centerInterface.AddRow(saveWarningLabel); // Social buttons Bitmap redditIcon = new Bitmap(Resources.redditIcon48); var redditButton = new ImageButton { ToolTip = Text.RedditToolTip, Image = redditIcon }; redditButton.Click += (sender, e) => CrossPlatformOperations.OpenURL("https://www.reddit.com/r/AM2R"); Bitmap githubIcon = new Bitmap(Resources.githubIcon48); var githubButton = new ImageButton { ToolTip = Text.GithubToolTip, Image = githubIcon }; githubButton.Click += (sender, e) => CrossPlatformOperations.OpenURL("https://www.github.com/AM2R-Community-Developers"); Bitmap youtubeIcon = new Bitmap(Resources.youtubeIcon48); var youtubeButton = new ImageButton { ToolTip = Text.YoutubeToolTip, Image = youtubeIcon }; youtubeButton.Click += (sender, e) => CrossPlatformOperations.OpenURL("https://www.youtube.com/c/AM2RCommunityUpdates"); Bitmap discordIcon = new Bitmap(Resources.discordIcon48); var discordButton = new ImageButton { ToolTip = Text.DiscordToolTip, Image = discordIcon }; discordButton.Click += (sender, e) => CrossPlatformOperations.OpenURL("https://discord.gg/nk7UYPbd5u"); // Social button panel var socialPanel = new DynamicLayout(); socialPanel.BeginVertical(); socialPanel.AddRow(redditButton); socialPanel.AddRow(githubButton); socialPanel.AddRow(youtubeButton); socialPanel.AddRow(discordButton); socialPanel.EndVertical(); // Version number label Label versionLabel = new Label { Text = "v" + VERSION + (isThisRunningFromWine ? "-WINE" : ""), Width = 48, TextAlignment = TextAlignment.Right, TextColor = colGreen, Font = new Font(SystemFont.Default, 12) }; // Tie everything together var mainLayout = new DynamicLayout(); mainLayout.BeginHorizontal(); mainLayout.AddColumn(null, socialPanel); mainLayout.AddSpace(); mainLayout.AddColumn(null, centerInterface, null); mainLayout.AddSpace(); // Yes, I'm hardcoding this string. Linux users can english. mainLayout.AddColumn(versionLabel, isThisRunningFromWine ? new Label { Text = "Unsupported", TextColor = colRed, TextAlignment = TextAlignment.Right } : null); drawable.Content = mainLayout; #endregion #region TABS #region MAIN PAGE // [MAIN PAGE] TabPage mainPage = new TabPage { BackgroundColor = colBGNoAlpha, Text = Text.PlayTab, Content = drawable }; #endregion #region CHANGELOG PAGE // [CHANGELOG] Uri changelogUri = new Uri("https://am2r-community-developers.github.io/DistributionCenter/changelog.html"); WebView changelogWebView = new WebView { Url = changelogUri }; if (OS.IsUnix && !isInternetThere) { changelogWebView = new WebView(); } Label changelogNoConnectionLabel = new Label { Text = Text.NoInternetConnection, TextColor = colGreen }; TabPage changelogPage = new TabPage { BackgroundColor = colBGNoAlpha, Text = Text.ChangelogTab, Content = new TableLayout { Rows = { changelogWebView } } }; #endregion #region NEWS PAGE // [NEWS] Uri newsUri = new Uri("https://am2r-community-developers.github.io/DistributionCenter/news.html"); WebView newsWebView = new WebView { Url = newsUri }; //TODO: why exactly is this check necessary? if (OS.IsUnix && !isInternetThere) { newsWebView = new WebView(); } Label newsNoConnectionLabel = new Label { Text = Text.NoInternetConnection, TextColor = colGreen }; TabPage newsPage = new TabPage { Text = Text.NewsTab, BackgroundColor = colBGNoAlpha, Content = new TableLayout { Rows = { newsWebView } } }; //TODO: this is hack because on linux / mac the other way doesn't work. eto issue? if (OS.IsUnix && !isInternetThere) { changelogPage.Content = new TableLayout { Rows = { null, changelogNoConnectionLabel, null } }; newsPage.Content = new TableLayout { Rows = { null, newsNoConnectionLabel, null } }; } #endregion #region SETTINGS PAGE // [LAUNCHER SETTINGS] DynamicLayout settingsLayout = new DynamicLayout(); // LanguageLabel Label languageLabel = new Label { Text = Text.LanguageNotice, TextColor = colGreen }; // Language DropDown menu List <ListItem> languageList = new List <ListItem> { Text.SystemLanguage, "Deutsch", "English", "Español", "Français", "Italiano", "Português", "Русский", "日本語", "中文(简体)" }; languageDropDown = new DropDown { TextColor = colGreen, BackgroundColor = OS.IsWindows ? colBGNoAlpha : new Color() }; if (OS.IsLinux) { languageDropDown = new DropDown(); } languageDropDown.Items.AddRange(languageList); var tmpLanguage = CrossPlatformOperations.ReadFromConfig("Language"); languageDropDown.SelectedIndex = tmpLanguage == "Default" ? 0 : languageDropDown.Items.IndexOf(languageDropDown.Items.FirstOrDefault(x => x.Text.Equals(tmpLanguage))); if (languageDropDown.SelectedIndex == -1) { log.Info("User has tried to use " + tmpLanguage + " as a Language, but it was not found. Reverting to System Language"); languageDropDown.SelectedIndex = 0; } // autoUpdateAM2R checkbox autoUpdateAM2RCheck = new CheckBox { Checked = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("AutoUpdateAM2R")), Text = Text.AutoUpdateAM2R, TextColor = colGreen }; // autoUpdateLauncher checkbox autoUpdateLauncherCheck = new CheckBox { Checked = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("AutoUpdateLauncher")), Text = Text.AutoUpdateLauncher, TextColor = colGreen }; // HQ music, PC hqMusicPCCheck = new CheckBox { Checked = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("MusicHQPC")), Text = Text.HighQualityPC, TextColor = colGreen }; // HQ music, Android hqMusicAndroidCheck = new CheckBox { Checked = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("MusicHQAndroid")), Text = Text.HighQualityAndroid, TextColor = colGreen }; // Create game debug logs profileDebugLogCheck = new CheckBox { Checked = bool.Parse(CrossPlatformOperations.ReadFromConfig("ProfileDebugLog")), Text = Text.ProfileDebugCheckBox, TextColor = colGreen }; // Custom environment variables label Label customEnvVarLabel = new Label(); if (OS.IsLinux) { customEnvVarLabel = new Label { Text = Text.CustomEnvVarLabel, TextColor = colGreen }; } // Custom environment variables textbox customEnvVarTextBox = null; if (OS.IsLinux) { customEnvVarTextBox = new TextBox { Text = CrossPlatformOperations.ReadFromConfig("CustomEnvVar"), BackgroundColor = colBGNoAlpha, TextColor = colGreen }; } // Mirror list mirrorLabel = new Label { Text = Text.DownloadSource, TextColor = colGreen }; mirrorDropDown = new DropDown { TextColor = colGreen, BackgroundColor = OS.IsWindows ? colBGNoAlpha : new Color() }; if (OS.IsLinux) { mirrorDropDown = new DropDown(); } mirrorDropDown.Items.AddRange(mirrorDescriptionList); // As above, find a way to get this inside the dropDown definition mirrorIndex = (Int32.Parse(CrossPlatformOperations.ReadFromConfig("MirrorIndex")) < mirrorDropDown.Items.Count) ? Int32.Parse(CrossPlatformOperations.ReadFromConfig("MirrorIndex")) : 0; mirrorDropDown.SelectedIndex = mirrorIndex; currentMirror = mirrorList[mirrorDropDown.SelectedIndex]; // Custom mirror customMirrorCheck = new CheckBox { Checked = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("CustomMirrorEnabled")), Text = Text.CustomMirrorCheck, TextColor = colGreen }; customMirrorTextBox = new TextBox { Text = CrossPlatformOperations.ReadFromConfig("CustomMirrorText"), BackgroundColor = colBGNoAlpha, TextColor = colGreen }; EnableMirrorControlsAccordingly(); settingsLayout.BeginHorizontal(); settingsLayout.AddSpace(); settingsLayout.AddColumn(null, languageLabel, languageDropDown, autoUpdateAM2RCheck, autoUpdateLauncherCheck, hqMusicPCCheck, hqMusicAndroidCheck, profileDebugLogCheck, customEnvVarLabel, (Control)customEnvVarTextBox ?? new Label(), mirrorLabel, mirrorDropDown, customMirrorCheck, customMirrorTextBox, null); settingsLayout.AddSpace(); TabPage settingsPage = new TabPage { BackgroundColor = colBGNoAlpha, Content = settingsLayout, Text = Text.LauncherSettingsTab }; #endregion #region MODSETTINGS PAGE // [MOD SETTINGS] DynamicLayout modSettingsLayout = new DynamicLayout(); addModButton = new ColorButton { ToolTip = null, Text = Text.AddNewMod, Font = smallButtonFont, Height = 30, Width = 275, TextColor = colGreen, BackgroundColor = colBG, FrameColor = colGreen, BackgroundColorHover = colBGHover }; Label modSpacer = new Label { Height = 14 }; settingsProfileLabel = new Label { Text = Text.CurrentProfile, TextColor = colGreen, Width = 275 }; modSettingsProfileDropDown = new DropDown { TextColor = colGreen, BackgroundColor = OS.IsWindows ? colBGNoAlpha : new Color() }; // In order to not have conflicting theming, we just always respect the users theme for dropdown on GTK. if (OS.IsLinux) { modSettingsProfileDropDown = new DropDown(); } modSettingsProfileDropDown.Items.AddRange(profileNames); // It's actually more comfortable if it's outside, because of GTK shenanigans profileButton = new ColorButton { ToolTip = null, Text = Text.OpenProfileFolder, Font = smallButtonFont, Height = 30, Width = 275, TextColor = colGreen, BackgroundColor = colBG, FrameColor = colGreen, BackgroundColorHover = colBGHover }; saveButton = new ColorButton { ToolTip = null, Text = Text.OpenSaveFolder, Font = smallButtonFont, Height = 30, Width = 275, TextColor = colGreen, BackgroundColor = colBG, FrameColor = colGreen, BackgroundColorHover = colBGHover }; updateModButton = new ColorButton { ToolTip = null, Text = Text.UpdateModButtonText, Font = smallButtonFont, Height = 30, Width = 275, TextColor = colGreen, BackgroundColor = colBG, FrameColor = colGreen, BackgroundColorHover = colBGHover }; deleteModButton = new ColorButton { ToolTip = null, Text = Text.DeleteModButtonText, Font = smallButtonFont, Height = 30, Width = 275, TextColor = colGreen, BackgroundColor = colBG, FrameColor = colGreen, BackgroundColorHover = colBGHover }; profileNotesTextArea = new TextArea { ReadOnly = true, BackgroundColor = colBGNoAlpha, TextColor = colInactive, SpellCheck = false, Width = 275, Height = 150, Text = Text.ProfileNotes }; modSettingsLayout.BeginHorizontal(); modSettingsLayout.AddSpace(); modSettingsLayout.AddColumn(null, addModButton, modSpacer, settingsProfileLabel, modSettingsProfileDropDown, profileButton, saveButton, updateModButton, deleteModButton, profileNotesTextArea, null); modSettingsLayout.AddSpace(); TabPage modSettingsPage = new TabPage { BackgroundColor = colBGNoAlpha, Content = modSettingsLayout, Text = Text.ModSettingsTab }; #endregion #endregion Content = new TabControl { Pages = { mainPage, changelogPage, newsPage, settingsPage, modSettingsPage } }; #region EVENTS log.Info("All UI objects have been initialized, UI has been set up."); log.Info("Beginning event linkage..."); Closing += MainformClosing; showButton.Click += ShowButtonClick; profileDropDown.SelectedIndexChanged += ProfileDropDownSelectedIndexChanged; languageDropDown.SelectedIndexChanged += LanguageDropDownSelectedIndexChanged; autoUpdateAM2RCheck.CheckedChanged += AutoUpdateAM2RCheckChanged; autoUpdateLauncherCheck.CheckedChanged += AutoUpdateLauncherCheckChanged; hqMusicAndroidCheck.CheckedChanged += HqMusicAndroidCheckChanged; hqMusicPCCheck.CheckedChanged += HqMusicPCCheckChanged; customMirrorCheck.CheckedChanged += CustomMirrorCheckChanged; apkButton.Click += ApkButtonClickEvent; apkButton.LoadComplete += (sender, e) => UpdateApkState(); profileDropDown.LoadComplete += (sender, e) => UpdateProfileState(); playButton.Click += PlayButtonClickEvent; playButton.LoadComplete += PlayButtonLoadComplete; customMirrorTextBox.LostFocus += CustomMirrorTextBoxLostFocus; mirrorDropDown.SelectedIndexChanged += MirrorDropDownSelectedIndexChanged; modSettingsLayout.LoadComplete += ProfileLayoutLoadComplete; addModButton.Click += AddModButtonClicked; profileButton.Click += ProfileDataButtonClickEvent; saveButton.Click += SaveButtonClickEvent; modSettingsProfileDropDown.SelectedIndexChanged += ModSettingsProfileDropDownSelectedIndexChanged; deleteModButton.Click += DeleteModButtonClicked; updateModButton.Click += UpdateModButtonClicked; profileDebugLogCheck.CheckedChanged += ProfileDebugLogCheckedChanged; if (OS.IsLinux) { customEnvVarTextBox.LostFocus += CustomEnvVarTextBoxLostFocus; } //TODO: Retest if these now work on mac newsWebView.DocumentLoaded += (sender, e) => ChangeToEmptyPageOnNoInternet(newsPage, newsNoConnectionLabel); changelogWebView.DocumentLoaded += (sender, e) => ChangeToEmptyPageOnNoInternet(changelogPage, changelogNoConnectionLabel); log.Info("Events linked successfully."); #endregion }
/// <summary> /// Performs the entire AM2RLauncher update procedure. /// </summary> public static void Main() { log.Info("Running update check..."); // Update section // Clean old files that have been left if (File.Exists(CrossPlatformOperations.CURRENTPATH + "/AM2RLauncher.bak")) { log.Info("AM2RLauncher.bak detected. Removing file."); File.Delete(CrossPlatformOperations.CURRENTPATH + "/AM2RLauncher.bak"); } if (OS.IsWindows && File.Exists(oldConfigPath)) { log.Info(CrossPlatformOperations.LAUNCHERNAME + ".oldCfg detected. Removing file."); File.Delete(oldConfigPath); } if (OS.IsWindows && Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/oldLib")) { log.Info("Old lib folder detected, removing folder."); Directory.Delete(CrossPlatformOperations.CURRENTPATH + "/oldLib", true); } // Clean up old update libs if (OS.IsWindows && Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/lib")) { foreach (FileInfo file in new DirectoryInfo(CrossPlatformOperations.CURRENTPATH + "/lib").GetFiles()) { if (file.Name.EndsWith(".bak")) { file.Delete(); } } // Do the same for each subdir foreach (DirectoryInfo dir in new DirectoryInfo(CrossPlatformOperations.CURRENTPATH + "/lib").GetDirectories()) { foreach (FileInfo file in dir.GetFiles()) { if (file.Name.EndsWith(".bak")) { file.Delete(); } } } } // Check settings if autoUpdateLauncher is set to true bool autoUpdate = Boolean.Parse(CrossPlatformOperations.ReadFromConfig("AutoUpdateLauncher")); if (autoUpdate) { log.Info("AutoUpdate Launcher set to true!"); // This is supposed to fix the updater throwing an exception on windows 7 and earlier(?) // See this for information: https://stackoverflow.com/q/2859790 and https://stackoverflow.com/a/50977774 if (OS.IsWindows) { ServicePointManager.Expect100Continue = true; ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; } HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://github.com/AM2R-Community-Developers/AM2RLauncher/releases/latest"); HttpWebResponse response; try { response = (HttpWebResponse)request.GetResponse(); } catch (WebException) { log.Error("WebException caught! Displaying MessageBox."); MessageBox.Show(Language.Text.NoInternetConnection); return; } Uri realUri = response.ResponseUri; string onlineVersion = realUri.AbsoluteUri.Substring(realUri.AbsoluteUri.LastIndexOf('/') + 1); bool isCurrentVersionOutdated = false; string[] localVersionArray = VERSION.Split('.'); string[] onlineVersionArray = onlineVersion.Split('.'); for (int i = 0; i < localVersionArray.Length; i++) { int onlineNum = Int32.Parse(onlineVersionArray[i]); int localNum = Int32.Parse(localVersionArray[i]); if (onlineNum > localNum) { isCurrentVersionOutdated = true; break; } if (localNum > onlineNum) { break; } } log.Info((isCurrentVersionOutdated ? "Updating" : "Not Updating") + " from " + VERSION + " to " + onlineVersion); // No new update, exiting if (!isCurrentVersionOutdated) { return; } // For mac, we just show a message box that a new version is available, because I don't want to support it yet. // hardcoded string, since also temporarily until it gets supported one day. if (OS.IsMac) { MessageBox.Show("Your current version is outdated! The newest version is " + onlineVersion + "." + "Please recompile AM2RLauncher again or disable auto-updating"); return; } log.Info("Current version (" + VERSION + ") is outdated! Initiating update for version " + onlineVersion + "."); string tmpUpdatePath = CrossPlatformOperations.CURRENTPATH + "/tmpupdate/"; string zipPath = CrossPlatformOperations.CURRENTPATH + "/launcher.zip"; // Clean tmpupdate if (Directory.Exists(tmpUpdatePath)) { Directory.Delete(tmpUpdatePath, true); } if (!Directory.Exists(tmpUpdatePath)) { Directory.CreateDirectory(tmpUpdatePath); } try { using (var client = new WebClient()) { string platformSuffix = ""; if (OS.IsWindows) { platformSuffix = "_win"; } else if (OS.IsLinux) { platformSuffix = "_lin"; } log.Info("Downloading https://github.com/AM2R-Community-Developers/AM2RLauncher/releases/latest/download/AM2RLauncher_" + onlineVersion + platformSuffix + ".zip to " + zipPath + "."); client.DownloadFile("https://github.com/AM2R-Community-Developers/AM2RLauncher/releases/latest/download/AM2RLauncher_" + onlineVersion + platformSuffix + ".zip", zipPath); log.Info("File successfully downloaded."); } } catch (UnauthorizedAccessException) { log.Error("UnauthorizedAccessException caught! Displaying MessageBox."); MessageBox.Show(Language.Text.UnauthorizedAccessMessage); return; } ZipFile.ExtractToDirectory(zipPath, tmpUpdatePath); log.Info("Updates successfully extracted to " + tmpUpdatePath); File.Delete(zipPath); File.Move(updatePath + "/" + CrossPlatformOperations.LAUNCHERNAME, CrossPlatformOperations.CURRENTPATH + "/AM2RLauncher.bak"); if (OS.IsWindows) { File.Move(CrossPlatformOperations.LAUNCHERNAME + ".config", CrossPlatformOperations.LAUNCHERNAME + ".oldCfg"); } foreach (var file in new DirectoryInfo(tmpUpdatePath).GetFiles()) { log.Info("Moving " + file.FullName + " to " + CrossPlatformOperations.CURRENTPATH + "/" + file.Name); File.Copy(file.FullName, updatePath + "/" + file.Name, true); } // For windows, the actual application is in "AM2RLauncher.dll". Which means, we need to update the lib folder as well. if (OS.IsWindows && Directory.Exists(CrossPlatformOperations.CURRENTPATH + "/lib")) { // So, because Windows behavior is dumb... // Rename all files in lib to *.bak foreach (FileInfo file in new DirectoryInfo(CrossPlatformOperations.CURRENTPATH + "/lib").GetFiles()) { file.CopyTo(file.Directory + "/" + file.Name + ".bak"); } // Do the same for each subdir foreach (DirectoryInfo dir in new DirectoryInfo(CrossPlatformOperations.CURRENTPATH + "/lib").GetDirectories()) { foreach (FileInfo file in dir.GetFiles()) { file.CopyTo(file.Directory + "/" + file.Name + ".bak"); } } // Yes, the above calls could be recursive. No, I can't be bothered to make them as such. if (Directory.Exists(tmpUpdatePath + "lib")) { HelperMethods.DirectoryCopy(tmpUpdatePath + "lib", CrossPlatformOperations.CURRENTPATH + "/lib"); } } Directory.Delete(tmpUpdatePath, true); CrossPlatformOperations.CopyOldConfigToNewConfig(); log.Info("Files extracted. Preparing to restart executable..."); if (OS.IsLinux) { System.Diagnostics.Process.Start("chmod", "+x " + updatePath + "./AM2RLauncher.Gtk"); } System.Diagnostics.Process.Start(updatePath + "/" + CrossPlatformOperations.LAUNCHERNAME); Environment.Exit(0); } else { log.Info("AutoUpdate Launcher set to false. Exiting update check."); } }