コード例 #1
0
ファイル: CASContainer.cs プロジェクト: roblankey/CASCHost
 public static void OpenCdnIndices(bool load)
 {
     if (CDNConfig != null)
     {
         CDNIndexHandler = new CDNIndexHandler(load);
     }
 }
コード例 #2
0
ファイル: CASCConfig.cs プロジェクト: DeKaDeNcE/Tools
        public static CASCConfig LoadOnlineStorageConfig(string product, string region, bool useCurrentBuild = false)
        {
            var config = new CASCConfig {
                OnlineMode = true, Region = region, Product = product
            };

            using (var ribbit = new RibbitClient("us"))
                using (var cdnsStream = ribbit.GetAsStream($"v1/products/{product}/cdns"))
                {
                    config._CDNData = VerBarConfig.ReadVerBarConfig(cdnsStream);
                }

            using (var ribbit = new RibbitClient("us"))
                using (var versionsStream = ribbit.GetAsStream($"v1/products/{product}/versions"))
                {
                    config._VersionsData = VerBarConfig.ReadVerBarConfig(versionsStream);
                }

            for (int i = 0; i < config._VersionsData.Count; ++i)
            {
                if (config._VersionsData[i]["Region"] == region)
                {
                    config._versionsIndex = i;
                    break;
                }
            }

            CDNCache.Init(config);

            config.GameType = CASCGame.DetectGameByUid(product);

            if (File.Exists("fakecdnconfig"))
            {
                using Stream stream = new FileStream("fakecdnconfig", FileMode.Open);
                config._CDNConfig   = KeyValueConfig.ReadKeyValueConfig(stream);
            }
            else
            {
                string cdnKey = config._VersionsData[config._versionsIndex]["CDNConfig"].ToLower();
                //string cdnKey = "da4896ce91922122bc0a2371ee114423";
                using Stream stream = CDNIndexHandler.OpenConfigFileDirect(config, cdnKey);
                config._CDNConfig   = KeyValueConfig.ReadKeyValueConfig(stream);
            }

            config.ActiveBuild = 0;

            config._Builds = new List <KeyValueConfig>();

            if (config._CDNConfig["builds"] != null)
            {
                for (int i = 0; i < config._CDNConfig["builds"].Count; i++)
                {
                    try
                    {
                        using Stream stream = CDNIndexHandler.OpenConfigFileDirect(config, config._CDNConfig["builds"][i]);
                        var cfg = KeyValueConfig.ReadKeyValueConfig(stream);
                        config._Builds.Add(cfg);
                    }
                    catch
                    {
                    }
                }

                if (useCurrentBuild)
                {
                    string curBuildKey = config._VersionsData[config._versionsIndex]["BuildConfig"];

                    int buildIndex = config._CDNConfig["builds"].IndexOf(curBuildKey);

                    if (buildIndex != -1)
                    {
                        config.ActiveBuild = buildIndex;
                    }
                }
            }

            if (File.Exists("fakebuildconfig"))
            {
                using Stream stream = new FileStream("fakebuildconfig", FileMode.Open);
                var cfg = KeyValueConfig.ReadKeyValueConfig(stream);
                config._Builds.Add(cfg);
            }
            else
            {
                string buildKey = config._VersionsData[config._versionsIndex]["BuildConfig"].ToLower();
                //string buildKey = "3b0517b51edbe0b96f6ac5ea7eaaed38";
                using Stream stream = CDNIndexHandler.OpenConfigFileDirect(config, buildKey);
                var cfg = KeyValueConfig.ReadKeyValueConfig(stream);
                config._Builds.Add(cfg);
            }

            return(config);
        }
