internal static async Task <bool> InstallFM(FanMission fm)
        {
            #region Checks

            AssertR(!fm.Installed, "fm.Installed == false");

            if (!GameIsKnownAndSupported(fm.Game))
            {
                Log("FM game type is unknown or unsupported.\r\n" +
                    "FM: " + (!fm.Archive.IsEmpty() ? fm.Archive : fm.InstalledDir) + "\r\n" +
                    "FM game was: " + fm.Game);
                Dialogs.ShowError(ErrorText.FMGameTypeUnknownOrUnsupported);
                return(false);
            }

            GameIndex gameIndex = GameToGameIndex(fm.Game);

            string fmArchivePath = FMArchives.FindFirstMatch(fm.Archive);

            if (fmArchivePath.IsEmpty())
            {
                Log("FM archive field was empty; this means an archive was not found for it on the last search.\r\n" +
                    "FM: " + (!fm.Archive.IsEmpty() ? fm.Archive : fm.InstalledDir) + "\r\n" +
                    "FM game was: " + fm.Game);
                Dialogs.ShowError(LText.AlertMessages.Install_ArchiveNotFound);
                return(false);
            }

            string gameExe  = Config.GetGameExe(gameIndex);
            string gameName = GetLocalizedGameName(gameIndex);
            if (!File.Exists(gameExe))
            {
                Log("Game executable not found.\r\n" +
                    "Game executable: " + gameExe);
                Dialogs.ShowError(gameName + ":\r\n" +
                                  LText.AlertMessages.Install_ExecutableNotFound);
                return(false);
            }

            string instBasePath = Config.GetFMInstallPath(gameIndex);

            if (!Directory.Exists(instBasePath))
            {
                Log("FM install path not found.\r\n" +
                    "FM: " + (!fm.Archive.IsEmpty() ? fm.Archive : fm.InstalledDir) + "\r\n" +
                    "FM game was: " + fm.Game + "\r\n" +
                    "FM install path: " + instBasePath
                    );
                Dialogs.ShowError(gameName + ":\r\n" +
                                  LText.AlertMessages.Install_FMInstallPathNotFound);
                return(false);
            }

            if (GameIsRunning(gameExe))
            {
                Dialogs.ShowAlert(gameName + ":\r\n" +
                                  LText.AlertMessages.Install_GameIsRunning, LText.AlertMessages.Alert);
                return(false);
            }

            #endregion

            string fmInstalledPath = Path.Combine(instBasePath, fm.InstalledDir);

            _extractCts = new CancellationTokenSource();

            Core.View.ShowProgressBox(ProgressTask.InstallFM);

            // Framework zip extracting is much faster, so use it if possible
            bool canceled = !await(fmArchivePath.ExtIsZip()
                ? Task.Run(() => InstallFMZip(fmArchivePath, fmInstalledPath))
                : Task.Run(() => InstallFMSevenZip(fmArchivePath, fmInstalledPath)));

            if (canceled)
            {
                Core.View.SetCancelingFMInstall();
                await Task.Run(() =>
                {
                    try
                    {
                        Directory.Delete(fmInstalledPath, recursive: true);
                    }
                    catch (Exception ex)
                    {
                        // @BetterErrors(InstallFM() - install cancellation (folder deletion) failed)
                        Log("Unable to delete FM installed directory " + fmInstalledPath, ex);
                    }
                });

                Core.View.HideProgressBox();
                return(false);
            }

            fm.Installed = true;

            Ini.WriteFullFMDataIni();

            try
            {
                using var sw = new StreamWriter(Path.Combine(fmInstalledPath, Paths.FMSelInf), append: false);
                await sw.WriteLineAsync("Name=" + fm.InstalledDir);

                await sw.WriteLineAsync("Archive=" + fm.Archive);
            }
            catch (Exception ex)
            {
                Log("Couldn't create " + Paths.FMSelInf + " in " + fmInstalledPath, ex);
            }

            // Only Dark engine games need audio conversion
            if (GameIsDark(gameIndex))
            {
                try
                {
                    Core.View.ShowProgressBox(ProgressTask.ConvertFiles);

                    // Dark engine games can't play MP3s, so they must be converted in all cases.
                    // This one won't be called anywhere except during install, because it always runs during
                    // install so there's no need to make it optional elsewhere. So we don't need to have a
                    // check bool or anything.
                    await FMAudio.ConvertToWAVs(fm, AudioConvert.MP3ToWAV, false);

                    if (Config.ConvertOGGsToWAVsOnInstall)
                    {
                        await FMAudio.ConvertToWAVs(fm, AudioConvert.OGGToWAV, false);
                    }
                    if (Config.ConvertWAVsTo16BitOnInstall)
                    {
                        await FMAudio.ConvertToWAVs(fm, AudioConvert.WAVToWAV16, false);
                    }
                }
                catch (Exception ex)
                {
                    Log("Exception in audio conversion", ex);
                }
            }

            // Don't be lazy about this; there can be no harm and only benefits by doing it right away
            GenerateMissFlagFileIfRequired(fm);

            // TODO: Put up a "Restoring saves and screenshots" box here to avoid the "converting files" one lasting beyond its time?
            try
            {
                await RestoreFM(fm);
            }
            catch (Exception ex)
            {
                Log(ex: ex);
            }
            finally
            {
                Core.View.HideProgressBox();
            }

            // Not doing RefreshFM(rowOnly: true) because that wouldn't update the install/uninstall buttons
            Core.View.RefreshFM(fm);

            return(true);
        }
