diff --git a/src/XIVLauncher.Common/Support/DalamudAndPluginConfigImportExport.cs b/src/XIVLauncher.Common/Support/DalamudAndPluginConfigImportExport.cs new file mode 100644 index 000000000..73e066cda --- /dev/null +++ b/src/XIVLauncher.Common/Support/DalamudAndPluginConfigImportExport.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.IO.Compression; +using System.IO; +using System.Threading.Tasks; +using XIVLauncher.Common.Util; + +namespace XIVLauncher.Common.Support +{ + public class DalamudAndPluginConfigImportExport : ZipMethods + { + public static async Task ExportConfig(string storagePath) + { + // grab any platform-specific details + // there's none to grab currently :) + + // start making our zip + var outFile = new FileInfo(Path.Combine(storagePath, $"XIVLauncher-{DateTime.Now:yyyyMMddhhmmss}.xlconf")); + using var archive = ZipFile.Open(outFile.FullName, ZipArchiveMode.Create); + + // add all the stock XIVLauncher based paths + var accountsListFile = Path.Combine(storagePath, "accountsList.json"); + var dalamudConfigFile = Path.Combine(storagePath, "dalamudConfig.json"); + var dalamudVfsFile = Path.Combine(storagePath, "dalamudVfs.db"); + var pluginConfigsFolder = Path.Combine(storagePath, "pluginConfigs"); + + ZipMethods.AddIfExist(accountsListFile, archive); + ZipMethods.AddIfExist(dalamudConfigFile, archive); + ZipMethods.AddIfExist(dalamudVfsFile, archive); + ZipMethods.AddIfExist(pluginConfigsFolder, archive); + + // add some known special exceptions. It might be better to not build these expectations though + var backupsFolder = Path.Combine(storagePath, "backups"); // Otter plugins + var playerTrackBackupsFolder = Path.Combine(storagePath, "playerTrackBackups"); // PlayerTrack + + ZipMethods.AddIfExist(backupsFolder, archive); + ZipMethods.AddIfExist(playerTrackBackupsFolder, archive); + + // return the folder containing our exported settings + return outFile.FullName; + } + + public static void ImportConfig(string zipFilePath, string storagePath) + { + // grab any platform-specific details + // there's none to grab currently :) + + // TODO: Decide if we're going to alert on overwriting config. + // Right now, Franz decided against it. The user has to intentionally try to use this feature + // Also, .Net Framework is dumb and will explode if we use ZipArchive.ExtractToDirectory() + // and there are any file conflicts. .Net Core doesn't have this issue though and provides + // an override we could have used to do it anyways. Alternatively, we could just delete + // all of the files/folders we'd be restoring to first, but that also feels bad. + + + + var inFile = new FileInfo(zipFilePath); + using var archive = ZipFile.Open(inFile.FullName, ZipArchiveMode.Read); + + // If we weren't on .Net Framework, we could use this... + // ZipFileExtensions.ExtractToDirectory(archive, storagePath, true); + + foreach (var entry in archive.Entries) + { + //var extractPath = storagePath + "\\" + entry.FullName; + var extractPath = Path.Combine(storagePath, entry.FullName); + + + // If we were going to warn about overwriting files, it would go here. + /* + bool promptAlwaysForOverwrite = true; + if (promptAlwaysForOverwrite && File.Exists(extractPath)) + { + // Make some prompt. Overwrite? Yes, Yes to All, Cancel + + if (result == no) break or something. + if (result == yestoall) promptAlwaysForOverwrite = false; + // yes is the default and needs no special handling. + } + */ + if (!Directory.Exists(Path.GetDirectoryName(extractPath))) + { + Directory.CreateDirectory(Path.GetDirectoryName(extractPath)); + } + ZipFileExtensions.ExtractToFile(entry, extractPath, true); + } + } + } +} diff --git a/src/XIVLauncher.Common/Util/ZipMethods.cs b/src/XIVLauncher.Common/Util/ZipMethods.cs new file mode 100644 index 000000000..27f326a3b --- /dev/null +++ b/src/XIVLauncher.Common/Util/ZipMethods.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; + +namespace XIVLauncher.Common.Util +{ + public class ZipMethods + { + + public static void AddIfExist(string entryPath, ZipArchive zip) + { + if (File.Exists(entryPath)) + { + using var stream = File.Open(entryPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); + var entry = zip.CreateEntry(new FileInfo(entryPath).Name); + using var entryStream = entry.Open(); + stream.CopyTo(entryStream); + //zip.CreateEntryFromFile(file.FullName, file.Name); + } + // directory handling solution based on answer from https://stackoverflow.com/a/62797701 + else if (Directory.Exists(entryPath)) + { + var dir = new DirectoryInfo(entryPath); + var folders = new Stack(); + folders.Push(entryPath); + + do + { + var currentFolder = folders.Pop(); + foreach (var filename in Directory.GetFiles(currentFolder)) + { + using var stream = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); + var entry = zip.CreateEntry($"{dir.Name}\\{filename.Substring(entryPath.Length + 1)}"); + using var entryStream = entry.Open(); + stream.CopyTo(entryStream); + //zip.CreateEntryFromFile(filename, $"{dir.Name}\\{filename.Substring(entryPath.Length + 1)}"); + } + foreach (var dirname in Directory.GetDirectories(currentFolder)) + { + folders.Push(dirname); + } + } while (folders.Count > 0); + } + } + } +} diff --git a/src/XIVLauncher/App.xaml.cs b/src/XIVLauncher/App.xaml.cs index d0ec9abd5..8e73419d6 100644 --- a/src/XIVLauncher/App.xaml.cs +++ b/src/XIVLauncher/App.xaml.cs @@ -1,4 +1,5 @@ -using System; +using System; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -59,6 +60,12 @@ public class CmdLineOptions [CommandLine.Option("clientlang", Required = false, HelpText = "Client language to use.")] public ClientLanguage? ClientLanguage { get; set; } + [CommandLine.Option("exportconfig", Required = false, HelpText = "Export a zip of all config")] + public bool DoExportConfig { get; set; } + + [CommandLine.Option("importconfig", Required = false, HelpText = "Import a zip of all config")] + public string DoImportConfig { get; set; } + // We don't care about these, just need it so that the parser doesn't error [CommandLine.Option("squirrel-updated", Hidden = true)] public string SquirrelUpdated { get; set; } @@ -251,6 +258,49 @@ private static void GenerateLocalizables() Environment.Exit(0); } + private static void ExportConfig() + { + try + { + var filename= DalamudAndPluginConfigImportExport.ExportConfig(Paths.RoamingPath); + + MessageBox.Show($"Exported config as {filename}.\n\nXIVLauncher will now exit.", "XIVLauncher", MessageBoxButton.OK, MessageBoxImage.Asterisk); + Process.Start("explorer.exe", $"/select, \"{filename}\""); + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString()); + } + + Environment.Exit(0); + } + + private static void ImportConfig(string zipFile) + { + try + { + var importPromptResult = MessageBox.Show($"XIVLauncher is going to import config from {zipFile}. " + + "This will overwrite any files that already exist.", "XIVLauncher", + MessageBoxButton.OKCancel, MessageBoxImage.Asterisk); + + if (importPromptResult == MessageBoxResult.OK) + { + DalamudAndPluginConfigImportExport.ImportConfig(zipFile, Paths.RoamingPath); + + MessageBox.Show($"Imported config.\n\nXIVLauncher will now exit. Please re-run XIVLauncher.", "XIVLauncher", MessageBoxButton.OK, MessageBoxImage.Asterisk); + Environment.Exit(0); + } + + } + catch (Exception ex) + { + MessageBox.Show(ex.ToString()); + Environment.Exit(0); + } + + + } + private bool _useFullExceptionHandler = false; private void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) @@ -355,6 +405,16 @@ private void App_OnStartup(object sender, StartupEventArgs e) { GenerateLocalizables(); } + + if (CommandLine.DoExportConfig) + { + ExportConfig(); + } + + if (!string.IsNullOrEmpty(CommandLine.DoImportConfig)) + { + ImportConfig(CommandLine.DoImportConfig); + } } catch (Exception ex) { diff --git a/src/XIVLauncher/Resources/Loc/XIVLauncher_Localizable.json b/src/XIVLauncher/Resources/Loc/XIVLauncher_Localizable.json index be2880027..ed28b7ae3 100644 --- a/src/XIVLauncher/Resources/Loc/XIVLauncher_Localizable.json +++ b/src/XIVLauncher/Resources/Loc/XIVLauncher_Localizable.json @@ -93,11 +93,11 @@ }, "NewsDlFailed": { "message": "Could not download news data.", - "description": "MainWindow.b__13_3" + "description": "MainWindow.b__14_5" }, "MaintenanceQueueBootPatch": { "message": "A patch for the official launcher was detected.\nThis usually means that there is a patch for the game as well.\n\nYou will now be logged in.", - "description": "d__22.MoveNext" + "description": "d__23.MoveNext" }, "SettingsGamePatchPathError": { "message": "Game and patch download paths cannot be the same.\nPlease make sure to choose distinct game and patch download paths.", @@ -119,6 +119,30 @@ "message": "Could not contact the server to get the current compatible game version for Dalamud. This might mean that your .NET installation is too old.\nPlease check the Discord for more information.", "description": "SettingsControl.EnableHooksCheckBox_OnChecked" }, + "ConfigImportPrompt1": { + "message": "XIVLauncher is going to import config from the file you selected.\n\n", + "description": "SettingsControl.ImportConfigButton_OnClick" + }, + "ConfigImportPrompt2": { + "message": "\n\nThis will overwrite any files that already exist.\n\nDo you want to proceed?", + "description": "SettingsControl.ImportConfigButton_OnClick" + }, + "ConfigImportPromptTitle": { + "message": "Import confg file?", + "description": "SettingsControl.ImportConfigButton_OnClick" + }, + "ConfigImportRunning": { + "message": "Running config import...", + "description": "SettingsControl.ImportConfigButton_OnClick" + }, + "ConfigImportCancelled": { + "message": "Dalamud and Plugin configuration import cancelled.", + "description": "SettingsControl.ImportConfigButton_OnClick" + }, + "ConfigExportRunning": { + "message": "Running config export...", + "description": "SettingsControl.ExportConfigButton_OnClick" + }, "SteamFtToggleAutoStartWarning": { "message": "To apply this setting, XIVLauncher needs to restart.\nPlease reopen XIVLauncher.", "description": "SettingsControl.IsFreeTrialCheckbox_OnClick" @@ -139,6 +163,14 @@ "message": "Your game install seems to be valid.", "description": "<>c__DisplayClass20_1.b__4" }, + "ConfigImportSuccess": { + "message": "Dalamud and Plugin configuration import completed.", + "description": "<b__1>d.MoveNext" + }, + "ConfigExportSuccess": { + "message": "Exported config as ", + "description": "<b__1>d.MoveNext" + }, "AccountSwitcherSetProfilePic": { "message": "Set profile picture", "description": "AccountSwitcherViewModel.SetupLoc" @@ -457,295 +489,295 @@ }, "DuplicateOtpAfterSuccess": { "message": "This OTP has been already used.\nIt may take up to 30 seconds for a new one.", - "description": "MainWindowViewModel.b__21_0" + "description": "MainWindowViewModel.b__23_0" }, "GameRepairProcessExitRequired1": { "message": "Close the following application to repair the game.", - "description": "<>c.b__25_1" + "description": "<>c.b__27_1" }, "GameRepairProcessExitRequiredPlural": { "message": "Close the following applications to repair the game.", - "description": "<>c.b__25_1" + "description": "<>c.b__27_1" }, "GameUpdateExitRequired1": { "message": "Close the following application to patch the game.", - "description": "<>c.b__34_2" + "description": "<>c.b__36_2" }, "GameUpdateExitRequiredPlural": { "message": "Close the following applications to patch the game.", - "description": "<>c.b__34_2" + "description": "<>c.b__36_2" }, "AutoLoginIntro": { "message": "You are enabling Auto-Login.\nThis means that XIVLauncher will always log you in with the current account and you will not see this window.\n\nTo change settings and accounts, you have to hold the shift button on your keyboard while clicking the XIVLauncher icon.", - "description": "<>c__DisplayClass19_0.b__0" + "description": "<>c__DisplayClass21_0.b__0" }, "GameIsOpenRepairError": { "message": "The game and/or the official launcher are open. XIVLauncher cannot repair the game if this is the case.\nPlease close them and try again.", - "description": "<>c__DisplayClass19_0.b__0" + "description": "<>c__DisplayClass21_0.b__0" }, "GameRepairDisclaimer": { "message": "XIVLauncher will now try to find corrupted game files and repair them.\nIf you use any TexTools mods, this will replace all of them and restore the game to its initial state.\n\nDo you want to continue?", - "description": "<>c__DisplayClass19_0.b__0" + "description": "<>c__DisplayClass21_0.b__0" }, "GateUnreachable": { "message": "The login servers could not be reached. This usually indicates that the game is under maintenance, or that your connection to the login servers is unstable.\n\nPlease try again later.", - "description": "d__22.MoveNext" + "description": "d__24.MoveNext" }, "GateClosed": { "message": "The game is currently under maintenance. Please try again later or see official sources for more information.", - "description": "d__22.MoveNext" + "description": "d__24.MoveNext" }, "CheckBootVersionError": { "message": "XIVLauncher was not able to check the boot version for the select game installation. This can happen if a maintenance is currently in progress or if your connection to the version check server is not available. Please report this error if you are able to login with the official launcher, but not XIVLauncher.", - "description": "d__33.MoveNext" + "description": "d__35.MoveNext" }, "BootPatchFailure": { "message": "Could not patch boot.", - "description": "d__33.MoveNext" + "description": "d__35.MoveNext" }, "KillswitchText": { "message": "XIVLauncher cannot start the game at this time, as there were changes to the login process during a recent patch.\nWe need to adjust to these changes and verify that our adjustments are safe before we can re-enable the launcher. Please try again later.\n\nWe apologize for these circumstances.\n\nYou can use the \"Official Launcher\" button below to start the official launcher.\n", - "description": "d__21.MoveNext" + "description": "d__23.MoveNext" }, "EmptyUsernameError": { "message": "Please enter an username.", - "description": "d__21.MoveNext" + "description": "d__23.MoveNext" }, "EmailUsernameError": { "message": "Please enter your SE account name, not your email address.", - "description": "d__21.MoveNext" + "description": "d__23.MoveNext" }, "EmptyPasswordError": { "message": "Please enter a password.", - "description": "d__21.MoveNext" + "description": "d__23.MoveNext" }, "UidCacheInstallError": { "message": "You enabled the UID cache in the patcher settings.\nThis setting does not allow you to reinstall the game.\n\nIf you want to reinstall the game, please take care to disable it first.", - "description": "d__21.MoveNext" + "description": "d__23.MoveNext" }, "GameRepairSuccess0": { "message": "All game files seem to be valid.", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "GameRepairSuccess1": { "message": "XIVLauncher has successfully repaired 1 game file.", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "GameRepairSuccessPlural": { "message": "XIVLauncher has successfully repaired {0} game files.", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "GameRepairSuccessMoved1": { "message": "Additionally, 1 file that did not come with the original game installation has been moved to {0}.\nIf you were using ReShade, you will have to reinstall it.", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "GameRepairSuccessMovedPlural": { "message": "Additionally, {0} files that did not come with the original game installation have been moved to {1}.\nIf you were using ReShade, you will have to reinstall it.", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "GameRepairSuccess_LaunchGame": { "message": "_Launch game", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "GameRepairSuccess_VerifyAgain": { "message": "_Verify again", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "GameRepairSuccess_Close": { "message": "_Close", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "NoVersionReferenceError": { "message": "The version of the game you are on cannot be repaired by XIVLauncher yet, as reference information is not yet available.\nPlease try again later.", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "GameRepairSuccess_TryAgain": { "message": "_Try again", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "GameRepairError": { "message": "An error occurred while repairing the game files.\nYou may have to reinstall the game.", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "PatcherAlreadyInProgress": { "message": "XIVLauncher is already patching your game in another instance. Please check if XIVLauncher is still open.", - "description": "d__25.MoveNext" + "description": "d__27.MoveNext" }, "DalamudVc2019RedistError": { "message": "The XIVLauncher in-game addon needs the Microsoft Visual C++ 2015-2019 redistributable to be installed to continue. Please install it from the Microsoft homepage.", - "description": "d__29.MoveNext" + "description": "d__31.MoveNext" }, "DalamudArchError": { "message": "Dalamud cannot run your computer's architecture. Please make sure that you are running a 64-bit version of Windows.\nIf you are using Windows on ARM, please make sure that x64-Emulation is enabled for XIVLauncher.", - "description": "d__29.MoveNext" + "description": "d__31.MoveNext" }, "DalamudEnsurementError": { "message": "Could not download necessary data files to use Dalamud and plugins.\nThis could be a problem with your internet connection, or might be caused by your antivirus application blocking necessary files. The game will start, but you will not be able to use plugins.\n\nPlease check our FAQ for more information.", - "description": "d__29.MoveNext" + "description": "d__31.MoveNext" }, "AddonLoadError": { "message": "This could be caused by your antivirus, please check its logs and add any needed exclusions.", - "description": "d__29.MoveNext" + "description": "d__31.MoveNext" }, "GameIsOpenError": { "message": "The game and/or the official launcher are open. XIVLauncher cannot patch the game if this is the case.\nPlease close the official launcher and try again.", - "description": "d__34.MoveNext" + "description": "d__36.MoveNext" }, "PatchManNoInstaller": { "message": "The patch installer could not start correctly.\n{0}\n\nIf you have denied access to it, please try again. If this issue persists, please contact us via Discord.", - "description": "d__34.MoveNext" + "description": "d__36.MoveNext" }, "FreeSpaceError": { "message": "There is not enough space on your drive to download patches.\n\nYou can change the location patches are downloaded to in the settings.\n\nRequired:{0}\nFree:{1}", - "description": "d__34.MoveNext" + "description": "d__36.MoveNext" }, "FreeSpaceErrorAll": { "message": "There is not enough space on your drive to download all patches.\n\nYou can change the location patches are downloaded to in the XIVLauncher settings.\n\nRequired:{0}\nFree:{1}", - "description": "d__34.MoveNext" + "description": "d__36.MoveNext" }, "FreeSpaceGameError": { "message": "There is not enough space on your drive to install patches.\n\nYou can change the location the game is installed to in the settings.\n\nRequired:{0}\nFree:{1}", - "description": "d__34.MoveNext" + "description": "d__36.MoveNext" }, "LoginNoOauthTitle": { "message": "Login issue", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginIoErrorSummary": { "message": "Could not locate game data files.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginIoErrorActionable": { "message": "This may mean that the game path set in XIVLauncher isn't preset, e.g. on a disconnected drive or network storage. Please check the game path in the XIVLauncher settings.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginInvalidVersionFiles": { "message": "Version information could not be read from your game files.\n\nYou need to reinstall or repair the game files. Right click the login button in XIVLauncher, and choose \"Repair Game\".", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginSteamNullTicket": { "message": "Steam did not authenticate you. This is likely a temporary issue with Steam and you may just have to try again in a few minutes.\n\nIf the issue persists, please make sure that Steam is running and that you are logged in with the account tied to your SE ID.\nIf you play using the Free Trial, please check the \"Using Free Trial account\" checkbox in the \"Game Settings\" tab of the XIVLauncher settings.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginSteamIssue": { "message": "Could not authenticate with Steam. Please make sure that Steam is running and that you are logged in with the account tied to your SE ID.\nIf you play using the Free Trial, please check the \"Using Free Trial account\" checkbox in the \"Game Settings\" tab of the XIVLauncher settings.\n\nContext: {0}", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginSteamWrongAccount": { "message": "The account you are logging in to is not the one that is linked to the Steam account on your PC. You can only log in with the account tied to your SE ID while using this Steam account.\n\nPlease log into matching accounts. The account that is linked to Steam is \"{0}\" - make sure there are no typos.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginSteamLinkNeeded": { "message": "Before starting the game with this account, you need to link it to your Steam account with the official launcher.\nPlease link your accounts and try again. You can do so by clicking the \"Official Launcher\" button.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginGenericError": { "message": "Could not log into your SE account.\nPlease check your username and password.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginGenericErrorCheckOtpUse": { "message": "If you're using OTP, then tick on \"{0}\" checkbox and try again.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginGenericErrorCheckOtp": { "message": "Double check whether your OTP device's clock is correct.\nIf you have recently logged in, then try logging in again in 30 seconds.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginWebExceptionContent": { "message": "XIVLauncher could not establish a connection to the game servers.\n\nThis may be a temporary issue, or a problem with your internet connection. Please try again later.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginGenericServerIssue": { "message": "The server has sent an invalid response. This is known to occur during outages or when servers are under heavy load.\nPlease wait a minute and try again, or try using the official launcher.\n\nYou can learn more about outages on the Lodestone.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "CheckLoginInfoNotAdditionally": { "message": "Please check your login information or try again.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginNoOauthAutologinHint": { "message": "\n\nAuto-Login has been disabled.", - "description": "d__23.MoveNext" + "description": "d__25.MoveNext" }, "LoginNoServiceMessage": { "message": "This account isn't eligible to play the game. Please make sure that you have an active subscription and that it is paid up.\n\nIf you bought the game on Steam, make sure to check the \"Use Steam service account\" checkbox while logging in.\nIf Auto-Login is enabled, hold shift while starting to access settings.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "LoginAcceptTermsMessage": { "message": "Please accept the Terms of Use in the official launcher.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "EverythingIsFuckedMessage": { "message": "Certain essential game files were modified/broken by a third party and the game can neither update nor start.\nYou have to reinstall the game to continue.\n\nIf this keeps happening, please contact us via Discord.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "LoginRepairResponseIsNotNeedsPatchGame": { "message": "The server sent an incorrect response - the repair cannot proceed.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "PatchInstallDisclaimer": { "message": "A new patch has been found that needs to be installed before you can play.\nDo you wish for XIVLauncher to install it?", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "LoginNoStartOk": { "message": "An update check was executed and any pending updates were installed.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "LaunchGameNonZeroExitCode": { "message": "It looks like the game has exited with a fatal error. Do you want to relaunch the game?\n\nExit code: 0x{0:X8}", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "LaunchGameRelaunch": { "message": "_Relaunch", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "LaunchGameClose": { "message": "_Close", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "LaunchGameDoNotAskAgain": { "message": "_Don't ask again", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "LaunchGameRetry": { "message": "_Try again", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "MultiboxDeniedWarningSummary": { "message": "You can't launch more than two instances of the game by default.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "MultiboxDeniedWarningActionable": { "message": "Please check if there is an instance of the game that did not close correctly. (Detected: {0})", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "LaunchGameKillThenRetry": { "message": "_Kill then try again", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "GameExitedPrematurelyErrorSummary": { "message": "XIVLauncher could not start the game correctly.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "GameExitedPrematurelyErrorActionable": { "message": "This may be a temporary issue. Please try restarting your PC.\nIt is possible that your game installation is not valid - you can repair your game installation by right clicking the Login button and choosing \"Repair game\".", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "GameExitedPrematurelyErrorAV": { "message": "\nThis issue could also be caused by your Antivirus program mistakenly marking XIVLauncher as malicious. You may have to add exclusions to its settings - please check our FAQ for more information.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "BinaryNotPresentErrorSummary": { "message": "Could not find the game executable.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "BinaryNotPresentErrorActionable": { "message": "This might be caused by your antivirus. You may have to reinstall the game.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "MultipleErrors": { "message": "Multiple errors have occurred.", - "description": "d__24.MoveNext" + "description": "d__26.MoveNext" }, "OtpInputPrompt": { "message": "Please enter your OTP key.", @@ -1003,6 +1035,26 @@ "message": "This method loads Dalamud via DLL-injection, which may be more stable on certain systems.", "description": "SettingsControlViewModel.SetupLoc" }, + "ImportConfigLoc": { + "message": "Import Config", + "description": "SettingsControlViewModel.SetupLoc" + }, + "ImportConfigTipLoc": { + "message": "Import Dalamud and Plugin config from a previously exported file.", + "description": "SettingsControlViewModel.SetupLoc" + }, + "ExportConfigLoc": { + "message": "Export Config", + "description": "SettingsControlViewModel.SetupLoc" + }, + "ExportConfigTipLoc": { + "message": "Export Dalamud and Plugin config to a file.", + "description": "SettingsControlViewModel.SetupLoc" + }, + "ImportExportConfigDescLoc": { + "message": "Import and export packages containing Dalamud and Plugin config files.", + "description": "SettingsControlViewModel.SetupLoc" + }, "UniversalisHint": { "message": "Market board data provided in cooperation with Universalis.", "description": "SettingsControlViewModel.SetupLoc" diff --git a/src/XIVLauncher/Windows/ConfigImportExportProgressWindow.xaml b/src/XIVLauncher/Windows/ConfigImportExportProgressWindow.xaml new file mode 100644 index 000000000..f8bcfb804 --- /dev/null +++ b/src/XIVLauncher/Windows/ConfigImportExportProgressWindow.xaml @@ -0,0 +1,26 @@ + + + + + Running Import/Export + + + + + diff --git a/src/XIVLauncher/Windows/ConfigImportExportProgressWindow.xaml.cs b/src/XIVLauncher/Windows/ConfigImportExportProgressWindow.xaml.cs new file mode 100644 index 000000000..da3b289d7 --- /dev/null +++ b/src/XIVLauncher/Windows/ConfigImportExportProgressWindow.xaml.cs @@ -0,0 +1,34 @@ +using CheapLoc; +using System.Windows; +using System.Windows.Input; +using XIVLauncher.Common.Game; +using XIVLauncher.Windows.ViewModel; + +namespace XIVLauncher.Windows +{ + /// + /// Interaction logic for ConfigImportExportProgressWindow.xaml + /// + public partial class ConfigImportExportProgressWindow : Window + { + public ConfigImportExportProgressWindow() + { + InitializeComponent(); + + this.DataContext = new ConfigImportExportProgressWindowViewModel(); + + MouseMove += ConfigImportExportProgressWindow_OnMouseMove; + } + + private void ConfigImportExportProgressWindow_OnMouseMove(object sender, MouseEventArgs e) + { + if (e.LeftButton == MouseButtonState.Pressed) + DragMove(); + } + + public void SetTitle(string newText) + { + InfoTextBlock.Text = $"{newText}"; + } + } +} diff --git a/src/XIVLauncher/Windows/SettingsControl.xaml b/src/XIVLauncher/Windows/SettingsControl.xaml index 59041d845..8ba708b84 100644 --- a/src/XIVLauncher/Windows/SettingsControl.xaml +++ b/src/XIVLauncher/Windows/SettingsControl.xaml @@ -1,4 +1,4 @@ - + + + + + @@ -344,4 +362,4 @@ - \ No newline at end of file + diff --git a/src/XIVLauncher/Windows/SettingsControl.xaml.cs b/src/XIVLauncher/Windows/SettingsControl.xaml.cs index bfef825d9..8fa4e5cae 100644 --- a/src/XIVLauncher/Windows/SettingsControl.xaml.cs +++ b/src/XIVLauncher/Windows/SettingsControl.xaml.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -19,6 +19,7 @@ using XIVLauncher.Common.Util; using XIVLauncher.Support; using XIVLauncher.Windows.ViewModel; +using XIVLauncher.Common.Support; namespace XIVLauncher.Windows { @@ -389,6 +390,84 @@ private void GamePathEntry_OnTextChanged(object sender, TextChangedEventArgs e) } } + private void ImportConfigButton_OnClick(object sender, RoutedEventArgs e) + { + var selectConfImportDialog = new Microsoft.Win32.OpenFileDialog(); + selectConfImportDialog.InitialDirectory = Paths.RoamingPath; + selectConfImportDialog.DefaultExt = ".xlconf"; + selectConfImportDialog.Filter = "XLConfig Export|*.xlconf"; + + // Show open file dialog box + bool? result = selectConfImportDialog.ShowDialog(); + + // Process open file dialog box results + if (result == true) + { + try + { + // Open document + string zipFile = selectConfImportDialog.FileName; + + //TODO: Localize this text like a good programmer + + var importConfirmPromptResult = CustomMessageBox.Show(Loc.Localize("ConfigImportPrompt1", "XIVLauncher is going to import config from the file you selected.\n\n") + zipFile + Loc.Localize("ConfigImportPrompt2", "\n\nThis will overwrite any files that already exist.\n\nDo you want to proceed?"), + Loc.Localize("ConfigImportPromptTitle", "Import config file?"), MessageBoxButton.YesNo); + + if (importConfirmPromptResult == MessageBoxResult.Yes) + { + var window = new ConfigImportExportProgressWindow(); + window.SetTitle(Loc.Localize("ConfigImportRunning", "Running config import...")); + + Task.Run(() => DalamudAndPluginConfigImportExport.ImportConfig(zipFile, Paths.RoamingPath)).ContinueWith(async task => + { + await task; + window.Dispatcher.Invoke(() => window.Close()); + + CustomMessageBox.Show(Loc.Localize("ConfigImportSuccess", "Dalamud and Plugin configuration import completed."), "XIVLauncher", MessageBoxButton.OK, MessageBoxImage.Information); + }); + + window.ShowDialog(); + } + if (importConfirmPromptResult == MessageBoxResult.No) + { + CustomMessageBox.Show(Loc.Localize("ConfigImportCancelled", "Dalamud and Plugin configuration import cancelled."), "XIVLauncher", MessageBoxButton.OK, MessageBoxImage.Information); + } + } + catch (Exception ex) + { + CustomMessageBox.Show(ex.ToString(), "XIVLauncher Error", MessageBoxButton.OK, MessageBoxImage.Error); + } + + } + } + private void ExportConfigButton_OnClick(object sender, RoutedEventArgs e) + { + try + { + var window = new ConfigImportExportProgressWindow(); + window.SetTitle(Loc.Localize("ConfigExportRunning", "Running config export...")); + + Task.Run(async () => await DalamudAndPluginConfigImportExport.ExportConfig(Paths.RoamingPath)).ContinueWith(async task => + { + var filename = await task; + window.Dispatcher.Invoke(() => window.Close()); + + var exportedFile = new FileInfo(filename); + + + CustomMessageBox.Show(Loc.Localize("ConfigExportSuccess", "Exported config as ") + exportedFile.Name, "XIVLauncher", MessageBoxButton.OK, MessageBoxImage.Information); + Process.Start("explorer.exe", $"/select, \"{filename}\""); + }); + + window.ShowDialog(); + + } + catch (Exception ex) + { + CustomMessageBox.Show(ex.ToString(), "XIVLauncher Error", MessageBoxButton.OK, MessageBoxImage.Error); + } + } + private void LicenseText_OnMouseUp(object sender, MouseButtonEventArgs e) { Process.Start(Path.Combine(Paths.ResourcesPath, "LICENSE.txt")); @@ -452,4 +531,4 @@ private void OpenAdvancedSettings_OnClick(object sender, RoutedEventArgs e) asw.ShowDialog(); } } -} \ No newline at end of file +} diff --git a/src/XIVLauncher/Windows/ViewModel/ConfigImportExportProgressWindowViewModel.cs b/src/XIVLauncher/Windows/ViewModel/ConfigImportExportProgressWindowViewModel.cs new file mode 100644 index 000000000..d0c20fe5a --- /dev/null +++ b/src/XIVLauncher/Windows/ViewModel/ConfigImportExportProgressWindowViewModel.cs @@ -0,0 +1,17 @@ +using CheapLoc; + +namespace XIVLauncher.Windows.ViewModel +{ + class ConfigImportExportProgressWindowViewModel + { + public ConfigImportExportProgressWindowViewModel() + { + SetupLoc(); + } + + private void SetupLoc() + { + return; + } + } +} diff --git a/src/XIVLauncher/Windows/ViewModel/SettingsControlViewModel.cs b/src/XIVLauncher/Windows/ViewModel/SettingsControlViewModel.cs index 4378119e4..4658cc50f 100644 --- a/src/XIVLauncher/Windows/ViewModel/SettingsControlViewModel.cs +++ b/src/XIVLauncher/Windows/ViewModel/SettingsControlViewModel.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.IO; using System.Runtime.CompilerServices; using CheapLoc; @@ -131,6 +131,11 @@ private void SetupLoc() "This method uses Entry-Point rewriting to load Dalamud, which may be more compatible with certain other software, like anti-virus tools."); InGameAddonLoadMethodDllInjectDescriptionLoc = Loc.Localize("InGameAddonLoadMethodDllInjectDescriptionLoc", "This method loads Dalamud via DLL-injection, which may be more stable on certain systems."); + ImportConfigLoc = Loc.Localize("ImportConfigLoc", "Import Config"); + ImportConfigTipLoc = Loc.Localize("ImportConfigTipLoc", "Import Dalamud and Plugin config from a previously exported file."); + ExportConfigLoc = Loc.Localize("ExportConfigLoc", "Export Config"); + ExportConfigTipLoc = Loc.Localize("ExportConfigTipLoc", "Export Dalamud and Plugin config to a file."); + ImportExportConfigDescLoc = Loc.Localize("ImportExportConfigDescLoc", "Import and export packages containing Dalamud and Plugin config files."); UniversalisHintLoc = Loc.Localize("UniversalisHint", "Market board data provided in cooperation with Universalis."); @@ -225,6 +230,11 @@ private void SetupLoc() public string InGameAddonLoadMethodDllInjectDescriptionLoc { get; private set; } public string UniversalisHintLoc { get; private set; } public string UniversalisOptOutLoc { get; private set; } + public string ImportConfigLoc { get; private set; } + public string ImportConfigTipLoc { get; private set; } + public string ExportConfigLoc { get; private set; } + public string ExportConfigTipLoc { get; private set; } + public string ImportExportConfigDescLoc { get; private set; } public string PluginsDescriptionLoc { get; private set; } public string PluginsToggleLoc { get; private set; }