コード例 #3
0
ファイル: ClientHandler.cs プロジェクト: HeroesReplay/TACTLib
        public ClientHandler(string basePath, ClientCreateArgs createArgs)
        {
            basePath   = basePath ?? "";
            BasePath   = basePath;
            CreateArgs = createArgs;
            string flavorInfoProductCode = null;

            if (createArgs.UseContainer && !Directory.Exists(basePath))
            {
                throw new FileNotFoundException("invalid archive directory");
            }

            var dbPath = Path.Combine(basePath, createArgs.ProductDatabaseFilename);

            try {
                if (File.Exists(dbPath))
                {
                    using (var _ = new PerfCounter("AgentDatabase::ctor`string`bool"))
                        foreach (var install in new AgentDatabase(dbPath).Data.ProductInstall)
                        {
                            if (string.IsNullOrEmpty(createArgs.Flavor) || install.Settings.GameSubfolder.Contains(createArgs.Flavor))
                            {
                                AgentProduct = install;
                                break;
                            }
                        }

                    if (AgentProduct == null)
                    {
                        throw new InvalidDataException();
                    }

                    Product = ProductHelpers.ProductFromUID(AgentProduct.ProductCode);
                }
                else
                {
                    throw new InvalidDataException();
                }
            } catch {
                try {
                    if (File.Exists(Path.Combine(basePath, ".flavor.info")))
                    {
                        // mixed installation, store the product code to be used below
                        flavorInfoProductCode = File.ReadLines(Path.Combine(basePath, ".flavor.info")).Skip(1).First();
                        Product  = ProductHelpers.ProductFromUID(flavorInfoProductCode);
                        BasePath = basePath = Path.Combine(basePath, "../"); // lmao

                        Logger.Info("Core", $".flavor.info detected. Found product \"{flavorInfoProductCode}\"");
                    }
                    else
                    {
                        throw new InvalidDataException();
                    }
                } catch {
                    try {
                        Product = ProductHelpers.ProductFromLocalInstall(basePath);
                    } catch {
                        if (createArgs.VersionSource == ClientCreateArgs.InstallMode.Local)    // if we need an archive then we should be able to detect the product
                        {
                            throw;
                        }

                        Product = createArgs.OnlineProduct;
                    }
                }

                AgentProduct = new ProductInstall {
                    ProductCode = flavorInfoProductCode ?? createArgs.Product ?? ProductHelpers.UIDFromProduct(Product),
                    Settings    = new UserSettings {
                        SelectedTextLanguage   = createArgs.TextLanguage ?? "enUS",
                        SelectedSpeechLanguage = createArgs.SpeechLanguage ?? "enUS",
                        PlayRegion             = "us"
                    }
                };

                if (AgentProduct.Settings.SelectedSpeechLanguage == AgentProduct.Settings.SelectedTextLanguage)
                {
                    AgentProduct.Settings.Languages.Add(new LanguageSetting {
                        Language = AgentProduct.Settings.SelectedTextLanguage,
                        Option   = LanguageOption.LangoptionTextAndSpeech
                    });
                }
                else
                {
                    AgentProduct.Settings.Languages.Add(new LanguageSetting {
                        Language = AgentProduct.Settings.SelectedTextLanguage,
                        Option   = LanguageOption.LangoptionText
                    });

                    AgentProduct.Settings.Languages.Add(new LanguageSetting {
                        Language = AgentProduct.Settings.SelectedSpeechLanguage,
                        Option   = LanguageOption.LangoptionSpeech
                    });
                }
            }

            if (string.IsNullOrWhiteSpace(createArgs.TextLanguage))
            {
                createArgs.TextLanguage = AgentProduct.Settings.SelectedTextLanguage;
            }

            if (string.IsNullOrWhiteSpace(createArgs.SpeechLanguage))
            {
                createArgs.SpeechLanguage = AgentProduct.Settings.SelectedSpeechLanguage;
            }

            if (createArgs.Online)
            {
                using var _ = new PerfCounter("INetworkHandler::ctor`ClientHandler");
                if (createArgs.OnlineRootHost.StartsWith("ribbit:"))
                {
                    NetHandle = new RibbitCDNClient(this);
                }
                else
                {
                    NetHandle = new NGDPClient(this);
                }
            }

            if (createArgs.VersionSource == ClientCreateArgs.InstallMode.Local)
            {
                var installationInfoPath = Path.Combine(basePath, createArgs.InstallInfoFileName) + createArgs.ExtraFileEnding;
                if (!File.Exists(installationInfoPath))
                {
                    throw new FileNotFoundException(installationInfoPath);
                }

                using var _      = new PerfCounter("InstallationInfo::ctor`string");
                InstallationInfo = new InstallationInfo(installationInfoPath, AgentProduct.ProductCode);
            }
            else
            {
                using var _      = new PerfCounter("InstallationInfo::ctor`INetworkHandler");
                InstallationInfo = new InstallationInfo(NetHandle, createArgs.OnlineRegion);
            }

            Logger.Info("CASC", $"{Product} build {InstallationInfo.Values["Version"]}");

            if (createArgs.UseContainer)
            {
                Logger.Info("CASC", "Initializing...");
                using var _      = new PerfCounter("ContainerHandler::ctor`ClientHandler");
                ContainerHandler = new ContainerHandler(this);
            }

            using (var _ = new PerfCounter("ConfigHandler::ctor`ClientHandler"))
                ConfigHandler = new ConfigHandler(this);

            using (var _ = new PerfCounter("EncodingHandler::ctor`ClientHandler"))
                EncodingHandler = new EncodingHandler(this);

            if (ConfigHandler.BuildConfig.VFSRoot != null)
            {
                using var _ = new PerfCounter("VFSFileTree::ctor`ClientHandler");
                VFS         = new VFSFileTree(this);
            }

            if (createArgs.Online)
            {
                m_cdnIdx = CDNIndexHandler.Initialize(this);
            }

            using (var _ = new PerfCounter("ProductHandlerFactory::GetHandler`TACTProduct`ClientHandler`Stream"))
                ProductHandler = ProductHandlerFactory.GetHandler(Product, this, OpenCKey(ConfigHandler.BuildConfig.Root.ContentKey));

            Logger.Info("CASC", "Ready");
        }
