diff --git a/AngelLoader/Forms/MainForm.cs b/AngelLoader/Forms/MainForm.cs index 5fe7d209f..1b87bbaf3 100644 --- a/AngelLoader/Forms/MainForm.cs +++ b/AngelLoader/Forms/MainForm.cs @@ -860,7 +860,7 @@ private async void MainForm_KeyDown(object sender, KeyEventArgs e) if (FMsDGV.Focused && FMsDGV.SelectedRows.Count > 0 && GameIsKnownAndSupported(GetSelectedFM())) { e.SuppressKeyPress = true; - await InstallAndPlay.InstallOrPlay(GetSelectedFM(), askConfIfRequired: true); + await InstallAndPlay.InstallIfNeededAndPlay(GetSelectedFM(), askConfIfRequired: true); } } else if (e.KeyCode == Keys.Escape) @@ -2036,7 +2036,7 @@ private void FMRightClickMenu_Opening(object sender, CancelEventArgs e) if (FMsDGV.RowCount == 0 || FMsDGV.SelectedRows.Count == 0) e.Cancel = true; } - private async void PlayFMMenuItem_Click(object sender, EventArgs e) => await InstallAndPlay.InstallOrPlay(GetSelectedFM()); + private async void PlayFMMenuItem_Click(object sender, EventArgs e) => await InstallAndPlay.InstallIfNeededAndPlay(GetSelectedFM()); private async void InstallUninstallMenuItem_Click(object sender, EventArgs e) => await InstallAndPlay.InstallOrUninstall(GetSelectedFM()); @@ -2052,7 +2052,7 @@ private void FMRightClickMenu_Opening(object sender, CancelEventArgs e) private async void InstallUninstallFMButton_Click(object sender, EventArgs e) => await InstallAndPlay.InstallOrUninstall(GetSelectedFM()); - private async void PlayFMButton_Click(object sender, EventArgs e) => await InstallAndPlay.InstallOrPlay(GetSelectedFM()); + private async void PlayFMButton_Click(object sender, EventArgs e) => await InstallAndPlay.InstallIfNeededAndPlay(GetSelectedFM()); #region Play original game @@ -3902,7 +3902,7 @@ private async void FMsDGV_CellDoubleClick(object sender, DataGridViewCellEventAr return; } - await InstallAndPlay.InstallOrPlay(fm, askConfIfRequired: true); + await InstallAndPlay.InstallIfNeededAndPlay(fm, askConfIfRequired: true); } private async void RefreshFromDiskButton_Click(object sender, EventArgs e) => await Core.RefreshFromDisk(); diff --git a/AngelLoader/InstallAndPlay.cs b/AngelLoader/InstallAndPlay.cs index 490d32fb2..93ce5aed7 100644 --- a/AngelLoader/InstallAndPlay.cs +++ b/AngelLoader/InstallAndPlay.cs @@ -25,7 +25,7 @@ internal static class InstallAndPlay internal static async Task InstallOrUninstall(FanMission fm) => await (fm.Installed ? UninstallFM(fm) : InstallFM(fm)); - internal static async Task InstallOrPlay(FanMission fm, bool askConfIfRequired = false) + internal static async Task InstallIfNeededAndPlay(FanMission fm, bool askConfIfRequired = false) { if (askConfIfRequired && Config.ConfirmPlayOnDCOrEnter) { @@ -44,46 +44,38 @@ internal static async Task InstallOrPlay(FanMission fm, bool askConfIfRequired = } } - internal static bool PlayFM(FanMission fm) - { - if (fm.Game == null) - { - Core.View.ShowAlert(LText.AlertMessages.Play_UnknownGameType, LText.AlertMessages.Alert); - return false; - } + #region Play / open - var gameExe = GetGameExeFromGameType((Game)fm.Game); - - #region Exe: Fail if blank or not found + internal static bool PlayOriginalGame(Game game) + { + var (success, gameExe, gamePath) = GetGameExeAndPath(game, LText.AlertMessages.Play_ExecutableNotFound); + if (!success) return false; - var gameName = GetGameNameFromGameType((Game)fm.Game); + // Even though we're not actually loading an FM, we still want to set us as the selector so that our + // stub can explicitly tell Thief to play without an FM. Otherwise, if another selector was specified, + // then that selector would start upon running of the game exe, which would be bad. + SetUsAsSelector(game, gameExe, gamePath); - if (gameExe.IsEmpty() || !File.Exists(gameExe)) - { - Core.View.ShowAlert(gameName + ":\r\n" + LText.AlertMessages.Play_ExecutableNotFoundFM, - LText.AlertMessages.Alert); - return false; - } + // When the stub finds nothing in the stub comm folder, it will just start the game with no FM + Paths.PrepareTempPath(Paths.StubCommTemp); - #endregion + StartExe(gameExe, gamePath, null); - #region Exe: Fail if already running + return true; + } - if (GameIsRunning(gameExe, checkAllGames: true)) + private static bool PlayFM(FanMission fm) + { + if (fm.Game == null) { - Core.View.ShowAlert(LText.AlertMessages.Play_AnyGameIsRunning, LText.AlertMessages.Alert); + Core.View.ShowAlert(LText.AlertMessages.Play_UnknownGameType, LText.AlertMessages.Alert); return false; } - #endregion + var (success, gameExe, gamePath) = GetGameExeAndPath(fm.Game, LText.AlertMessages.Play_ExecutableNotFoundFM); + if (!success) return false; - var gamePath = Path.GetDirectoryName(gameExe); - if (gamePath.IsEmpty()) - { - return false; - } - - SetUsAsLoader(fm.Game); + SetUsAsSelector((Game)fm.Game, gameExe, gamePath); // Only use the stub if we need to pass something we can't pass on the command line // Add quotes around it in case there are spaces in the dir name. Will only happen if you put an FM @@ -108,20 +100,7 @@ internal static bool PlayFM(FanMission fm) } } - using (var proc = new Process()) - { - proc.StartInfo.FileName = gameExe; - proc.StartInfo.Arguments = args; - proc.StartInfo.WorkingDirectory = gamePath; - try - { - proc.Start(); - } - catch (Exception ex) - { - Log("Exception starting game " + gameExe, ex); - } - } + StartExe(gameExe, gamePath, args); // Don't clear the temp folder here, because the stub program will need to read from it. It will // delete the temp file itself after it's done with it. @@ -131,6 +110,8 @@ internal static bool PlayFM(FanMission fm) internal static bool OpenFMInDromEd(FanMission fm) { + #region Checks (specific to DromEd) + if (!GameIsDark(fm)) return false; if (fm.Game == null) @@ -146,8 +127,6 @@ internal static bool OpenFMInDromEd(FanMission fm) return false; } - #region Exe: Fail if blank or not found - var dromedExe = GetDromEdExe((Game)fm.Game); if (dromedExe.IsEmpty()) { @@ -155,70 +134,58 @@ internal static bool OpenFMInDromEd(FanMission fm) return false; } - #endregion - - SetUsAsLoader(fm.Game); - var gamePath = Path.GetDirectoryName(gameExe); if (gamePath.IsEmpty()) return false; + #endregion + + SetUsAsSelector((Game)fm.Game, gameExe, gamePath); + // We don't need the stub for DromEd, cause we don't need to pass anything except the fm folder + StartExe(dromedExe, gamePath, "-fm=\"" + fm.InstalledDir + "\""); + + return true; + } + + #endregion + + #region Helpers + + private static void StartExe(string exe, string workingPath, string args) + { using (var proc = new Process()) { - proc.StartInfo.FileName = dromedExe; - proc.StartInfo.Arguments = "-fm=\"" + fm.InstalledDir + "\""; - proc.StartInfo.WorkingDirectory = gamePath; - + proc.StartInfo.FileName = exe; + if (!args.IsEmpty()) proc.StartInfo.Arguments = args; + proc.StartInfo.WorkingDirectory = workingPath; try { proc.Start(); } catch (Exception ex) { - Log("Exception starting " + dromedExe, ex); + Log("Exception starting " + exe, ex); } } - - return true; } - private static void SetUsAsLoader(Game? game) + private static (bool Success, string gameExe, string gamePath) + GetGameExeAndPath(Game? game, string exeNotFoundMessage) { - if (game == null) return; + (bool, string, string) failed = (false, null, null); - if (GameIsDark(game)) - { - var success = SetDarkFMSelectorToAngelLoader((Game)game); - if (!success) - { - Log("Unable to set us as the selector for " + game + " (" + - nameof(SetDarkFMSelectorToAngelLoader) + " returned false)", stackTrace: true); - } - } - else if (game == Game.Thief3) - { - var success = SetT3FMSelectorToAngelLoader(); - if (!success) - { - Log("Unable to set us as the selector for Thief: Deadly Shadows (" + - nameof(SetT3FMSelectorToAngelLoader) + " returned false)", stackTrace: true); - } - } - } + if (game == null) return failed; - internal static bool PlayOriginalGame(Game game) - { - var gameExe = GetGameExeFromGameType(game); + var gameExe = GetGameExeFromGameType((Game)game); #region Exe: Fail if blank or not found - var gameName = GetGameNameFromGameType(game); + var gameName = GetGameNameFromGameType((Game)game); if (gameExe.IsEmpty() || !File.Exists(gameExe)) { - Core.View.ShowAlert(gameName + ":\r\n" + LText.AlertMessages.Play_ExecutableNotFound, - LText.AlertMessages.Alert); - return false; + Core.View.ShowAlert(gameName + ":\r\n" + exeNotFoundMessage, LText.AlertMessages.Alert); + return failed; } #endregion @@ -228,7 +195,7 @@ internal static bool PlayOriginalGame(Game game) if (GameIsRunning(gameExe, checkAllGames: true)) { Core.View.ShowAlert(LText.AlertMessages.Play_AnyGameIsRunning, LText.AlertMessages.Alert); - return false; + return failed; } #endregion @@ -238,50 +205,32 @@ internal static bool PlayOriginalGame(Game game) { Core.View.ShowAlert(gameName + ":\r\n" + LText.AlertMessages.Play_GamePathNotFound, LText.AlertMessages.Alert); - return false; + return failed; } - // Even though we're not actually loading an FM, we still want to set us as the loader so that our - // stub can explicitly tell Thief to play without an FM. Otherwise, if another loader was specified, - // then that loader would start upon running of the game exe, which would be bad. - SetUsAsLoader(game); + return (true, gameExe, gamePath); + } - // When the stub finds nothing in the stub comm folder, it will just start the game with no FM - Paths.PrepareTempPath(Paths.StubCommTemp); + #endregion - try - { - using (var proc = new Process()) - { - proc.StartInfo.FileName = gameExe; - proc.StartInfo.WorkingDirectory = gamePath; - proc.Start(); - } - } - catch (Exception ex) + #region Set us as selector + + private static void SetUsAsSelector(Game game, string gameExe, string gamePath) + { + Debug.Assert(GameIsKnownAndSupported(game), "!GameIsKnownAndSupported(game)"); + + bool success = GameIsDark(game) ? SetUsAsDarkFMSelector(gameExe, gamePath) : SetUsAsT3FMSelector(); + if (!success) { - Log("Exception starting " + gameExe, ex); + Log("Unable to set us as the selector for " + gameExe + " (" + + (GameIsDark(game) ? nameof(SetUsAsDarkFMSelector) : nameof(SetUsAsT3FMSelector)) + + " returned false)", stackTrace: true); } - - return true; } - private static bool SetDarkFMSelectorToAngelLoader(Game game) + private static bool SetUsAsDarkFMSelector(string gameExe, string gamePath) { const string fmSelectorKey = "fm_selector"; - var gameExe = GetGameExeFromGameType(game); - if (gameExe.IsEmpty()) - { - Log("gameExe is empty for " + game, stackTrace: true); - return false; - } - - var gamePath = Path.GetDirectoryName(gameExe); - if (gamePath.IsEmpty()) - { - Log("gamePath is empty for " + game, stackTrace: true); - return false; - } var camModIni = Path.Combine(gamePath, "cam_mod.ini"); if (!File.Exists(camModIni)) @@ -379,7 +328,7 @@ private static bool SetDarkFMSelectorToAngelLoader(Game game) // If only you could do this with a command-line switch. You can say -fm to always start with the loader, // and you can say -fm=name to always start with the named FM, but you can't specify WHICH loader to use // on the command line. Only way to do it is through a file. Meh. - private static bool SetT3FMSelectorToAngelLoader() + private static bool SetUsAsT3FMSelector() { const string externSelectorKey = "ExternSelector="; bool existingKeyOverwritten = false; @@ -447,6 +396,10 @@ private static bool SetT3FMSelectorToAngelLoader() return true; } + #endregion + + #region Install / uninstall + internal static async Task InstallFM(FanMission fm) { Debug.Assert(!fm.Installed, "!fm.Installed"); @@ -714,54 +667,7 @@ internal static void CancelInstallFM() } } - private static async Task DeleteFMInstalledDirectory(string path) - { - bool result = await Task.Run(() => - { - var triedReadOnlyRemove = false; - - // Failsafe cause this is nasty - for (int i = 0; i < 2; i++) - { - try - { - Directory.Delete(path, recursive: true); - return true; - } - catch (Exception) - { - try - { - if (triedReadOnlyRemove) return false; - - // FMs installed by us will not have any readonly attributes set, so we work on the - // assumption that this is the rarer case and only do this extra work if we need to. - foreach (var f in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) - { - new FileInfo(f).IsReadOnly = false; - } - - foreach (var d in Directory.EnumerateDirectories(path, "*", SearchOption.AllDirectories)) - { - new DirectoryInfo(d).Attributes = FileAttributes.Normal; - } - - triedReadOnlyRemove = true; - } - catch (Exception) - { - return false; - } - } - } - - return false; - }); - - return result; - } - - internal static async Task UninstallFM(FanMission fm) + private static async Task UninstallFM(FanMission fm) { if (!fm.Installed || !GameIsKnownAndSupported(fm)) return; @@ -851,15 +757,11 @@ internal static async Task UninstallFM(FanMission fm) await BackupFM(fm, fmInstalledPath, fmArchivePath); } - // --- DEBUG - //return; - // TODO: Give the user the option to retry or something, if it's cause they have a file open if (!await DeleteFMInstalledDirectory(fmInstalledPath)) { // TODO: Make option to open the folder in Explorer and delete it manually? - Core.View.ShowAlert(LText.AlertMessages.Uninstall_UninstallNotCompleted, - LText.AlertMessages.Alert); + Core.View.ShowAlert(LText.AlertMessages.Uninstall_UninstallNotCompleted, LText.AlertMessages.Alert); } fm.Installed = false; @@ -880,14 +782,59 @@ internal static async Task UninstallFM(FanMission fm) catch (Exception ex) { Log("Exception uninstalling FM " + fm.Archive + ", " + fm.InstalledDir, ex); - Core.View.InvokeSync(new Action(() => - Core.View.ShowAlert(LText.AlertMessages.Uninstall_FailedFullyOrPartially, - LText.AlertMessages.Alert))); + Core.View.ShowAlert(LText.AlertMessages.Uninstall_FailedFullyOrPartially, LText.AlertMessages.Alert); } finally { Core.ProgressBox.HideThis(); } } + + private static async Task DeleteFMInstalledDirectory(string path) + { + return await Task.Run(() => + { + var triedReadOnlyRemove = false; + + // Failsafe cause this is nasty + for (int i = 0; i < 2; i++) + { + try + { + Directory.Delete(path, recursive: true); + return true; + } + catch (Exception) + { + try + { + if (triedReadOnlyRemove) return false; + + // FMs installed by us will not have any readonly attributes set, so we work on the + // assumption that this is the rarer case and only do this extra work if we need to. + foreach (var f in Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories)) + { + new FileInfo(f).IsReadOnly = false; + } + + foreach (var d in Directory.EnumerateDirectories(path, "*", SearchOption.AllDirectories)) + { + new DirectoryInfo(d).Attributes = FileAttributes.Normal; + } + + triedReadOnlyRemove = true; + } + catch (Exception) + { + return false; + } + } + } + + return false; + }); + } + + #endregion } }