Beispiel #2
0
        GetFMSupportedLanguagesFromArchive(string archiveName, bool earlyOutOnEnglish)
        {
            // @DIRSEP: '/' conversion in here due to string.IndexOf()

            (bool, List <string>)failed = (false, new List <string>());

            string archivePath = FMArchives.FindFirstMatch(archiveName);

            if (archivePath.IsEmpty())
            {
                return(failed);
            }

            var ret = new List <string>(Supported.Length);

            bool[] FoundLangInArchive = new bool[Supported.Length];
            // Pre-concat each string only once for perf
            string[] SLangsFSPrefixed = new string[Supported.Length];
            for (int i = 0; i < Supported.Length; i++)
            {
                SLangsFSPrefixed[i] = "/" + Supported[i];
            }

            FMScanner.FastZipReader.ZipArchiveFast?zipArchive = null;
            SevenZipExtractor?sevenZipArchive = null;

            try
            {
                bool fmIsZip = archivePath.ExtIsZip();

                if (fmIsZip)
                {
                    zipArchive = new FMScanner.FastZipReader.ZipArchiveFast(File.OpenRead(archivePath));
                }
                else
                {
                    sevenZipArchive = new SevenZipExtractor(archivePath);
                }

                int filesCount = fmIsZip ? zipArchive !.Entries.Count : (int)sevenZipArchive !.FilesCount;
                for (int i = 0; i < filesCount; i++)
                {
                    string fn = fmIsZip
                                // ZipArchiveFast guarantees full names to never contain backslashes
                        ? zipArchive !.Entries[i].FullName
                                // For some reason ArchiveFileData[i].FileName is like 20x faster than ArchiveFileNames[i]
                        : sevenZipArchive !.ArchiveFileData[i].FileName.ToForwardSlashes();

                    for (int j = 0; j < Supported.Length; j++)
                    {
                        string sl = Supported[j];
                        if (!FoundLangInArchive[j])
                        {
                            // Do as few string operations as humanly possible
                            int index = fn.IndexOf(SLangsFSPrefixed[j], StringComparison.OrdinalIgnoreCase);
                            if (index < 1)
                            {
                                continue;
                            }

                            if ((fn.Length > index + sl.Length + 1 && fn[index + sl.Length + 1] == '/') ||
                                (index == (fn.Length - sl.Length) - 1))
                            {
                                if (earlyOutOnEnglish && j == 0)
                                {
                                    return(true, new List <string> {
                                        "English"
                                    });
                                }
                                ret.Add(sl);
                                FoundLangInArchive[j] = true;
                            }
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Log("Scanning archive '" + archivePath + "' for languages failed.", ex);
                return(false, new List <string>());
            }
            finally
            {
                zipArchive?.Dispose();
                sevenZipArchive?.Dispose();
            }

            return(true, ret);
        }