コード例 #4
0
ファイル: CASContainer.cs プロジェクト: roblankey/CASCHost
        public static void Save()
        {
            Settings.Cache?.Clean();

            //Patch exe
            //Patcher.Run("http://" + CASContainer.Settings.Host);

            // Entries
            var entries = SaveEntries().Result;

            // CDN Archives
            Settings.Logger.LogInformation("Starting CDN Index.");
            CDNIndexHandler?.CreateArchive(entries);

            // Root
            Settings.Logger.LogInformation("Starting Root.");
            foreach (var entry in entries)
            {
                RootHandler.AddEntry(entry.Path, entry);
            }
            entries.Add(RootHandler.Write());             //Add to entry list

            // Download
            if (DownloadHandler != null)
            {
                Settings.Logger.LogInformation("Starting Download.");

                foreach (var entry in entries)
                {
                    DownloadHandler.AddEntry(entry);
                }
                entries.Add(DownloadHandler.Write());                 //Add to entry list
            }

            if (InstallHandler != null)
            {
                Settings.Logger.LogInformation("Starting Install.");
                var installManifest = InstallHandler.Write(entries);

                if (installManifest != null)
                {
                    entries.Add(installManifest);                     //Add to entry list
                }
            }

            // Encoding
            Settings.Logger.LogInformation("Starting Encoding.");
            foreach (var entry in entries)
            {
                EncodingHandler.AddEntry(entry);
            }
            entries.Insert(0, EncodingHandler.Write());


            Settings.Logger.LogInformation("Starting Configs.");

            // CDN Config
            CDNConfig.Remove("archive-group");
            CDNConfig.Remove("patch-archives");
            CDNConfig.Remove("patch-archive-group");

            // Build Config
            BuildConfig.Set("patch", "");
            BuildConfig.Set("patch-size", "0");
            BuildConfig.Set("patch-config", "");

            string buildconfig = BuildConfig.Write();
            string cdnconfig   = CDNConfig.Write();
            string version     = BuildInfo["Version"];

            // Build Info - redundant
            BuildInfo["Build Key"] = buildconfig;
            BuildInfo["CDN Key"]   = cdnconfig;
            BuildInfo["CDN Hosts"] = string.Join(" ", Settings.CDNs);
            BuildInfo.Write();

            // CDNs file
            CDNs["Hosts"] = string.Join(" ", Settings.CDNs);
            CDNs.Write();

            // Versions file
            Versions["BuildConfig"]  = buildconfig;
            Versions["CDNConfig"]    = cdnconfig;
            Versions["VersionsName"] = version;
            Versions["BuildId"]      = version.Split('.').Last();
            Versions.Write();

            // Done!
            Logger.LogInformation("CDN Config: " + cdnconfig);
            Logger.LogInformation("Build Config: " + buildconfig);

            // update Cache files
            Settings.Cache?.Save();

            // cleanup
            entries.Clear();
            entries.TrimExcess();
            Close();
        }
