示例#1
0
        /// <summary>
        /// Get container directory from product type
        /// </summary>
        /// <param name="product">Target product</param>
        /// <returns>Container directory</returns>
        /// <exception cref="NotImplementedException">Product is unsupported</exception>
        public static string GetContainerDirectory(TACTProduct product)
        {
            if (product == TACTProduct.HeroesOfTheStorm)
            {
                return("HeroesData");
            }

            if (product == TACTProduct.StarCraft2)
            {
                return("SC2Data");
            }

            if (product == TACTProduct.Hearthstone)
            {
                return("Hearthstone_Data");
            }

            if (product == TACTProduct.Warcraft3 || product == TACTProduct.WorldOfWarcraft || product == TACTProduct.Diablo3 ||
                product == TACTProduct.BlackOps4 || product == TACTProduct.ModernWarfare)
            {
                return("Data");
            }

            if (product == TACTProduct.Overwatch)
            {
                return(Path.Combine("data", "casc"));
            }

            throw new NotImplementedException($"Product \"{product}\" is not supported.");
        }
示例#2
0
 public static Type GetHandlerType(TACTProduct product)
 {
     if (!_handlers.TryGetValue(product, out var type))
     {
         type = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(x => typeof(IProductHandler).IsAssignableFrom(x) && x.GetCustomAttributes <ProductHandlerAttribute>().Any(i => i.Product == product));
     }
     return(type);
 }
示例#3
0
 public static string?TryGetUIDFromProduct(TACTProduct product)
 {
     try {
         return(UIDFromProduct(product));
     } catch {
         return(null);
     }
 }
示例#4
0
        public static IProductHandler GetHandler(TACTProduct product, ClientHandler client, Stream root)
        {
            var handlerType = GetHandlerType(product);

            if (handlerType == null)
            {
                return(null);
            }

            using (var _ = new PerfCounter($"{handlerType.Name}::ctor"))
                return((IProductHandler)Activator.CreateInstance(handlerType, client, root));
        }
示例#5
0
        /// <summary>
        /// Get product uid from <see cref="TACTProduct"/>
        /// </summary>
        /// <param name="product">product</param>
        /// <returns>Product type</returns>
        /// <exception cref="ArgumentOutOfRangeException">Product is unknown</exception>
        public static string UIDFromProduct(TACTProduct product)
        {
            switch (product)
            {
            case TACTProduct.HeroesOfTheStorm:
                return("hero");

            case TACTProduct.Hearthstone:
                return("hsb");

            case TACTProduct.Warcraft3:
                return("w3");

            case TACTProduct.StarCraft1:
                return("s1");

            case TACTProduct.StarCraft2:
                return("s2");

            case TACTProduct.WorldOfWarcraft:
                return("wow");

            case TACTProduct.Diablo2:
                return("d2");

            case TACTProduct.Diablo3:
                return("d3");

            case TACTProduct.Agent:
                return("agent");

            case TACTProduct.Overwatch:
                return("pro");

            case TACTProduct.BattleNetApp:
                return("bna");

            case TACTProduct.Destiny2:
                return("dst2");

            case TACTProduct.BlackOps4:
                return("viper");

            case TACTProduct.Catalog:
                return("catalogs");

            case TACTProduct.ModernWarfare:
                return("odin");

            default:
                throw new ArgumentOutOfRangeException(nameof(product), product, null);
            }
        }
示例#6
0
        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");
        }
