Beispiel #1
0
        private static async Task <int> RunAsync(Options options)
        {
            var sourceDirectories = new List <string>
            {
                new DirectoryInfo(options.DownloadDirectoryPath).FullName,
            };

            DirectoryInfo dumpDirectory = new DirectoryInfo(options.OutputDirectoryPath);
            bool          needsDelete   = dumpDirectory.Exists && dumpDirectory.EnumerateFileSystemInfos().Any();

            if (needsDelete && !options.Scorch)
            {
                Console.Error.WriteLine("Output folder exists already and is not empty.  Aborting...");
                Console.WriteLine("(run with -x / --scorch to have us automatically delete instead).");
                return(2);
            }

            dumpDirectory.Create();

            // HACK: these really shouldn't be hardcoded like this, but it's better than nothing.
            // ideal would be to dynamically detect parameters from the pack files themselves and
            // offer up a dynamic UI that lets the user specify whatever they want from that.  this
            // works for now, though, and it might be good enough for a long time.
            uint screenHeight           = options.ScreenHeight;
            uint screenWidth            = options.ScreenWidth;
            bool isFullScreen           = options.FullScreenMode.HasFlag(FullScreenMode.IsFullScreen);
            bool isBorderless           = options.FullScreenMode.HasFlag(FullScreenMode.IsBorderless);
            bool isBorderlessFullScreen = options.FullScreenMode.HasFlag(FullScreenMode.IsBorderless | FullScreenMode.IsFullScreen);

            // MAGIC1: TAN(65 * PI / 360) / (16 / 10)
            // MAGIC2: 360 / PI
            // Magic numbers in MAGIC1's definition come from the claim that the ideal Skyrim FOV is
            // 65 degrees, based on a 16x10 resolution.
            const double MAGIC1 = 0.3981689130046832;
            const double MAGIC2 = 114.591559026164642;
            double       optimalSkyrimFOVDegrees = Math.Atan(screenWidth / (double)screenHeight * MAGIC1) * MAGIC2;

            var modpacks = new List <Modpack>();

            // lots of strings show up multiple times each.
            StringPool pool = new StringPool();

            string gameInstallPath = null;
            string gameDataPath    = null;

            HashSet <string> seenSoFar  = new HashSet <string>();
            int longestOutputPathLength = 0;

            foreach (string packDefinitionFilePath in options.PackDefinitionFilePaths)
            {
                bool   requiresJava, requiresSteam, requiresSkyrim;
                string steamPath = null;

                XDocument doc;
                using (FileStream packDefinitionFileStream = AsyncFile.OpenReadSequential(packDefinitionFilePath))
                    using (StreamReader reader = new StreamReader(packDefinitionFileStream, Encoding.UTF8, false, 4096, true))
                    {
                        string docText = await reader.ReadToEndAsync().ConfigureAwait(false);

                        requiresJava  = docText.Contains("{JavaBinFolderForwardSlashes}", StringComparison.Ordinal);
                        requiresSteam = docText.Contains("{SteamInstallFolder}", StringComparison.Ordinal) ||
                                        docText.Contains("{SteamInstallFolderEscapeBackslashes}", StringComparison.Ordinal);

                        requiresSkyrim = docText.Contains("{SkyrimInstallFolder}", StringComparison.Ordinal) ||
                                         docText.Contains("{SkyrimInstallFolderForwardSlashes}", StringComparison.Ordinal) ||
                                         docText.Contains("{SkyrimInstallFolderEscapeBackslashes}", StringComparison.Ordinal);

                        StringBuilder docTextBuilder = new StringBuilder(docText);
                        docTextBuilder = docTextBuilder.Replace("{DumpFolderForwardSlashes}", dumpDirectory.FullName.Replace(Path.DirectorySeparatorChar, '/'))
                                         .Replace("{DumpFolderEscapeBackslashes}", dumpDirectory.FullName.Replace("\\", "\\\\"))
                                         .Replace("{ScreenHeight}", screenHeight.ToString(CultureInfo.InvariantCulture))
                                         .Replace("{ScreenWidth}", screenWidth.ToString(CultureInfo.InvariantCulture))
                                         .Replace("{IsFullScreenTrueFalse}", isFullScreen ? "true" : "false")
                                         .Replace("{IsFullScreenNumeric}", isFullScreen ? "1" : "0")
                                         .Replace("{IsBorderlessTrueFalse}", isBorderless ? "true" : "false")
                                         .Replace("{IsBorderlessNumeric}", isBorderless ? "1" : "0")
                                         .Replace("{IsBorderlessFullScreenTrueFalse}", isBorderlessFullScreen ? "true" : "false")
                                         .Replace("{IsBorderlessFullScreenNumeric}", isBorderlessFullScreen ? "1" : "0")
                                         .Replace("{OptimalSkyrimFOVDegrees}", optimalSkyrimFOVDegrees.ToString("F2", CultureInfo.InvariantCulture))
                                         .Replace("{GraphicsPreset}", options.GraphicsPreset.ToString().ToLowerInvariant());

                        if (requiresJava)
                        {
                            string javaBinPath = GetJavaBinDirectoryPath(options);
                            if (String.IsNullOrEmpty(javaBinPath))
                            {
                                Console.Error.WriteLine("--javaBinFolder is required for {0}.", XDocument.Parse(docTextBuilder.ToString()).Element("Modpack").Attribute("Name").Value);
                                return(6);
                            }

                            DirectoryInfo javaBinDirectory = new DirectoryInfo(javaBinPath);
                            javaBinPath = javaBinDirectory.FullName;
                            if (!javaBinDirectory.EnumerateFiles().Any(fl => "javaw.exe".Equals(fl.Name, StringComparison.OrdinalIgnoreCase)))
                            {
                                Console.Error.WriteLine("Java bin folder {0} does not contain a file called \"javaw.exe\".", javaBinPath);
                                return(14);
                            }

                            docTextBuilder = docTextBuilder.Replace("{JavaBinFolderForwardSlashes}", javaBinPath.Replace(Path.DirectorySeparatorChar, '/'));
                        }

                        if (requiresSteam)
                        {
                            if (options.SteamDirectoryPath == null)
                            {
                                Console.Error.WriteLine("-s / --steamFolder is required for {0}.", XDocument.Parse(docTextBuilder.ToString()).Element("Modpack").Attribute("Name").Value);
                                return(11);
                            }

                            steamPath      = new DirectoryInfo(options.SteamDirectoryPath).FullName;
                            docTextBuilder = docTextBuilder.Replace("{SteamInstallFolder}", steamPath)
                                             .Replace("{SteamInstallFolderEscapeBackslashes}", steamPath.Replace("\\", "\\\\"));

                            // pack files targeting versions earlier than 0.9.3.0 would need this; other
                            // pack files won't use this anyway, so it's just a small waste of time.
                            gameInstallPath = Path.Combine(steamPath, "steamapps", "common", "Skyrim");
                        }

                        if (requiresSkyrim)
                        {
                            gameInstallPath = GetSkyrimDirectoryPath(options);
                            if (String.IsNullOrEmpty(gameInstallPath))
                            {
                                Console.Error.WriteLine("-s / --steamFolder, or a valid Skyrim registry key, is required for {0}.", XDocument.Parse(docTextBuilder.ToString()).Element("Modpack").Attribute("Name").Value);
                                return(12);
                            }

                            gameInstallPath = new DirectoryInfo(gameInstallPath).FullName;
                            docTextBuilder  = docTextBuilder.Replace("{SkyrimInstallFolder}", gameInstallPath)
                                              .Replace("{SkyrimInstallFolderForwardSlashes}", gameInstallPath.Replace(Path.DirectorySeparatorChar, '/'))
                                              .Replace("{SkyrimInstallFolderEscapeBackslashes}", gameInstallPath.Replace("\\", "\\\\"));
                        }

                        doc = XDocument.Parse(docTextBuilder.ToString());
                    }

                doc = doc.PoolStrings(pool);

                foreach (var modpackElement in doc.Descendants("Modpack"))
                {
                    var modpack = new Modpack(modpackElement);
                    modpacks.Add(modpack);

                    var currentToolVersion = Assembly.GetExecutingAssembly().GetName().Version;
                    if (currentToolVersion < modpack.MinimumToolVersion)
                    {
                        Console.Error.WriteLine("Current tool version ({0}) is lower than the minimum tool version ({1}) required for this pack.", currentToolVersion, modpack.MinimumToolVersion);
                        return(3);
                    }

                    if (modpack.MinimumToolVersion < new Version("2.0.0.0"))
                    {
                        Console.Error.WriteLine("Modpacks designed for tool versions earlier than 2.x are no longer supported in tool versions 2.x and above.");
                        return(15);
                    }

                    if (!seenSoFar.IsSupersetOf(modpack.Requirements))
                    {
                        Console.Error.WriteLine("{0} needs to be set up in the same run, after all of the following are set up as well: {1}", modpack.Name, String.Join(", ", modpack.Requirements));
                        return(4);
                    }

                    if (seenSoFar.Contains(modpack.Name))
                    {
                        Console.Error.WriteLine("Trying to set up {0} twice in the same run", modpack.Name);
                        return(5);
                    }

                    seenSoFar.Add(modpack.Name);
                    seenSoFar.Add(modpack.Name + ", " + modpack.PackVersion);
                    seenSoFar.Add(modpack.Name + ", " + modpack.PackVersion + ", " + modpack.FileVersion);

                    if (longestOutputPathLength < modpack.LongestOutputPathLength)
                    {
                        longestOutputPathLength = modpack.LongestOutputPathLength.Value;
                    }

                    switch (modpack.Game)
                    {
                    case Game.Unknown:
                        break;

                    case Game.Skyrim2011:
                        if (requiresSkyrim)
                        {
                            break;
                        }

                        gameInstallPath = GetSkyrimDirectoryPath(options);
                        if (String.IsNullOrEmpty(gameInstallPath))
                        {
                            Console.Error.WriteLine("-s / --steamFolder, or a valid Skyrim registry key, is required for {0}.", modpack.Name);
                            return(12);
                        }

                        gameInstallPath = new DirectoryInfo(gameInstallPath).FullName;
                        requiresSkyrim  = true;
                        break;

                    default:
                        Console.Error.WriteLine("Unrecognized game: {0}", modpack.Game);
                        return(13);
                    }

                    if (requiresSkyrim)
                    {
                        sourceDirectories.Add(gameDataPath = Path.Combine(gameInstallPath, "Data"));
                    }
                }
            }

            // minus 1 for the path separator char.
            longestOutputPathLength = 255 - longestOutputPathLength - 1;
            if (!options.SkipOutputDirectoryPathLengthCheck &&
                longestOutputPathLength < dumpDirectory.FullName.Length)
            {
                Console.Error.WriteLine(Invariant($"Output directory ({options.OutputDirectoryPath}, {options.OutputDirectoryPath.Length} chars) exceeds the maximum supported length of {longestOutputPathLength} chars."));
                return(7);
            }

            Console.WriteLine("Checking existing files...");

            var groups = modpacks.SelectMany(
                modpack => modpack
                .CheckedFiles
                .SelectMany(grp => grp.CheckedFiles)
                .GroupBy(fl => modpack.Name + "|" + (fl.Option ?? fl.Name)))
                         .ToArray();

            // we hit all the files in the Skyrim directory, which has lots of gigabytes of BSAs we
            // don't care about, and the MO download folder itself might have tons of crap we don't
            // care about either.  quick and easy fix is to just look at files whose lengths match.
            // not absolutely 100% perfect, but good enough.
            var sizes = new Dictionary <long, Hashes>();

            foreach (var fl in groups.SelectMany(grp => grp))
            {
                if (!sizes.TryGetValue(fl.LengthInBytes, out var hashesToCheck))
                {
                    hashesToCheck = Hashes.Md5;
                }

                if (fl.Sha512Checksum != default)
                {
                    hashesToCheck |= Hashes.Sha512;
                }

                sizes[fl.LengthInBytes] = hashesToCheck;
            }

            var sourceFiles = sourceDirectories.Distinct(StringComparer.OrdinalIgnoreCase)
                              .Select(dir => new DirectoryInfo(dir).EnumerateFiles()
                                      .Where(fl => sizes.ContainsKey(fl.Length))
                                      .Select(fl => (fl, sizes[fl.Length]))