コード例 #5
0
        private CASCHandler(CASCConfig config, ProgressReportSlave worker)
        {
            Config = config;

            if (!config.OnlineMode)
            {
                Debugger.Log(0, "CASC", "CASCHandler: loading local indices\r\n");

                using (PerfCounter _ = new PerfCounter("LocalIndexHandler.Initialize()")) {
                    LocalIndex = LocalIndexHandler.Initialize(config, worker);
                }

                Debugger.Log(0, "CASC", $"CASCHandler: loaded {LocalIndex.Count} local indices\r\n");
            }
            else      // todo: supposed to do this?
            {
                Debugger.Log(0, "CASC", "CASCHandler: loading CDN indices\r\n");

                using (PerfCounter _ = new PerfCounter("CDNIndexHandler.Initialize()")) {
                    CDNIndex = CDNIndexHandler.Initialize(config, worker, Cache);
                }

                Debugger.Log(0, "CASC", $"CASCHandler: loaded {CDNIndex.Count} CDN indexes\r\n");
            }

            Debugger.Log(0, "CASC", "CASCHandler: loading encoding entries\r\n");
            using (PerfCounter _ = new PerfCounter("new EncodingHandler()")) {
                using (BinaryReader encodingReader = OpenEncodingKeyFile()) {
                    EncodingHandler = new EncodingHandler(encodingReader, worker);
                }
            }
            Debugger.Log(0, "CASC", $"CASCHandler: loaded {EncodingHandler.Count} encoding entries\r\n");

            Debugger.Log(0, "CASC", "CASCHandler: loading root data\r\n");
            using (PerfCounter _ = new PerfCounter("new RootHandler()")) {
                using (BinaryReader rootReader = OpenRootKeyFile()) {
                    RootHandler = new RootHandler(rootReader, worker, this);
                }
            }

            //if ((CASCConfig.LoadFlags & LoadFlags.Download) != 0)
            //{
            //    Debugger.Log(0, "CASC", "CASCHandler: loading download data\r\n");
            //    using (var _ = new PerfCounter("new DownloadHandler()"))
            //    {
            //        using (BinaryReader fs = OpenDownloadFile(EncodingHandler))
            //            DownloadHandler = new DownloadHandler(fs, worker);
            //    }
            //    Debugger.Log(0, "CASC", $"CASCHandler: loaded {EncodingHandler.Count} download data\r\n");
            //}

            //if ((CASCConfig.LoadFlags & LoadFlags.Install) != 0) {
            //    Debugger.Log(0, "CASC", "CASCHandler: loading install data\r\n");
            //    using (var _ = new PerfCounter("new InstallHandler()"))
            //    {
            //        using (var fs = OpenInstallFile(EncodingHandler))
            //            InstallHandler = new InstallHandler(fs, worker);
            //        InstallHandler.Print();
            //    }
            //    Debugger.Log(0, "CASC", $"CASCHandler: loaded {InstallHandler.Count} install data\r\n");
            //}
        }