示例#7
0
        public static void GenerateKeyIV <T>(string name, string manifestType, T header, uint buildVersion, TACTProduct product, out byte[] key, out byte[] iv)
        {
            if (!_baseProvidersFound)
            {
                FindProviders();
                _baseProvidersFound = true;
            }

            if (!s_headerTypeToProviderType.TryGetValue(typeof(T), out var providerType))
            {
                throw new InvalidDataException($"[Manifest]: Unable to get crypto provider for {typeof(T)}");
            }
            if (!Providers.TryGetValue(product, out var cryptoTypeMap))
            {
                throw new InvalidDataException($"[Manifest]: {product} does not have any crypto providers?");
            }
            if (!cryptoTypeMap.TryGetValue(providerType, out var providerVersions))
            {
                throw new InvalidDataException($"[Manifest]: {product} does not have any {providerType} providers?");
            }

            byte[] digest = CreateDigest(name);

            if (providerVersions.TryGetValue(buildVersion, out var providerRaw))
            {
                Logger.Info("Manifest", $"Using {manifestType} procedure {buildVersion} for {name}");
            }
            else
            {
                if (!AttemptFallbackManifests)
                {
                    throw new UnsupportedBuildVersionException($"Build version {buildVersion} is not supported");
                }

                Logger.Warn("Manifest", $"No {manifestType} procedure for build {buildVersion}, trying closest version");
                try {
                    var pair = providerVersions.Where(it => it.Key < buildVersion).OrderByDescending(it => it.Key).First();
                    Logger.Info("Manifest", $"Using {manifestType} procedure {pair.Key}");
                    providerRaw = pair.Value;
                } catch {
                    throw new CryptographicException($"Missing {manifestType} generators");
                }
            }

            var provider = (IManifestCrypto <T>)providerRaw;

            key = provider.Key(header, 32);
            try {
                iv = provider.IV(header, digest, 16);
            } catch (Exception ex) {
                iv = new byte[16];
                Logger.Error("Manifest", $"Error generating IV but we dont care i guess: {ex}");
            }

            name = Path.GetFileNameWithoutExtension(name);
            Logger.Debug(manifestType, $"{name} key:{string.Join(" ", key.Select(x => x.ToString("X2")))}");
            Logger.Debug(manifestType, $"{name} iv:{string.Join(" ", iv.Select(x => x.ToString("X2")))}");
        }
示例#8
0
        public static BinaryReader GetDecryptedReader <T>(string name, string manifestType, T header, uint buildVersion, TACTProduct product, Stream stream)
        {
            GenerateKeyIV(name, manifestType, header, buildVersion, product, out byte[] key, out byte[] iv);

            using (Aes aes = Aes.Create()) {
                aes.KeySize      = 128;
                aes.FeedbackSize = 128;
                aes.BlockSize    = 128;
                aes.Key          = key;
                aes.IV           = iv;
                aes.Mode         = CipherMode.CBC;
                aes.Padding      = PaddingMode.None;
                var cryptoStream = new CryptoStream(stream, aes.CreateDecryptor(), CryptoStreamMode.Read);
                return(new BinaryReader(cryptoStream));
            }
        }
示例#9
0
 public static void SetHandler(TACTProduct product, Type type)
 {
     _handlers[product] = type;
 }
示例#10
0
 public ProductHandlerAttribute(TACTProduct product)
 {
     Product = product;
 }
示例#11
0
        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");
        }
示例#12
0
        public static void GenerateKeyIV(string name, ContentManifestFile.CMFHeader header, TACTProduct product, out byte[] key, out byte[] iv)
        {
            if (!_baseProvidersFound)
            {
                FindProviders();
                _baseProvidersFound = true;
            }

            byte[] digest = CreateDigest(name);

            ICMFEncryptionProc provider;

            if (Providers[product].ContainsKey(header.BuildVersion))
            {
                Logger.Info("CMF", $"Using CMF procedure {header.BuildVersion}");
                provider = Providers[product][header.BuildVersion];
            }
            else
            {
                Logger.Warn("CMF", $"No CMF procedure for build {header.BuildVersion}, trying closest version");
                try {
                    KeyValuePair <uint, ICMFEncryptionProc> pair = Providers[product].Where(it => it.Key < header.BuildVersion).OrderByDescending(it => it.Key).First();
                    Logger.Info("CMF", $"Using CMF procedure {pair.Key}");
                    provider = pair.Value;
                } catch {
                    throw new CryptographicException("Missing CMF generators");
                }
            }

            key = provider.Key(header, 32);
            iv  = provider.IV(header, digest, 16);

            name = Path.GetFileNameWithoutExtension(name);
            Logger.Debug("CMF", $"{name} key:{string.Join(" ", key.Select(x => x.ToString("X2")))}");
            Logger.Debug("CMF", $"{name} iv:{string.Join(" ", iv.Select(x => x.ToString("X2")))}");
        }