private static void RunApplication(ProgramArgs args)
        {
            // Simple convention for files:
            //    - Source data is stored in <build path>/src/<package name>
            //    - Output data will always be in the same folder

            var configPath = args.BuildConfigPath;

            if (!File.Exists(configPath))
            {
                throw new Exception($"Could not find build config file. Looking for: {configPath}");
            }

            var buildConfig = Serialization.Deserialize <BuildConfig>(File.ReadAllText(configPath));

            foreach (var buildConfigPackage in buildConfig.Packages)
            {
                BuildPackage(args, buildConfigPackage);
            }

            if (buildConfig.GenerateIndex)
            {
                var buildIndex = new BuildIndex();
                buildIndex.BuiltAt = DateTime.Now;
                buildIndex.Entries = new List <BuildIndexEntry>();

                foreach (var buildConfigPackage in buildConfig.Packages)
                {
                    var outPath  = Path.Combine(args.OutPath, buildConfigPackage.DistributionName + ".mods");
                    var checksum = CryptUtil.SHA1File(outPath);

                    buildIndex.Entries.Add(new BuildIndexEntry
                    {
                        Checksum = checksum,
                        Name     = buildConfigPackage.DistributionName + ".mods"
                    });
                }

                File.WriteAllText(Path.Combine(args.OutPath, "index.json"), Serialization.Serialize(buildIndex));
            }
        }
        private static void BuildPackage(ProgramArgs args, BuildConfigPackage buildConfigPackage)
        {
            var packageBasePath   = Path.Combine(Path.GetDirectoryName(args.BuildConfigPath) ?? "", "src", buildConfigPackage.SourceName);
            var packageConfigPath = Path.Combine(packageBasePath, "config.json");

            if (!File.Exists(packageConfigPath))
            {
                throw new Exception($"Could not find config file for package {buildConfigPackage.SourceName} ({buildConfigPackage.DistributionName}): looking for {packageConfigPath}");
            }

            var packageConfig = Serialization.Deserialize <PackageConfig>(File.ReadAllText(packageConfigPath));
            var masterKey     = new byte[0];

            Console.WriteLine($"Building: {buildConfigPackage.SourceName} ({buildConfigPackage.DistributionName})");


            var outPath = Path.Combine(args.OutPath, buildConfigPackage.DistributionName + ".mods");

            using var fs = File.Open(outPath, FileMode.Create, FileAccess.Write);
            using var ms = new MemoryStream();
            var packageHeader = new PackageHeader
            {
                Magic = 0x4459495A,
                CompilationTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
                EncryptionEnabled    = packageConfig.EncryptFiles
            };

            if (packageConfig.EncryptFiles)
            {
                packageHeader.KeyLength = KeyLength;
                masterKey = CryptUtil.GenerateKey(KeyLength);
            }

            BinaryUtils.MarshalStruct(ms, packageHeader);

            if (packageConfig.EncryptFiles)
            {
                byte[] xorTable =
                {
                    0x94, 0xce, 0xc3, 0xae, 0x73, 0xf9, 0xf1, 0xb9
                };

                for (var i = 0; i < masterKey.Length; i++)
                {
                    ms.WriteByte((byte)(masterKey[i] ^ xorTable[i % xorTable.Length]));
                }
            }

            using (var zipFile = new ZipFile())
            {
                if (packageConfig.EncryptFiles)
                {
                    zipFile.Encryption = EncryptionAlgorithm.WinZipAes256;
                    zipFile.Password   = Encoding.ASCII.GetString(masterKey);
                }

                foreach (var packageConfigEntry in packageConfig.Entries)
                {
                    ProcessPackageEntry(args, packageConfigEntry, buildConfigPackage, zipFile);
                }

                Console.WriteLine($"Saving: {buildConfigPackage.SourceName} ({buildConfigPackage.DistributionName})");

                zipFile.Save(ms);
            }

            Console.WriteLine($"Flushing: {buildConfigPackage.SourceName} ({buildConfigPackage.DistributionName}) -> {outPath}");
            ms.Position = 0;
            ms.CopyTo(fs);
        }