コード例 #6
0
ファイル: ClientHandler.cs プロジェクト: overtools/TACTLib
        public ClientHandler(string?basePath, ClientCreateArgs createArgs)
        {
            BasePath    = basePath ?? ""; // should it be empty string? lol
            CreateArgs  = createArgs;
            ProductCode = createArgs.Product;

            // If we are using a container OR if InstallMode == Local
            if (createArgs.UseContainer)
            {
                if (!Directory.Exists(BasePath))
                {
                    throw new FileNotFoundException($"Invalid archive directory. Directory {BasePath} does not exist. Please specify a valid directory.");
                }

                try {
                    // if someone specified a flavor, try and see what flavor and fix the base path
                    var flavorInfoPath = Path.Combine(BasePath, ".flavor.info");
                    if (File.Exists(flavorInfoPath))
                    {
                        // mixed installation, store the product code to be used below
                        ProductCode = File.ReadLines(flavorInfoPath).Skip(1).First();
                        Product     = ProductHelpers.ProductFromUID(ProductCode);
                        BasePath    = Path.GetFullPath(Path.Combine(BasePath, "../")); // base path is a directory up from the flavor

                        Logger.Info("Core", $".flavor.info detected. Found product \"{ProductCode}\"");
                    }
                } catch (Exception ex) {
                    Logger.Warn("Core", $"Failed reading .flavor.info file! {ex.Message}");
                }

                // ensure to see the .build.info file exists. if it doesn't then we can't continue
                InstallationInfoPath = Path.Combine(BasePath, createArgs.InstallInfoFileName) + createArgs.ExtraFileEnding;
                if (!File.Exists(InstallationInfoPath))
                {
                    throw new FileNotFoundException($"Invalid archive directory! {InstallationInfoPath} was not found. You must provide the path to a valid install.");
                }

                // If there was no flavor specified, try to find the flavor in the .build.info file
                ProductCode ??= createArgs.Product ?? ProductHelpers.TryGetUIDFromProduct(ProductHelpers.TryGetProductFromLocalInstall(BasePath));
                InstallationInfoFile = new InstallationInfoFile(InstallationInfoPath);

                // If product is unknown it means we loaded on the base path and not a flavor e.g. C:/Games/Overwatch
                // so we need to load the .build.info file and get the product from there
                if (Product == TACTProduct.Unknown)
                {
                    var installationInfo = InstallationInfoFile.GetInstallationInfoForProduct(ProductCode);

                    if (installationInfo == null)
                    {
                        Logger.Warn("Core", $"Failed to find product \"{ProductCode}\" in {createArgs.InstallInfoFileName} file! Using first available.");
                        installationInfo = InstallationInfoFile.GetFirstOrDefault();
                    }

                    // if there's no data in the .build.info file? Shouldn't really be possible
                    if (installationInfo == null)
                    {
                        throw new Exception($"Failed to find a valid product in {createArgs.InstallInfoFileName} file!");
                    }

                    // If product code is null this ProductFromUID will throw an exception
                    ProductCode = installationInfo.Values.GetValueOrDefault("Product");
                    Product     = ProductHelpers.ProductFromUID(ProductCode);
                    Logger.Info("Core", $"Found product \"{ProductCode}\" via {createArgs.InstallInfoFileName}");
                }
            }

            // If there is no product specified it's because we aren't using a container or we're using online mode.
            // Find the product from the productCode provided by createArgs or if there is none, find it from the local install path
            // tho i'm not sure what the chances are of there being an install path provided if you're loading from remote as it would be kind of redundant but whatever
            if (Product == TACTProduct.Unknown)
            {
                Product = ProductHelpers.TryGetProductFromUID(ProductCode);

                // if no product was specified via ClientCreateArgs, try and find it from the local install path
                if (Product == TACTProduct.Unknown)
                {
                    Product = ProductHelpers.TryGetProductFromLocalInstall(BasePath);
                }

                if (Product == TACTProduct.Unknown)
                {
                    if (createArgs.VersionSource == ClientCreateArgs.InstallMode.Remote)
                    {
                        throw new Exception("Failed to determine TACT Product. This is required if you're loading from remote.");
                    }

                    Logger.Warn("Core", "Failed to determine TACT Product! This could potentially cause issues!");
                }
            }

            if (createArgs.Online)
            {
                using var _ = new PerfCounter("INetworkHandler::ctor`ClientHandler");
                if (createArgs.OnlineRootHost.StartsWith("ribbit:"))
                {
                    NetHandle = new RibbitCDNClient(this);
                }
                else
                {
                    NetHandle = new NGDPClient(this);
                }
            }

            if (createArgs.VersionSource == ClientCreateArgs.InstallMode.Local)
            {
                // should always exist as it's fetched above but we can't continue without being able to load the installation info
                if (!File.Exists(InstallationInfoPath))
                {
                    throw new FileNotFoundException(InstallationInfoPath);
                }

                using var _      = new PerfCounter("InstallationInfo::ctor`string");
                InstallationInfo = new InstallationInfo(InstallationInfoPath !, ProductCode !);
            }
            else
            {
                using var _      = new PerfCounter("InstallationInfo::ctor`INetworkHandler");
                InstallationInfo = new InstallationInfo(NetHandle !, createArgs.OnlineRegion);
            }

            // try to load the agent database and use the selected language if we don't already have one specified
            if (createArgs.UseContainer)
            {
                AgentProduct = TryGetAgentDatabase();
                if (AgentProduct != null)
                {
                    if (string.IsNullOrWhiteSpace(createArgs.TextLanguage))
                    {
                        createArgs.TextLanguage = AgentProduct.Settings.SelectedTextLanguage;
                        CreateArgs.TextLanguage = AgentProduct.Settings.SelectedTextLanguage;
                    }

                    if (string.IsNullOrWhiteSpace(createArgs.SpeechLanguage))
                    {
                        createArgs.SpeechLanguage = AgentProduct.Settings.SelectedSpeechLanguage;
                        CreateArgs.SpeechLanguage = AgentProduct.Settings.SelectedSpeechLanguage;
                    }
                }
            }

            Logger.Info("CASC", $"{Product} build {InstallationInfo.Values["Version"]}");

            if (createArgs.UseContainer)
            {
                Logger.Info("CASC", "Initializing...");
                using var _      = new PerfCounter("ContainerHandler::ctor`ClientHandler");
                ContainerHandler = new ContainerHandler(this);
            }

            using (var _ = new PerfCounter("ConfigHandler::ctor`ClientHandler"))
                ConfigHandler = new ConfigHandler(this);

            using (var _ = new PerfCounter("EncodingHandler::ctor`ClientHandler"))
                EncodingHandler = new EncodingHandler(this);

            if (ConfigHandler.BuildConfig.VFSRoot != null)
            {
                using var _ = new PerfCounter("VFSFileTree::ctor`ClientHandler");
                VFS         = new VFSFileTree(this);
            }

            if (createArgs.Online)
            {
                m_cdnIdx = CDNIndexHandler.Initialize(this);
            }

            using (var _ = new PerfCounter("ProductHandlerFactory::GetHandler`TACTProduct`ClientHandler`Stream"))
                ProductHandler = ProductHandlerFactory.GetHandler(Product, this, OpenCKey(ConfigHandler.BuildConfig.Root.ContentKey) !);

            Logger.Info("CASC", "Ready");
        }
コード例 #7
0
        /// <summary>
        ///     Loads data from disk
        /// </summary>
        /// <param name="basePath"></param>
        /// <param name="useKeyring"></param>
        /// <param name="loadMultipleLangs"></param>
        /// <returns></returns>
        public static CASCConfig LoadLocalStorageConfig(string basePath, bool useKeyring, bool loadMultipleLangs)
        {
            CASCConfig config = new CASCConfig {
                OnlineMode = false,
                BasePath   = basePath
            };

            try {
                string productDbPath = Path.Combine(basePath, ".product.db");
                config.InstallData = new ProductDatabase(productDbPath, true).Data.ProductInstalls[0];
            } catch { }

            config.SpeechLanguage = config.InstallData?.Settings?.SelectedSpeechLanguage ?? "enUS";
            config.TextLanguage   = config.InstallData?.Settings?.SelectedTextLanguage ?? "enUS";

            string buildInfoPath = Path.Combine(basePath, ".build.info");

            using (Stream buildInfoStream = new FileStream(buildInfoPath, FileMode.Open)) {
                config._buildInfo = BarSeparatedConfig.Read(buildInfoStream);
            }

            Dictionary <string, string> bi = config.GetActiveBuild();

            if (bi == null)
            {
                throw new Exception("Can't find active BuildInfoEntry");
            }

            string dataFolder = GetDataFolder();

            config.ActiveBuild = 0;

            config.Builds = new List <KeyValueConfig>();

            string buildKey     = bi["BuildKey"];
            string buildCfgPath = Path.Combine(basePath, dataFolder, "config", buildKey.Substring(0, 2), buildKey.Substring(2, 2), buildKey);

            try {
                using (Stream stream = new FileStream(buildCfgPath, FileMode.Open)) {
                    config.Builds.Add(KeyValueConfig.Read(stream));
                }
            } catch {
                using (Stream stream = CDNIndexHandler.OpenConfigFileDirect(config, buildKey)) {
                    config.Builds.Add(KeyValueConfig.Read(stream));
                }
            }

            string cdnKey     = bi["CDNKey"];
            string cdnCfgPath = Path.Combine(basePath, dataFolder, "config", cdnKey.Substring(0, 2), cdnKey.Substring(2, 2), cdnKey);

            try {
                using (Stream stream = new FileStream(cdnCfgPath, FileMode.Open)) {
                    config._cdnConfig = KeyValueConfig.Read(stream);
                }
            } catch {
                using (Stream stream = CDNIndexHandler.OpenConfigFileDirect(config, cdnKey)) {
                    config._cdnConfig = KeyValueConfig.Read(stream);
                }
            }

            if (bi.ContainsKey("Keyring") && bi["Keyring"].Length > 0)
            {
                string keyringKey     = bi["Keyring"];
                string keyringCfgPath = Path.Combine(basePath, dataFolder, "config", keyringKey.Substring(0, 2), keyringKey.Substring(2, 2), keyringKey);
                try {
                    using (Stream stream = new FileStream(keyringCfgPath, FileMode.Open)) {
                        config.KeyRing = KeyValueConfig.Read(stream);
                    }
                } catch {
                    using (Stream stream = CDNIndexHandler.OpenConfigFileDirect(config, keyringKey)) {
                        config.KeyRing = KeyValueConfig.Read(stream);
                    }
                }
                if (useKeyring)
                {
                    config.LoadKeyringKeys(TACTKeyService.Keys, true);
                }
            }

            config.InstalledLanguages        = new HashSet <string>();
            config.LoadAllInstalledLanguages = loadMultipleLangs;
            if (bi.ContainsKey("Tags") && bi["Tags"].Trim().Length > 0)
            {
                string[] tags = bi["Tags"].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                foreach (string tag in tags)
                {
                    try {
                        Enum.Parse(typeof(LocaleFlags), tag.Substring(0, 4));
                        config.InstalledLanguages.Add(tag);
                    } catch { }
                }
            }

            // for debugging:
            //var buildInfo = config.Builds[config.ActiveBuild];

            return(config);
        }