private void Initialize() { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("Loading local CASC storage...please wait"); Console.ResetColor(); TextWriter console = Console.Out; Stopwatch time = new Stopwatch(); using ManualResetEvent resetEvent = new ManualResetEvent(false); using BackgroundWorkerEx backgroundWorker = new BackgroundWorkerEx(); backgroundWorker.DoWork += (_, e) => { Console.SetOut(TextWriter.Null); // suppress output CASCConfig.ThrowOnFileNotFound = true; CASCConfig.ThrowOnMissingDecryptionKey = true; CASCConfig config = CASCConfig.LoadLocalStorageConfig(_storagePath); CASCHandler = CASCHandler.OpenStorage(config, backgroundWorker); LocaleFlags locale = LocaleFlags.All; Console.SetOut(console); // enable output CASCHandler.Root.LoadListFile(Path.Combine(Environment.CurrentDirectory, "listfile.txt"), backgroundWorker); Console.SetOut(TextWriter.Null); // suppress output CASCFolderRoot = CASCHandler.Root.SetFlags(locale); }; backgroundWorker.ProgressChanged += (_, e) => { // main thread is blocked, so push it on another thread Task.Run(() => { DrawProgressBar(e.ProgressPercentage, 100, 72, '#'); }); }; backgroundWorker.RunWorkerCompleted += (_, e) => { time.Stop(); Console.SetOut(console); // enable output Console.Write("\r"); DrawProgressBar(100, 100, 72, '#'); Console.WriteLine(); Console.WriteLine($"Finished in {time.Elapsed.TotalSeconds:0.####} seconds"); Console.WriteLine(); resetEvent.Set(); }; try { // use backgroundworker for progress reporting since it is provided by CASCLib // the main thread is blocked until the background process is completed time.Start(); backgroundWorker.RunWorkerAsync(); resetEvent.WaitOne(); } catch (Exception ex) { resetEvent.Set(); Console.SetOut(console); Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(); Console.WriteLine("Error while opening storage"); Console.WriteLine(ex.Message); Console.ResetColor(); Environment.Exit(1); } }
public void InitCasc(CASCConfig config, LocaleFlags firstInstalledLocale) { // Opens Storage cascHandler = CASCHandler.OpenStorage(config); cascHandler.Root.SetFlags(firstInstalledLocale, false); }
static void Main(string[] args) { // APMTool.exe "root" file flag [query] if (args.Length < 2) { Console.Out.WriteLine("Usage: APMTool.exe \"root directory\" op args"); Console.Out.WriteLine("OP f: Find files in APM. args: query... query: a[APM NAME] i[INDEX HEX] t[TYPE HEX] s[SIZE LESS THAN] S[SIZE GREATER THAN] N[INDEX WITHOUT IDENTIFIER]"); Console.Out.WriteLine("OP l: List files in package. args: query query: p[PACKAGE KEY HEX] i[CONTENT KEY HEX]"); Console.Out.WriteLine("OP a: Output all APM names"); Console.Out.WriteLine("OP t: Output all types"); Console.Out.WriteLine(""); Console.Out.WriteLine("Examples:"); Console.Out.WriteLine("APMTool.exe overwatch l iDFEF49BEE7E66774E46DA9EEA750A552"); Console.Out.WriteLine("APMTool.exe overwatch fa i11CE t00C"); Console.Out.WriteLine("APMTool.exe overwatch fa i4F2"); return; } string root = args[0]; string flag = args[1]; Console.Out.WriteLine("{0} v{1}", Assembly.GetExecutingAssembly().GetName().Name, OWLib.Util.GetVersion()); OwRootHandler.LOAD_PACKAGES = true; CASCConfig config = null; // ngdp:us:pro // http:us:pro:us.patch.battle.net:1119 if (root.ToLowerInvariant().Substring(0, 5) == "ngdp:") { string cdn = root.Substring(5, 4); string[] parts = root.Substring(5).Split(':'); string region = "us"; string product = "pro"; if (parts.Length > 1) { region = parts[1]; } if (parts.Length > 2) { product = parts[2]; } if (cdn == "bnet") { config = CASCConfig.LoadOnlineStorageConfig(product, region); } else { if (cdn == "http") { string host = string.Join(":", parts.Skip(3)); config = CASCConfig.LoadOnlineStorageConfig(host, product, region, true, true, true); } } } else { config = CASCConfig.LoadLocalStorageConfig(root, true, true); } object[] query = null; if (flag[0] == 'f' || flag[0] == 'C') { object[] t = new object[6] { null, null, null, null, null, null }; List <ulong> id = new List <ulong>(); List <ulong> type = new List <ulong>(); List <string> apms = new List <string>(); List <ulong> idi = new List <ulong>(); for (int i = 2; i < args.Length; ++i) { string arg = args[i]; try { switch (arg[0]) { case 'I': case 'i': id.Add(ulong.Parse(arg.Substring(1), NumberStyles.HexNumber)); break; case 'T': case 't': type.Add(ulong.Parse(arg.Substring(1), NumberStyles.HexNumber)); break; case 'n': case 'N': idi.Add(ulong.Parse(arg.Substring(1), NumberStyles.HexNumber)); break; case 'a': apms.Add(arg.Substring(1).ToLowerInvariant()); break; case 's': { int v = int.Parse(arg.Substring(1), NumberStyles.Number); if (t[2] == null) { t[2] = v; } else if (v > (int)t[2]) { t[2] = v; } } break; case 'S': { int v = int.Parse(arg.Substring(1), NumberStyles.Number); if (t[3] == null) { t[3] = v; } else if (v < (int)t[3]) { t[3] = v; } } break; } } finally { t[0] = id; t[1] = type; t[4] = apms; t[5] = idi; query = t; } } } else if (flag[0] == 'l') { object[] t = new object[2] { null, null }; List <ulong> pkg = new List <ulong>(); List <string> content = new List <string>(); for (int i = 2; i < args.Length; ++i) { string arg = args[i]; try { switch (arg[0]) { case 'p': pkg.Add(ulong.Parse(arg.Substring(1), NumberStyles.HexNumber)); break; case 'P': pkg.Add(ulong.Parse(arg.Substring(1), NumberStyles.Number)); break; case 'i': content.Add(arg.Substring(1).ToUpperInvariant()); break; } } finally { t[0] = pkg; t[1] = content; query = t; } } } else if (flag[0] == 'a') { config.Languages = null; } else if (flag[0] == 't') { query = new object[1] { new HashSet <ulong>() }; } else { Console.Error.WriteLine("Unsupported operand {0}", flag[0]); return; } CASCHandler handler = CASCHandler.OpenStorage(config); OwRootHandler ow = handler.Root as OwRootHandler; if (ow == null) { Console.Error.WriteLine("Not a valid Overwatch installation"); return; } if (flag[0] == 'a') { foreach (string name in ow.APMList) { Console.Out.WriteLine(name); } return; } foreach (APMFile apm in ow.APMFiles) { string apmName = System.IO.Path.GetFileName(apm.Name); if (flag[0] == 'f' && query[4] != null && ((List <string>)query[4]).Count > 0 && !((List <string>)query[4]).Contains(apmName.ToLowerInvariant())) { continue; } if (flag[0] == 'C') { foreach (ulong key in apm.CMFMap.Keys) { ulong rtype = GUID.Type(key); ulong rindex = GUID.LongKey(key); ulong rindex2 = GUID.Index(key); bool check1 = ((List <ulong>)query[0]).Count == 0; bool check2 = ((List <ulong>)query[1]).Count == 0; bool check5 = ((List <ulong>)query[5]).Count == 0; if (((List <ulong>)query[0]).Count > 0 && ((List <ulong>)query[0]).Contains(rindex)) // if index is not in i { check1 = true; } if (((List <ulong>)query[1]).Count > 0 && ((List <ulong>)query[1]).Contains(rtype)) // if type is not in t { check2 = true; } if (((List <ulong>)query[5]).Count > 0 && ((List <ulong>)query[5]).Contains(rindex2)) // if type is not in t { check5 = true; } bool check = check1 && check2 && check5; if (check) { Console.Out.WriteLine("Found {0:X12}.{1:X3} in APM {2}", rindex, rtype, apm.Name); } } } else if (flag[0] == 't') { foreach (ulong key in apm.CMFMap.Keys) { ((HashSet <ulong>)query[0]).Add(key >> 48); } } if (flag[0] == 'C' || flag[0] == 't') { continue; } for (long i = 0; i < apm.Packages.LongLength; ++i) { APMPackage package = apm.Packages[i]; PackageIndexRecord[] records = apm.Records[i]; if (flag[0] == 'l') { if (((List <ulong>)query[0]).Count + ((List <string>)query[1]).Count == 0) { return; } bool ret = true; if (((List <ulong>)query[0]).Count > 0 && ((List <ulong>)query[0]).Contains(package.packageKey)) { ret = false; } if (ret && ((List <string>)query[1]).Count > 0 && ((List <string>)query[1]).Contains(package.indexContentKey.ToHexString().ToUpperInvariant())) { ret = false; } if (ret) { continue; } ((List <ulong>)query[0]).Remove(package.packageKey); ((List <string>)query[1]).Remove(package.indexContentKey.ToHexString().ToUpperInvariant()); Console.Out.WriteLine("Dump for package i{0} / p{1:X} in APM {2}", package.indexContentKey.ToHexString().ToUpperInvariant(), package.packageKey, apm.Name); } for (long j = 0; j < records.LongLength; ++j) { PackageIndexRecord record = records[j]; ulong rtype = GUID.Type(record.Key); ulong rindex = GUID.LongKey(record.Key); ulong rindex2 = GUID.Index(record.Key); if (flag[0] == 'f') { bool check1 = ((List <ulong>)query[0]).Count == 0; bool check2 = ((List <ulong>)query[1]).Count == 0; bool check3 = query[2] == null; bool check4 = query[3] == null; bool check5 = ((List <ulong>)query[5]).Count == 0; if (((List <ulong>)query[0]).Count > 0 && ((List <ulong>)query[0]).Contains(rindex)) // if index is not in i { check1 = true; } if (((List <ulong>)query[1]).Count > 0 && ((List <ulong>)query[1]).Contains(rtype)) // if type is not in t { check2 = true; } if (query[2] != null && (int)query[2] > record.Size) // if size is less than s[lt] { check3 = true; } if (query[3] != null && (int)query[3] < record.Size) // if size is greater than s[gt] { check4 = true; } if (((List <ulong>)query[5]).Count > 0 && ((List <ulong>)query[5]).Contains(rindex2)) // if type is not in t { check5 = true; } bool check = check1 && check2 && check3 && check4 && check5; if (check) { Console.Out.WriteLine("Found {0:X12}.{1:X3} in package p{2:X} in APM {3}", rindex, rtype, package.packageKey, apm.Name); } } else if (flag[0] == 'l') { Console.Out.WriteLine("\t{0:X12}.{1:X3} ({2} bytes) - {3:X}", rindex, rtype, record.Size, record.Key); } } } } if (flag[0] == 't') { foreach (ulong type in (HashSet <ulong>)query[0]) { byte[] be = BitConverter.GetBytes((ushort)type); Array.Reverse(be); Console.Out.WriteLine("{2:X4} : {0:X4} : {1:X3}", (ushort)type, GUID.Type(type << 48), BitConverter.ToUInt16(be, 0)); } } }
static void Main(string[] args) { //byte[] keyBytes = new byte[16]; //ArmadilloCrypt crypt = new ArmadilloCrypt(keyBytes); //string buildconfigfile = "9f6048f8bd01f38ec0be83f4a9fe5a10"; //byte[] data = File.ReadAllBytes(buildconfigfile); //byte[] IV = buildconfigfile.Substring(16).ToByteArray(); //unsafe //{ // fixed (byte* ptr = keyBytes) // { // for (ulong i = 0; i < ulong.MaxValue; i++) // { // for (ulong j = 0; j < ulong.MaxValue; j++) // { // byte[] decrypted = crypt.DecryptFile(IV, data); // if (decrypted[0] == 0x23 && decrypted[1] == 0x20 && decrypted[2] == 0x42 && decrypted[3] == 0x75) // { // Console.WriteLine("key found: {0} {1} ?", i, j); // } // *(ulong*)ptr = j; // if (j % 1000000 == 0) // Console.WriteLine("{0}/{1}", j, ulong.MaxValue); // } // *(ulong*)(ptr + 8) = i; // } // } //} if (args.Length != 5) { Console.WriteLine("Invalid arguments count!"); Console.WriteLine("Usage: CASCConsole <mode> <pattern|listfile> <destination> <localeFlags> <contentFlags>"); return; } Console.WriteLine("Settings:"); Console.WriteLine(" WowPath: {0}", Settings.Default.StoragePath); Console.WriteLine(" OnlineMode: {0}", Settings.Default.OnlineMode); Console.WriteLine("Loading..."); BackgroundWorkerEx bgLoader = new BackgroundWorkerEx(); bgLoader.ProgressChanged += BgLoader_ProgressChanged; //CASCConfig.LoadFlags |= LoadFlags.Install; CASCConfig config = Settings.Default.OnlineMode ? CASCConfig.LoadOnlineStorageConfig(Settings.Default.Product, "us") : CASCConfig.LoadLocalStorageConfig(Settings.Default.StoragePath); CASCHandler cascHandler = CASCHandler.OpenStorage(config, bgLoader); string mode = args[0]; string pattern = args[1]; string dest = args[2]; LocaleFlags locale = (LocaleFlags)Enum.Parse(typeof(LocaleFlags), args[3]); ContentFlags content = (ContentFlags)Enum.Parse(typeof(ContentFlags), args[4]); cascHandler.Root.LoadListFile(Path.Combine(Environment.CurrentDirectory, "listfile.txt"), bgLoader); CASCFolder root = cascHandler.Root.SetFlags(locale, content); //cascHandler.Root.MergeInstall(cascHandler.Install); Console.WriteLine("Loaded."); Console.WriteLine("Extract params:"); Console.WriteLine(" Mode: {0}", mode); Console.WriteLine(" Pattern: {0}", pattern); Console.WriteLine(" Destination: {0}", dest); Console.WriteLine(" LocaleFlags: {0}", locale); Console.WriteLine(" ContentFlags: {0}", content); if (mode == "pattern") { Wildcard wildcard = new Wildcard(pattern, true, RegexOptions.IgnoreCase); foreach (var file in CASCFolder.GetFiles(root.Entries.Select(kv => kv.Value))) { if (wildcard.IsMatch(file.FullName)) { ExtractFile(cascHandler, file.FullName, dest); } } } else if (mode == "listfile") { var names = File.ReadLines(pattern); foreach (var file in names) { ExtractFile(cascHandler, file, dest); } } Console.WriteLine("Extracted."); }
//class Hashes //{ // public string[] install; // public string[] encoding; //} static void Main(string[] args) { //HashSet<string> data = new HashSet<string>(); //HashSet<string> installs = new HashSet<string>(); //using (StreamReader sr = new StreamReader("list.txt")) //{ // string line1; // while((line1 = sr.ReadLine()) != null) // { // data.Add(line1.Substring(19, 32)); // if (line1.Contains("install")) // { // installs.Add(line1.Substring(93, 32)); // } // } //} ////foreach (var cfg in data) ////{ //// string url = string.Format("https://bnet.marlam.in/tpr/wow/config/{0}/{1}/{2}", cfg.Substring(0, 2), cfg.Substring(2, 2), cfg); //// var stream = CDNIndexHandler.OpenFileDirect(url); //// using (var fileStream = File.Create("builds\\" + cfg)) //// { //// stream.CopyTo(fileStream); //// } ////} //Dictionary<string, Hashes> data2 = new Dictionary<string, Hashes>(); //foreach (var file in Directory.GetFiles("builds")) //{ // using(var sr = new StreamReader(file)) // { // string line; // while ((line = sr.ReadLine()) != null) // { // if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#")) // skip empty lines and comments // continue; // string[] tokens = line.Split(new char[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries); // if (tokens.Length != 2) // throw new Exception("KeyValueConfig: tokens.Length != 2"); // var values = tokens[1].Trim().Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); // var valuesList = values.ToList(); // if (!data2.ContainsKey(file)) // data2[file] = new Hashes(); // if (values[0] == "install") // { // data2[file].install = values; // } // if (values[0] == "encoding") // { // data2[file].encoding = values; // } // //sr.Data.Add(tokens[0].Trim(), valuesList); // } // } //} //Dictionary<string, string> realInstalls = new Dictionary<string, string>(); //foreach(var kv in data2) //{ // for(int i = 6; i < kv.Value.install.Length; i++) // { // if (installs.Contains(kv.Value.install[i])) // { // //Console.WriteLine("{0} {1}", kv.Value.install[i], kv.Value.encoding[i]); // realInstalls[kv.Value.install[i]] = kv.Value.encoding[i]; // } // } //} //CASCConfig.ValidateData = false; //int buildIndex = 0; //foreach (var kvinstall in realInstalls) //{ // string url1 = string.Format("http://bnet.marlamin.com/tpr/wow/data/{0}/{1}/{2}", kvinstall.Key.Substring(0, 2), kvinstall.Key.Substring(2, 2), kvinstall.Key); // BLTEStream instFile = new BLTEStream(CDNIndexHandler.OpenFileDirect(url1), new MD5Hash()); // InstallHandler install = new InstallHandler(new BinaryReader(instFile), null); // //foreach(var ent in install.GetEntries().Where(e => e.Name.Contains("MacOS"))) // //{ // // Console.WriteLine(ent.Name); // //} // //continue; // string url2 = string.Format("http://bnet.marlamin.com/tpr/wow/data/{0}/{1}/{2}", kvinstall.Value.Substring(0, 2), kvinstall.Value.Substring(2, 2), kvinstall.Value); // BLTEStream encFile = new BLTEStream(CDNIndexHandler.OpenFileDirect(url2), new MD5Hash()); // EncodingHandler encoding = new EncodingHandler(new BinaryReader(encFile), null); // string[] files = new string[] { "WowB.exe", "WowT.exe", "Wow.exe", "WowB-64.exe", "WowT-64.exe", "Wow-64.exe", "RenderService.exe", "RenderService-64.exe", // @"World of Warcraft Public Test.app\Contents\MacOS\World of Warcraft", // @"World of Warcraft Public Test.app\Contents\MacOS\World of Warcraft 64", // @"World of Warcraft Beta.app\Contents\MacOS\World of Warcraft", // @"World of Warcraft Beta.app\Contents\MacOS\World of Warcraft 64", // @"World of Warcraft Retail.app\Contents\MacOS\World of Warcraft", // @"World of Warcraft Retail.app\Contents\MacOS\World of Warcraft 64", // @"World of Warcraft Test.app\Contents\MacOS\World of Warcraft", // @"World of Warcraft.app\Contents\MacOS\World of Warcraft" // }; // string outFolder = "build_" + buildIndex++; // foreach (var file in files) // { // var entries = install.GetEntriesByName(file); // foreach (var entry in entries) // { // bool ok = encoding.GetEntry(entry.MD5, out var encEntry); // if (ok) // { // string chash = encEntry.Key.ToHexString().ToLower(); // Console.WriteLine("http://bnet.marlamin.com/tpr/wow/data/{0}/{1}/{2} {3}", chash.Substring(0, 2), chash.Substring(2, 2), chash, file); // string url = string.Format("http://blzddist1-a.akamaihd.net/tpr/wow/data/{0}/{1}/{2}", chash.Substring(0, 2), chash.Substring(2, 2), chash); // //string url = string.Format("http://bnet.marlamin.com/tpr/wow/data/{0}/{1}/{2}", chash.Substring(0, 2), chash.Substring(2, 2), chash); // try // { // var stream = CDNIndexHandler.OpenFileDirect(url); // BLTEStream blte3 = new BLTEStream(stream, new MD5Hash()); // string outFile = Path.Combine(outFolder, chash + "_" + file); // string outDir = Path.GetDirectoryName(outFile); // if (!Directory.Exists(outDir)) // Directory.CreateDirectory(outDir); // using (var fileStream = File.Create(outFile)) // { // blte3.CopyTo(fileStream); // } // } // catch(Exception exc) // { // Console.WriteLine(exc.Message); // } // } // } // } //} //ArmadilloCrypt crypt = new ArmadilloCrypt("sc1Dev"); //ArmadilloCrypt crypt = new ArmadilloCrypt(new byte[] { 0xe2, 0x56, 0x27, 0xf6, 0xe6, 0xc6, 0xd6, 0x02, 0xc2, 0x86, 0x37, 0x46, 0x02, 0x72, 0xc6, 0x24 }); // //ulong x = BitConverter.ToUInt64(crypt.Key, 0); //ulong y = BitConverter.ToUInt64(crypt.Key, 8); //var decr = crypt.DecryptFile(@"c:\Users\TOM_RUS\Downloads\e32f46c7245bfc154e43924555a5cf9f"); //var decr = crypt.DecryptFile(@"c:\Users\TOM_RUS\Downloads\e6185c20749d6b15dfd3aaf4110f3045"); //File.WriteAllBytes("e6185c20749d6b15dfd3aaf4110f3045_decrypted", decr); //; //byte[] keyBytes = new byte[16]; //ArmadilloCrypt crypt = new ArmadilloCrypt(keyBytes); //string buildconfigfile = "9f6048f8bd01f38ec0be83f4a9fe5a10"; //byte[] data = File.ReadAllBytes(buildconfigfile); //byte[] IV = buildconfigfile.Substring(16).ToByteArray(); //unsafe //{ // fixed (byte* ptr = keyBytes) // { // for (ulong i = 0; i < ulong.MaxValue; i++) // { // for (ulong j = 0; j < ulong.MaxValue; j++) // { // byte[] decrypted = crypt.DecryptFile(IV, data); // if (decrypted[0] == 0x23 && decrypted[1] == 0x20 && decrypted[2] == 0x42 && decrypted[3] == 0x75) // { // Console.WriteLine("key found: {0} {1} ?", i, j); // } // *(ulong*)ptr = j; // if (j % 1000000 == 0) // Console.WriteLine("{0}/{1}", j, ulong.MaxValue); // } // *(ulong*)(ptr + 8) = i; // } // } //} if (args.Length != 6) { Console.WriteLine("Invalid arguments count!"); Console.WriteLine("Usage: CASCConsole <mode> <pattern|listfile> <destination> <localeFlags> <product> <overrideArchive>"); return; } DateTime startTime = DateTime.Now; Console.WriteLine($"Started at {startTime}"); Console.WriteLine("Settings:"); Console.WriteLine(" WowPath: {0}", Settings.Default.StoragePath); Console.WriteLine(" OnlineMode: {0}", Settings.Default.OnlineMode); Console.WriteLine("Loading..."); BackgroundWorkerEx bgLoader = new BackgroundWorkerEx(); bgLoader.ProgressChanged += BgLoader_ProgressChanged; //CASCConfig.LoadFlags |= LoadFlags.Install; string mode = args[0]; string pattern = args[1]; string dest = args[2]; LocaleFlags locale = (LocaleFlags)Enum.Parse(typeof(LocaleFlags), args[3]); string product = args[4]; bool overrideArchive = bool.Parse(args[5]); CASCConfig.LoadFlags |= LoadFlags.Install; CASCConfig config = Settings.Default.OnlineMode ? CASCConfig.LoadOnlineStorageConfig(product, "us") : CASCConfig.LoadLocalStorageConfig(Settings.Default.StoragePath, product); CASCHandler cascHandler = CASCHandler.OpenStorage(config, bgLoader); cascHandler.Root.LoadListFile(Path.Combine(Environment.CurrentDirectory, "listfile.csv"), bgLoader); CASCFolder root = cascHandler.Root.SetFlags(locale, overrideArchive); cascHandler.Root.MergeInstall(cascHandler.Install); Console.WriteLine("Loaded."); Console.WriteLine("Extract params:"); Console.WriteLine(" Mode: {0}", mode); Console.WriteLine(" Pattern/Listfile: {0}", pattern); Console.WriteLine(" Destination: {0}", dest); Console.WriteLine(" LocaleFlags: {0}", locale); Console.WriteLine(" Product: {0}", product); Console.WriteLine(" OverrideArchive: {0}", overrideArchive); if (mode == "pattern") { Wildcard wildcard = new Wildcard(pattern, true, RegexOptions.IgnoreCase); foreach (var file in CASCFolder.GetFiles(root.Entries.Select(kv => kv.Value))) { if (wildcard.IsMatch(file.FullName)) { ExtractFile(cascHandler, file.Hash, file.FullName, dest); } } } else if (mode == "listfile") { if (cascHandler.Root is WowRootHandler wowRoot) { char[] splitChar = new char[] { ';' }; var names = File.ReadLines(pattern).Select(s => s.Split(splitChar, 2)).Select(s => new { id = int.Parse(s[0]), name = s[1] }); foreach (var file in names) { ExtractFile(cascHandler, wowRoot.GetHashByFileDataId(file.id), file.name, dest); } } else { var names = File.ReadLines(pattern); foreach (var file in names) { ExtractFile(cascHandler, 0, file, dest); } } } Console.WriteLine("Extracted."); DateTime endTime = DateTime.Now; Console.WriteLine($"Ended at {endTime} (took {endTime - startTime})"); }
private static void Main() { Console.OutputEncoding = Encoding.UTF8; Files = new Dictionary <ulong, MD5Hash>(); TrackedFiles = new Dictionary <ushort, HashSet <ulong> >(); #region Tool Detection HashSet <Type> tools = new HashSet <Type>(); { Assembly asm = typeof(ITool).Assembly; Type t = typeof(ITool); List <Type> types = asm.GetTypes().Where(tt => tt != t && t.IsAssignableFrom(tt)).ToList(); foreach (Type tt in types) { ToolAttribute attrib = tt.GetCustomAttribute <ToolAttribute>(); if (tt.IsInterface || attrib == null) { continue; } tools.Add(tt); if (attrib.TrackTypes == null) { continue; } foreach (ushort type in attrib.TrackTypes) { if (!TrackedFiles.ContainsKey(type)) { TrackedFiles[type] = new HashSet <ulong>(); } } } } #endregion Flags = FlagParser.Parse <ToolFlags>(() => PrintHelp(tools)); if (Flags == null) { return; } Logger.EXIT = !Flags.GracefulExit; ITool targetTool = null; ICLIFlags targetToolFlags = null; #region Tool Activation foreach (Type type in tools) { ToolAttribute attrib = type.GetCustomAttribute <ToolAttribute>(); if (!string.Equals(attrib.Keyword, Flags.Mode, StringComparison.InvariantCultureIgnoreCase)) { continue; } targetTool = Activator.CreateInstance(type) as ITool; if (attrib.CustomFlags != null) { Type flags = attrib.CustomFlags; if (typeof(ICLIFlags).IsAssignableFrom(flags)) { targetToolFlags = typeof(FlagParser).GetMethod("Parse", new Type[] { }).MakeGenericMethod(flags).Invoke(null, null) as ICLIFlags; } } break; } #endregion if (targetTool == null) { FlagParser.Help <ToolFlags>(false); PrintHelp(tools); if (Debugger.IsAttached) { Debugger.Break(); } return; } #region Initialize CASC Log("{0} v{1}", Assembly.GetExecutingAssembly().GetName().Name, Util.GetVersion()); Log("Initializing CASC..."); Log("Set language to {0}", Flags.Language); CDNIndexHandler.Cache.Enabled = Flags.UseCache; CDNIndexHandler.Cache.CacheData = Flags.CacheData; CDNIndexHandler.Cache.Validate = Flags.ValidateCache; // ngdp:us:pro // http:us:pro:us.patch.battle.net:1119 if (Flags.OverwatchDirectory.ToLowerInvariant().Substring(0, 5) == "ngdp:") { string cdn = Flags.OverwatchDirectory.Substring(5, 4); string[] parts = Flags.OverwatchDirectory.Substring(5).Split(':'); string region = "us"; string product = "pro"; if (parts.Length > 1) { region = parts[1]; } if (parts.Length > 2) { product = parts[2]; } if (cdn == "bnet") { Config = CASCConfig.LoadOnlineStorageConfig(product, region); } else { if (cdn == "http") { string host = string.Join(":", parts.Skip(3)); Config = CASCConfig.LoadOnlineStorageConfig(host, product, region, true, true, true); } } } else { Config = CASCConfig.LoadLocalStorageConfig(Flags.OverwatchDirectory, !Flags.SkipKeys, false); } Config.Languages = new HashSet <string>(new[] { Flags.Language }); #endregion foreach (Dictionary <string, string> build in Config.BuildInfo) { if (!build.ContainsKey("Tags")) { continue; } if (build["Tags"].Contains("XX?")) { IsPTR = true; } // us ptr region is known as XX, so just look for it in the tags. // this should work... untested for Asia } BuildVersion = uint.Parse(Config.BuildName.Split('.').Last()); if (Flags.SkipKeys) { Log("Disabling Key auto-detection..."); } Log("Using Overwatch Version {0}", Config.BuildName); CASC = CASCHandler.OpenStorage(Config); Root = CASC.Root as OwRootHandler; if (Root == null) { ErrorLog("Not a valid overwatch installation"); return; } // Fail when trying to extract data from a specified language with 2 or less files found. if (!Root.APMFiles.Any()) { ErrorLog("Could not find the files for language {0}. Please confirm that you have that language installed, and are using the names from the target language.", Flags.Language); if (!Flags.GracefulExit) { return; } } Log("Mapping..."); TrackedFiles[0x90] = new HashSet <ulong>(); IO.MapCMF(); IO.LoadGUIDTable(); Sound.WwiseBank.GetReady(); #region Key Detection if (!Flags.SkipKeys) { Log("Adding Encryption Keys..."); foreach (ulong key in TrackedFiles[0x90]) { if (!ValidKey(key)) { continue; } using (Stream stream = IO.OpenFile(Files[key])) { if (stream == null) { continue; } STUEncryptionKey encryptionKey = GetInstance <STUEncryptionKey>(key); if (encryptionKey != null && !KeyService.keys.ContainsKey(encryptionKey.LongRevKey)) { KeyService.keys.Add(encryptionKey.LongRevKey, encryptionKey.KeyValue); Log("Added Encryption Key {0}, Value: {1}", encryptionKey.KeyNameProper, encryptionKey.Key); } } } } #endregion Log("Tooling..."); targetTool.Parse(targetToolFlags); if (Debugger.IsAttached) { Debugger.Break(); } }
static void Main(string[] args) { if (args.Length < 3) { Console.Out.WriteLine("Usage: PackageTool.exe [-LLang] \"overwatch_folder\" \"output_folder\" <keys...>"); Console.Out.WriteLine("Keys must start with 'i' for content keys, 'p' for package keys, 'n' for package indexes, 't' for package indexes + indices"); Console.Out.WriteLine("Keys must start with 'a' for specific APMs"); Console.Out.WriteLine("If any key starts with Q it will dump all files."); Console.Out.WriteLine("If any key starts with D it will only output filenames to console, but not write files"); return; } string root = args[0]; OwRootHandler.LOAD_PACKAGES = true; CASCConfig config = null; // ngdp:us:pro // http:us:pro:us.patch.battle.net:1119 if (root.ToLowerInvariant().Substring(0, 5) == "ngdp:") { string cdn = root.Substring(5, 4); string[] parts = root.Substring(5).Split(':'); string region = "us"; string product = "pro"; if (parts.Length > 1) { region = parts[1]; } if (parts.Length > 2) { product = parts[2]; } if (cdn == "bnet") { config = CASCConfig.LoadOnlineStorageConfig(product, region); } else { if (cdn == "http") { string host = string.Join(":", parts.Skip(3)); config = CASCConfig.LoadOnlineStorageConfig(host, product, region, true, true, true); } } } else { config = CASCConfig.LoadLocalStorageConfig(root, true, true); } if (args[0][0] == '-' && args[0][1] == 'L') { string lang = args[0].Substring(2); config.Languages = new HashSet <string>(new string[1] { lang }); args = args.Skip(1).ToArray(); } string output = args[1] + Path.DirectorySeparatorChar; Console.Out.WriteLine("{0} v{1}", Assembly.GetExecutingAssembly().GetName().Name, OWLib.Util.GetVersion()); HashSet <ulong> packageKeys = new HashSet <ulong>(); HashSet <ulong> packageIndices = new HashSet <ulong>(); HashSet <ulong> packageIndent = new HashSet <ulong>(); HashSet <string> contentKeys = new HashSet <string>(); HashSet <ulong> dumped = new HashSet <ulong>(); HashSet <ulong> fileKeys = new HashSet <ulong>(); HashSet <ulong> types = new HashSet <ulong>(); string apmName = null; bool dumpAll = false; bool dry = false; for (int i = 2; i < args.Length; ++i) { string arg = args[i]; switch (arg[0]) { case 'q': case 'Q': dumpAll = true; break; case 'd': case 'D': dry = true; break; case 'p': packageKeys.Add(ulong.Parse(arg.Substring(1), NumberStyles.HexNumber)); break; case 'P': packageKeys.Add(ulong.Parse(arg.Substring(1), NumberStyles.Number)); break; case 'n': packageIndices.Add(ulong.Parse(arg.Substring(1), NumberStyles.HexNumber)); break; case 'N': packageIndices.Add(ulong.Parse(arg.Substring(1), NumberStyles.Number)); break; case 't': packageIndent.Add(ulong.Parse(arg.Substring(1), NumberStyles.HexNumber)); break; case 'T': packageIndent.Add(ulong.Parse(arg.Substring(1), NumberStyles.Number)); break; case 'a': case 'A': apmName = arg.Substring(1).ToLowerInvariant(); break; case 'i': case 'I': contentKeys.Add(arg.Substring(1).ToUpperInvariant()); break; case 'f': fileKeys.Add(ulong.Parse(arg.Substring(1), NumberStyles.HexNumber)); break; case 'F': fileKeys.Add(ulong.Parse(arg.Substring(1), NumberStyles.Number)); break; case 'M': types.Add(ulong.Parse(arg.Substring(1), NumberStyles.HexNumber)); break; } } if (contentKeys.Count + packageKeys.Count + packageIndices.Count + packageIndent.Count + fileKeys.Count + types.Count == 0 && !dumpAll) { Console.Error.WriteLine("Must have at least 1 query"); return; } CASCHandler handler = CASCHandler.OpenStorage(config); OwRootHandler ow = handler.Root as OwRootHandler; if (ow == null) { Console.Error.WriteLine("Not a valid Overwatch installation"); return; } Console.Out.WriteLine("Extracting..."); HashSet <ulong> indicesExtracted = new HashSet <ulong>(); HashSet <ulong> CMFExtracted = new HashSet <ulong>(); foreach (APMFile apm in ow.APMFiles) { if (apmName != null && !Path.GetFileName(apm.Name).ToLowerInvariant().Contains(apmName)) { continue; } Console.Out.WriteLine("Iterating {0}", Path.GetFileName(apm.Name)); HashSet <ulong> removed = new HashSet <ulong>(); foreach (ulong key in fileKeys) { if (apm.CMFMap.ContainsKey(key)) { ulong rtype = GUID.Type(key); ulong rindex = GUID.LongKey(key); string ofn = $"{output}{Path.DirectorySeparatorChar}cmf{Path.DirectorySeparatorChar}{rtype:X3}{Path.DirectorySeparatorChar}"; if (!dry && !Directory.Exists(ofn)) { Console.Out.WriteLine("Created directory {0}", ofn); Directory.CreateDirectory(ofn); } ofn = $"{ofn}{rindex:X12}.{rtype:X3}"; if (!dry) { using (Stream outputStream = File.Open(ofn, FileMode.Create, FileAccess.Write)) { EncodingEntry recordEncoding; if (!handler.Encoding.GetEntry(apm.CMFMap[key].HashKey, out recordEncoding)) { Console.Error.WriteLine("Cannot open file {0} -- malformed CMF?", ofn); continue; } try { using (Stream recordStream = handler.OpenFile(recordEncoding.Key)) { CopyBytes(recordStream, outputStream, recordEncoding.Size); } Console.Out.WriteLine("Saved file {0}", ofn); removed.Add(key); } catch { Console.Error.WriteLine("Cannot open file {0} -- encryption", ofn); } } } } } foreach (ulong key in removed) { fileKeys.Remove(key); } if (types.Count > 0 || dumpAll) { foreach (ulong key in apm.CMFMap.Keys) { if (types.Contains(GUID.Type(key)) || dumpAll) { ulong rtype = GUID.Type(key); ulong rindex = GUID.LongKey(key); string ofn = $"{output}{Path.DirectorySeparatorChar}cmf{Path.DirectorySeparatorChar}{rtype:X3}{Path.DirectorySeparatorChar}"; if (!dry && !Directory.Exists(ofn)) { Console.Out.WriteLine("Created directory {0}", ofn); Directory.CreateDirectory(ofn); } ofn = $"{ofn}{rindex:X12}.{rtype:X3}"; if (!dry) { using (Stream outputStream = File.Open(ofn, FileMode.Create, FileAccess.Write)) { EncodingEntry recordEncoding; if (!handler.Encoding.GetEntry(apm.CMFMap[key].HashKey, out recordEncoding)) { Console.Error.WriteLine("Cannot open file {0} -- malformed CMF?", ofn); continue; } try { using (Stream recordStream = handler.OpenFile(recordEncoding.Key)) { CopyBytes(recordStream, outputStream, recordEncoding.Size); } Console.Out.WriteLine("Saved file {0}", ofn); } catch { Console.Error.WriteLine("Cannot open file {0} -- encryption", ofn); } } } } } } if (dumpAll) { continue; } if (contentKeys.Count + packageKeys.Count + packageIndices.Count + packageIndent.Count + fileKeys.Count > 0) { for (long i = 0; i < apm.Packages.LongLength; ++i) { if (contentKeys.Count + packageKeys.Count + packageIndices.Count + packageIndent.Count + fileKeys.Count == 0) { break; } APMPackage package = apm.Packages[i]; if (!dumpAll) { bool ret = true; if (packageKeys.Count > 0 && packageKeys.Contains(package.packageKey)) { ret = false; } if (ret && contentKeys.Count > 0 && contentKeys.Contains(package.indexContentKey.ToHexString().ToUpperInvariant())) { ret = false; } if (ret && packageIndices.Count > 0 && packageIndices.Contains(GUID.Index(package.packageKey)) && !indicesExtracted.Contains(package.packageKey)) { ret = false; } if (ret && packageIndent.Count > 0 && packageIndent.Contains(GUID.LongKey(package.packageKey))) { ret = false; } if (ret) { continue; } } packageKeys.Remove(package.packageKey); indicesExtracted.Add(package.packageKey); packageIndent.Remove(GUID.LongKey(package.packageKey)); contentKeys.Remove(package.indexContentKey.ToHexString().ToUpperInvariant()); PackageIndex index = apm.Indexes[i]; PackageIndexRecord[] records = apm.Records[i]; string o = null; if (dumpAll) { o = output; } else { o = $"{output}{GUID.LongKey(package.packageKey):X12}{Path.DirectorySeparatorChar}"; } EncodingEntry bundleEncoding; bool allowBundle = handler.Encoding.GetEntry(index.bundleContentKey, out bundleEncoding); Stream bundleStream = null; if (allowBundle) { try { bundleStream = handler.OpenFile(bundleEncoding.Key); } catch { Console.Error.WriteLine("Cannot open bundle {0:X16} -- encryption", index.bundleKey); continue; } } foreach (PackageIndexRecord record in records) { if (dumpAll && !dumped.Add(record.Key)) { continue; } ulong rtype = GUID.Type(record.Key); ulong rindex = GUID.LongKey(record.Key); string ofn = $"{o}{rtype:X3}{Path.DirectorySeparatorChar}"; if (!dry && !Directory.Exists(ofn)) { Console.Out.WriteLine("Created directory {0}", ofn); Directory.CreateDirectory(ofn); } ofn = $"{ofn}{rindex:X12}.{rtype:X3}"; if (!dry) { using (Stream outputStream = File.Open(ofn, FileMode.Create, FileAccess.Write)) { if (((ContentFlags)record.Flags & ContentFlags.Bundle) == ContentFlags.Bundle) { if (allowBundle) { bundleStream.Position = record.Offset; CopyBytes(bundleStream, outputStream, record.Size); } else { Console.Error.WriteLine("Cannot open file {0} -- can't open bundle", ofn); continue; } } else { EncodingEntry recordEncoding; if (!handler.Encoding.GetEntry(record.ContentKey, out recordEncoding)) { Console.Error.WriteLine("Cannot open file {0} -- doesn't have bundle flags", ofn); continue; } try { using (Stream recordStream = handler.OpenFile(recordEncoding.Key)) { CopyBytes(recordStream, outputStream, record.Size); } } catch { Console.Error.WriteLine("Cannot open file {0} -- encryption", ofn); } } } } Console.Out.WriteLine("Saved file {0}", ofn); } if (allowBundle) { bundleStream.Dispose(); } } } } }
public static async Task <IActionResult> Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, [Blob("wow")] CloudBlobContainer container, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); string localeString = req.Query["locale"]; string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); var localeName = localeString ?? "en-US"; CASCConfig config = CASCConfig.LoadLocalStorageConfig(@"C:\Games\World of Warcraft", "wow"); CASCHandler handler = CASCHandler.OpenStorage(config, null); handler.Root.LoadListFile(Path.Combine(Environment.CurrentDirectory, "listfile8x.csv")); var locale = WowLocales.Locales.Single(x => x.Name.Equals(localeName, StringComparison.OrdinalIgnoreCase)); CASCFolder root = handler.Root.SetFlags(locale.Flag); handler.Root.MergeInstall(handler.Install); var dbfiles = (CASCFolder)root.Entries["dbfilesclient"]; await container.CreateIfNotExistsAsync(); // Apply translations to expansion / tiers var tiers = WowExpansion.All; { var entry = dbfiles.GetEntry("journaltier.db2"); await using var stream = handler.OpenFile(entry.Hash); var reader = new WDC3Reader(stream); foreach (var pair in reader) { var tier = tiers.Single(x => x.Value.TierId == pair.Key); tier.Value.Name = pair.Value.GetField <string>(0); } } await container.GetBlockBlobReference("expansions." + localeName + ".json") .UploadTextAsync(JsonConvert.SerializeObject(tiers.Values, serializerSettings)); var instanceTiers = new Dictionary <int, int>(); { var entry = dbfiles.GetEntry("JournalTierXInstance.db2"); await using var stream = handler.OpenFile(entry.Hash); var reader = new WDC3Reader(stream); foreach (var pair in reader) { var tierId = pair.Value.GetField <int>(0); var instanceId = pair.Value.GetField <int>(1); instanceTiers[instanceId] = tierId; } } // Get all instances var instances = new List <Instance>(); { var entry = dbfiles.GetEntry("JournalInstance.db2"); await using var stream = handler.OpenFile(entry.Hash); var reader = new WDC3Reader(stream); foreach (var pair in reader) { if (!instanceTiers.ContainsKey(pair.Key)) { continue; } var tierId = instanceTiers[pair.Key]; var instance = new Instance { Id = pair.Key, Name = pair.Value.GetField <string>(0), Description = pair.Value.GetField <string>(1), MapId = pair.Value.GetField <int>(3), BackgroundImageId = pair.Value.GetField <int>(4), ButtonImageId = pair.Value.GetField <int>(5), ButtonSmallImageId = pair.Value.GetField <int>(6), LoreImageId = pair.Value.GetField <int>(7), Order = pair.Value.GetField <int>(8), Flags = pair.Value.GetField <int>(9), TierId = tierId }; instances.Add(instance); } } // Save the list of all instances await container.GetBlockBlobReference("data/dungeons." + localeName + ".json") .UploadTextAsync(JsonConvert.SerializeObject(instances, serializerSettings)); foreach (var tier in tiers.Values) { var dungeons = new List <Instance>(); // Get details and images for each dungeon foreach (var instance in instances) { // Only encounters for this tier if (instance.TierId != tier.TierId) { continue; } // World instances are flagged with 0x2 if ((instance.Flags & 0x2) == 0x2) { continue; } // Raids have an order number, dungeons have order of 0 and sorted by name if (instance.Order != 0) { continue; } dungeons.Add(instance); var blobPath = "static/img/instance/" + instance.Id + "/"; // Get the dungeon images using var loreStream = handler.OpenFile(instance.LoreImageId); await container.GetBlockBlobReference(blobPath + "lg." + localeName + ".blp").UploadFromStreamAsync(loreStream); using var bg = handler.OpenFile(instance.BackgroundImageId); await container.GetBlockBlobReference(blobPath + "xl." + localeName + ".blp").UploadFromStreamAsync(bg); using var btnLarge = handler.OpenFile(instance.ButtonImageId); await container.GetBlockBlobReference(blobPath + "sm." + localeName + ".blp").UploadFromStreamAsync(btnLarge); using var btnSmall = handler.OpenFile(instance.ButtonImageId); await container.GetBlockBlobReference(blobPath + "xs." + localeName + ".blp").UploadFromStreamAsync(btnSmall); var encounters = new List <Encounter>(); foreach (var encounter in dbfiles.EnumerateTable("JournalEncounter", handler)) { var instanceId = encounter.Value.GetField <int>(3); if (instanceId != instance.Id) { continue; } var journalEncounter = new Encounter { Id = encounter.Key, Name = encounter.Value.GetField <string>(0), Description = encounter.Value.GetField <string>(1), MapX = encounter.Value.GetField <float>(2, 0), MapY = encounter.Value.GetField <float>(2, 1), InstanceId = instanceId, EncounterId = encounter.Value.GetField <int>(4), Order = encounter.Value.GetField <int>(5), FirstSectionId = encounter.Value.GetField <int>(6), UiMapId = encounter.Value.GetField <int>(7), MapDisplayConditionId = encounter.Value.GetField <int>(8), Flags = encounter.Value.GetField <byte>(9), Difficulty = encounter.Value.GetField <byte>(10), Sections = new List <JournalSection>() }; var sections = new Dictionary <int, JournalSection>(); foreach (var encounterSection in dbfiles.EnumerateTable("JournalEncounterSection", handler)) { var section = new JournalSection { Id = encounterSection.Key, Name = encounterSection.Value.GetField <string>(0), Description = encounterSection.Value.GetField <string>(1), JournalEncounterId = encounterSection.Value.GetField <ushort>(2), Order = encounterSection.Value.GetField <byte>(3), ParentSectionId = encounterSection.Value.GetField <ushort>(4), FirstChildSectionId = encounterSection.Value.GetField <ushort>(5), NextSiblingSectionId = encounterSection.Value.GetField <ushort>(6), SectionType = encounterSection.Value.GetField <byte>(7), // 3 = overview, 1 = creature, 2 = spell IconCreatureDisplayId = encounterSection.Value.GetField <uint>(8), UiModelSceneId = encounterSection.Value.GetField <int>(9), SpellId = encounterSection.Value.GetField <int>(10), IconFileDataId = encounterSection.Value.GetField <int>(11), Flags = encounterSection.Value.GetField <ushort>(12), IconFlags = encounterSection.Value.GetField <ushort>(13), // 1=tank, 2=dps, 4=healer, DifficultyMask = encounterSection.Value.GetField <byte>(14), }; if (section.JournalEncounterId != journalEncounter.Id) { continue; } sections[section.Id] = section; } journalEncounter.Sections = BuildSectionTree(sections); encounters.Add(journalEncounter); } await container.GetBlockBlobReference("data/dungeon/" + instance.Id + "/encounters." + localeName + ".json") .UploadTextAsync(JsonConvert.SerializeObject(encounters, serializerSettings)); } // Save the list of dungeons for this tier await container.GetBlockBlobReference("data/" + tier.Id + "/dungeons." + localeName + ".json") .UploadTextAsync(JsonConvert.SerializeObject(dungeons, serializerSettings)); } return(new OkObjectResult(tiers)); }
static void Main(string[] args) { Console.OutputEncoding = Encoding.UTF8; Files = new Dictionary <ulong, PackageRecord>(); TrackedFiles = new Dictionary <ushort, HashSet <ulong> >(); Flags = FlagParser.Parse <ToolFlags>(); if (Flags == null) { return; } #region Initialize CASC Log("{0} v{1}", Assembly.GetExecutingAssembly().GetName().Name, Util.GetVersion()); Log("Initializing CASC..."); Log("Set language to {0}", Flags.Language); CDNIndexHandler.Cache.Enabled = Flags.UseCache; CDNIndexHandler.Cache.CacheData = Flags.CacheData; CDNIndexHandler.Cache.Validate = Flags.ValidateCache; // ngdp:us:pro // http:us:pro:us.patch.battle.net:1119 if (Flags.OverwatchDirectory.ToLowerInvariant().Substring(0, 5) == "ngdp:") { string cdn = Flags.OverwatchDirectory.Substring(5, 4); string[] parts = Flags.OverwatchDirectory.Substring(5).Split(':'); string region = "us"; string product = "pro"; if (parts.Length > 1) { region = parts[1]; } if (parts.Length > 2) { product = parts[2]; } if (cdn == "bnet") { Config = CASCConfig.LoadOnlineStorageConfig(product, region); } else { if (cdn == "http") { string host = string.Join(":", parts.Skip(3)); Config = CASCConfig.LoadOnlineStorageConfig(host, product, region, true, true, true); } } } else { Config = CASCConfig.LoadLocalStorageConfig(Flags.OverwatchDirectory, !Flags.SkipKeys, false); } Config.Languages = new HashSet <string>(new[] { Flags.Language }); #endregion BuildVersion = uint.Parse(Config.BuildName.Split('.').Last()); if (Flags.SkipKeys) { Log("Disabling Key auto-detection..."); } Log("Using Overwatch Version {0}", Config.BuildName); CASC = CASCHandler.OpenStorage(Config); Root = CASC.Root as OwRootHandler; if (Root == null) { ErrorLog("Not a valid overwatch installation"); return; } if (!Root.APMFiles.Any()) { ErrorLog("Could not find the files for language {0}. Please confirm that you have that language installed, and are using the names from the target language.", Flags.Language); if (!Flags.GracefulExit) { return; } } string[] modeArgs = Flags.Positionals.Skip(2).ToArray(); switch (Flags.Mode.ToLower()) { case "extract": Extract(modeArgs); break; case "search": Search(modeArgs); break; case "search-type": SearchType(modeArgs); break; case "info": Info(modeArgs); break; case "convert": Convert(modeArgs); break; case "types": Types(modeArgs); break; default: Console.Out.WriteLine("Available modes: extract, search, search-type, info"); break; } }
public static void Main(string[] args) { string overwatchDir = args[0]; string mode = args[1]; const string language = "enUS"; // Usage: // {overwatch dir} dump -- Dump hashes // {overwatch dir} compare-enc {other ver num} -- Extract added files from encoding (requires dump from other version) // {overwatch dir} compare-idx {other ver num} -- Extract added files from indices (requires dump from other version) // {overwatch dir} nonblte -- Extract non-blte files // {overwatch dir} extract-encoding -- Extract encoding file // {overwatch dir} addcmf -- Extract all files from the cmf // casc setup Config = CASCConfig.LoadLocalStorageConfig(overwatchDir, false, false); Config.SpeechLanguage = Config.TextLanguage = language; if (mode != "allcmf" && mode != "dump-guids" && mode != "compare-guids" && mode != "dump-cmf") { Config.LoadContentManifest = false; Config.LoadPackageManifest = false; } CASC = CASCHandler.Open(Config); MapCMF(language); //var temp = Config.Builds[Config.ActiveBuild].KeyValue; BuildVersion = uint.Parse(Config.BuildVersion.Split('.').Last()); // c:\\ow\\game\\Overwatch dump // "D:\Games\Overwatch Test" compare 44022 if (mode == "dump") { Dump(args); } else if (mode == "compare-enc") { CompareEnc(args); } else if (mode == "compare-idx") { CompareIdx(args); } else if (mode == "nonblte") { DumpNonBLTE(args); } else if (mode == "extract-encoding") { ExtractEncodingFile(args); } else if (mode == "allcmf") { AllCMF(args); } else if (mode == "dump-guids") { DumpGUIDs(args); } else if (mode == "compare-guids") { CompareGUIDs(args); } else if (mode == "dump-cmf") { DumpCMF(args); } else { throw new Exception($"unknown mode: {mode}"); } }
static async Task ExtractJournal(string localeName = "en-US") { CASCConfig config = CASCConfig.LoadLocalStorageConfig(@"C:\Games\World of Warcraft", "wow"); CASCHandler handler = CASCHandler.OpenStorage(config, null); handler.Root.LoadListFile(Path.Combine(Environment.CurrentDirectory, "listfile8x.csv")); var locale = GetLocale(localeName); CASCFolder root = handler.Root.SetFlags(locale); handler.Root.MergeInstall(handler.Install); var dbfiles = (CASCFolder)root.Entries["dbfilesclient"]; // Apply translations to expansion / tiers var tiers = WowExpansion.All; foreach (var pair in dbfiles.EnumerateTable("JournalTier", handler)) { var tier = tiers.Single(x => x.Value.TierId == pair.Key); tier.Value.Name = pair.Value.GetField <string>(0); } var instanceTiers = new Dictionary <int, int>(); { foreach (var pair in dbfiles.EnumerateTable("JournalTierXInstance", handler)) { var tierId = pair.Value.GetField <int>(0); var instanceId = pair.Value.GetField <int>(1); instanceTiers[instanceId] = tierId; } } // All keystone dungeons Console.WriteLine(); Console.WriteLine("Loading challenge dungeons..."); var keystones = new List <MythicKeystone>(); foreach (var pair in dbfiles.EnumerateTable("MapChallengeMode", handler)) { var keystone = new MythicKeystone(); keystone.Id = pair.Key; keystone.Slug = GetSlug(pair.Value.GetField <string>(0)); keystone.Name = pair.Value.GetField <string>(0); keystone.MapId = pair.Value.GetField <ushort>(2); keystone.Flags = pair.Value.GetField <byte>(3); // keystone.ExpansionId = pair.Value.GetField<uint>(4); keystone.ScenarioId = 0; keystone.BronzeTimer = pair.Value.GetField <ushort>(4, 0); keystone.SilverTimer = pair.Value.GetField <ushort>(4, 1); keystone.GoldTimer = pair.Value.GetField <ushort>(4, 2); keystones.Add(keystone); } // Get all instances Console.WriteLine(); Console.WriteLine("Loading instances..."); var instances = (from pair in dbfiles.EnumerateTable("JournalInstance", handler) where instanceTiers.ContainsKey(pair.Key) select new Instance { Id = pair.Key, Slug = GetSlug(pair.Value.GetField <string>(0)), Name = pair.Value.GetField <string>(0), Description = pair.Value.GetField <string>(1), MapId = pair.Value.GetField <int>(3), BackgroundImageId = pair.Value.GetField <int>(4), ButtonImageId = pair.Value.GetField <int>(5), ButtonSmallImageId = pair.Value.GetField <int>(6), LoreImageId = pair.Value.GetField <int>(7), Order = pair.Value.GetField <int>(8), Flags = pair.Value.GetField <int>(9), TierId = instanceTiers[pair.Key] }).ToList(); // All encounters Console.WriteLine(); Console.WriteLine("Loading encounters..."); var encounters = (from pair in dbfiles.EnumerateTable("JournalEncounter", handler) select new Encounter { Id = pair.Key, Slug = "", Name = pair.Value.GetField <string>(0), Description = pair.Value.GetField <string>(1), MapX = pair.Value.GetField <float>(2, 0), MapY = pair.Value.GetField <float>(2, 1), InstanceId = pair.Value.GetField <int>(3), EncounterId = pair.Value.GetField <int>(4), Order = pair.Value.GetField <int>(5), FirstSectionId = pair.Value.GetField <int>(6), UiMapId = pair.Value.GetField <int>(7), MapDisplayConditionId = pair.Value.GetField <int>(8), Flags = pair.Value.GetField <byte>(9), Difficulty = pair.Value.GetField <byte>(10), Sections = new List <JournalSection>() }).ToList(); // All sections Console.WriteLine(); Console.WriteLine("Loading encounter sections..."); var sections = (from pair in dbfiles.EnumerateTable("JournalEncounterSection", handler) select new JournalSection { Id = pair.Key, Name = pair.Value.GetField <string>(0), Description = pair.Value.GetField <string>(1), JournalEncounterId = pair.Value.GetField <ushort>(2), Order = pair.Value.GetField <byte>(3), ParentSectionId = pair.Value.GetField <ushort>(4), FirstChildSectionId = pair.Value.GetField <ushort>(5), NextSiblingSectionId = pair.Value.GetField <ushort>(6), SectionType = pair.Value.GetField <byte>(7), // 3 = overview, 1 = creature, 2 = spell IconCreatureDisplayId = pair.Value.GetField <uint>(8), UiModelSceneId = pair.Value.GetField <int>(9), SpellId = pair.Value.GetField <int>(10), IconFileDataId = pair.Value.GetField <int>(11), Flags = pair.Value.GetField <ushort>(12), IconFlags = pair.Value.GetField <ushort>(13), // 1=tank, 2=dps, 4=healer, DifficultyMask = pair.Value.GetField <byte>(14), }).ToList(); // Build the tree Console.WriteLine(); Console.WriteLine("Building tree.."); foreach (var instance in instances) { instance.Encounters = new List <Encounter>(); foreach (var encounter in encounters.Where(x => x.InstanceId == instance.Id)) { encounter.Sections = BuildSectionTree(sections.Where(x => x.JournalEncounterId == encounter.Id)); instance.Encounters.Add(encounter); } } Console.WriteLine(); Console.WriteLine("Reading in edits..."); var existingKeystones = await ReadJson <IList <MythicKeystone> >("keystones.json"); var existingInstances = await ReadJson <IList <Instance> >("instances.json"); // Update keystone slugs foreach (var existingKeystone in existingKeystones) { var keystone = keystones.SingleOrDefault(x => x.Id == existingKeystone.Id); if (keystone == null) { continue; } keystone.Slug = existingKeystone.Slug; } // Update instance and encounter slugs foreach (var existingInstance in existingInstances) { var instance = instances.SingleOrDefault(x => x.Id == existingInstance.Id); if (instance == null) { continue; } instance.Slug = existingInstance.Slug; foreach (var existingEncounter in existingInstance.Encounters) { var encounter = instance.Encounters.SingleOrDefault(x => x.Id == existingEncounter.Id); if (encounter == null) { continue; } encounter.Slug = existingEncounter.Slug; } } Console.WriteLine(); Console.WriteLine("Exporting.."); await DumpJson("keystones.json", keystones); await DumpJson("tiers.json", tiers.Values); await DumpJson("instances.json", instances); Console.WriteLine(); Console.WriteLine("Done."); }
static void Main(string[] args) { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine(@" ____ __ "); Console.WriteLine(@"/\ _`\ /\ \ "); Console.WriteLine(@"\ \ \/\_\ __ __ _____\ \ \___ __ _ __ "); Console.WriteLine(@" \ \ \/_/_/\ \/\ \/\ '__`\ \ _ `\ /'__`\/\`'__\"); Console.WriteLine(@" \ \ \L\ \ \ \_\ \ \ \L\ \ \ \ \ \/\ __/\ \ \/ "); Console.WriteLine(@" \ \____/\/`____ \ \ ,__/\ \_\ \_\ \____\\ \_\ "); Console.WriteLine(@" \/___/ `/___/> \ \ \/ \/_/\/_/\/____/ \/_/ "); Console.WriteLine(@" /\___/\ \_\ "); Console.WriteLine(@" \/__/ \/_/ Core Data Extractor"); Console.WriteLine("\r"); BaseDirectory = Environment.CurrentDirectory; if (args.Length > 0) { BaseDirectory = Path.GetDirectoryName(args[0]); } Console.ForegroundColor = ConsoleColor.Green; uint localeMask = 0; CASCConfig config = CASCConfig.LoadLocalStorageConfig(BaseDirectory); string[] tagLines = config.BuildInfo[0]["Tags"].Split(' '); foreach (var line in tagLines) { if (!Enum.TryParse(typeof(LocaleFlags), line, out object locale)) { continue; } localeMask = localeMask | Convert.ToUInt32(locale); } installedLocalesMask = (LocaleFlags)localeMask; firstInstalledLocale = LocaleFlags.None; for (Locale i = 0; i < Locale.Total; ++i) { if (i == Locale.None) { continue; } if (!Convert.ToBoolean(installedLocalesMask & SharedConst.WowLocaleToCascLocaleFlags[(int)i])) { continue; } firstInstalledLocale = SharedConst.WowLocaleToCascLocaleFlags[(int)i]; break; } if (firstInstalledLocale < LocaleFlags.None) { Console.WriteLine("No locales detected"); Shutdown(); } Console.WriteLine("Initializing CASC library..."); CascHandler = CASCHandler.OpenStorage(config); CascHandler.Root.SetFlags(firstInstalledLocale, ContentFlags.None); Console.WriteLine("Done."); while (true) { PrintInstructions(); string[] lines = Console.ReadLine().Split(' '); for (var i = 0; i < lines.Length; ++i) { switch (lines[i]) { case "maps": ExtractMaps(); break; case "vmaps": ExtractVMaps(); break; case "mmaps": ExtractMMaps(lines); i = lines.Length; break; case "all": ExtractMaps(); ExtractVMaps(); //ExtractMMaps(lines); break; case "exit": return; } } } }
private static void Main() { AppDomain.CurrentDomain.UnhandledException += ExceptionHandler; Process.GetCurrentProcess().EnableRaisingEvents = true; AppDomain.CurrentDomain.ProcessExit += (sender, @event) => Console.ForegroundColor = ConsoleColor.Gray; Console.CancelKeyPress += (sender, @event) => Console.ForegroundColor = ConsoleColor.Gray; Console.OutputEncoding = Encoding.UTF8; Files = new Dictionary <ulong, ApplicationPackageManifest.Types.PackageRecord>(); TrackedFiles = new Dictionary <ushort, HashSet <ulong> >(); #region Tool Detection HashSet <Type> tools = new HashSet <Type>(); { Assembly asm = typeof(ITool).Assembly; Type t = typeof(ITool); List <Type> types = asm.GetTypes().Where(tt => tt != t && t.IsAssignableFrom(tt)).ToList(); foreach (Type tt in types) { ToolAttribute attrib = tt.GetCustomAttribute <ToolAttribute>(); if (tt.IsInterface || attrib == null) { continue; } tools.Add(tt); if (attrib.TrackTypes == null) { continue; } foreach (ushort type in attrib.TrackTypes) { if (!TrackedFiles.ContainsKey(type)) { TrackedFiles[type] = new HashSet <ulong>(); } } } } #endregion Flags = FlagParser.Parse <ToolFlags>(() => PrintHelp(tools)); if (Flags == null) { return; } //Logger.EXIT = !Flags.GracefulExit; ITool targetTool = null; ICLIFlags targetToolFlags = null; #region Tool Activation foreach (Type type in tools) { ToolAttribute attrib = type.GetCustomAttribute <ToolAttribute>(); if (!string.Equals(attrib.Keyword, Flags.Mode, StringComparison.InvariantCultureIgnoreCase)) { continue; } targetTool = Activator.CreateInstance(type) as ITool; if (attrib.CustomFlags != null) { Type flags = attrib.CustomFlags; if (typeof(ICLIFlags).IsAssignableFrom(flags)) { targetToolFlags = typeof(FlagParser).GetMethod(nameof(FlagParser.Parse), new Type[] { }).MakeGenericMethod(flags).Invoke(null, null) as ICLIFlags; } } break; } #endregion if (targetTool == null) { FlagParser.Help <ToolFlags>(false); PrintHelp(tools); if (Debugger.IsAttached) { Debugger.Break(); } return; } TankLib.Helpers.Logger.Info("Core", $"{Assembly.GetExecutingAssembly().GetName().Name} v{TankLib.Util.GetVersion(typeof(Program).Assembly)}"); TankLib.Helpers.Logger.Info("Core", $"CommandLine: [{string.Join(", ", Environment.GetCommandLineArgs().Skip(1).Select(x => $"\"{x}\""))}]"); #region Initialize CASC if (Flags.Language != null) { TankLib.Helpers.Logger.Info("CASC", $"Set language to {Flags.Language}"); } if (Flags.SpeechLanguage != null) { TankLib.Helpers.Logger.Info("CASC", $"Set speech language to {Flags.SpeechLanguage}"); } CASCHandler.Cache.CacheAPM = Flags.UseCache; CASCHandler.Cache.CacheCDN = Flags.UseCache; CASCHandler.Cache.CacheCDNData = Flags.CacheData; Config = CASCConfig.LoadFromString(Flags.OverwatchDirectory, Flags.SkipKeys); Config.SpeechLanguage = Flags.SpeechLanguage ?? Flags.Language ?? Config.SpeechLanguage; Config.TextLanguage = Flags.Language ?? Config.TextLanguage; if (Config != null) { if (Flags.Language != null && !Config.InstallData.Settings.Languages.Select(x => x.Language).Contains(Flags.Language)) { TankLib.Helpers.Logger.Warn("Core", "Battle.Net Agent reports that language {0} is not installed.", Flags.Language); } if (Config.InstallData.Uid != "prometheus") { TankLib.Helpers.Logger.Warn("Core", $"The branch \"{Config.InstallData.Uid}\" is not supported!. This might result in failure to load. Proceed with caution."); } } #endregion BuildVersion = uint.Parse(Config.BuildVersion.Split('.').Last()); if (BuildVersion < 39028) { TankLib.Helpers.Logger.Error("Core", "DataTool doesn't support Overwatch versions below 1.14. Please use OverTool"); } else if (BuildVersion < 39241) { TankLib.Helpers.Logger.Error("Core", "DataTool doesn't support this 1.14 release as it uses unmangeled hashes"); } else if (BuildVersion < 49154) { TankLib.Helpers.Logger.Error("Core", "This version of DataTool doesn't properly support versions below 1.26. Please downgrade DataTool."); } TankLib.Helpers.Logger.Info("Core", $"Using Overwatch Version {Config.BuildVersion}"); TankLib.Helpers.Logger.Info("CASC", "Initializing..."); CASC = CASCHandler.Open(Config); Root = CASC.RootHandler; //if (Root== null) { // ErrorLog("Not a valid overwatch installation"); // return; //} if (Config.InstallData != null) { } // Fail when trying to extract data from a specified language with 2 or less files found. if (!Root.APMFiles.Any()) { TankLib.Helpers.Logger.Error("Core", "Unable to load APM files for language {0}. Please confirm that you have that language installed.", Flags.Language); return; } TankLib.Helpers.Logger.Info("Core", "Mapping storage"); TrackedFiles[0x90] = new HashSet <ulong>(); IO.MapCMF(); IO.LoadGUIDTable(); Sound.WwiseBank.GetReady(); //foreach (KeyValuePair<ushort, HashSet<ulong>> type in TrackedFiles.OrderBy(x => x.Key)) { // //Console.Out.WriteLine($"Found type: {type.Key:X4} ({type.Value.Count} files)"); // Console.Out.WriteLine($"Found type: {type.Key:X4}"); //} #region Key Detection if (!Flags.SkipKeys) { TankLib.Helpers.Logger.Info("Core", "Checking ResourceKeys"); foreach (ulong key in TrackedFiles[0x90]) { if (!ValidKey(key)) { continue; } using (Stream stream = IO.OpenFile(Files[key])) { if (stream == null) { continue; } STUResourceKey resourceKey = GetInstance <STUResourceKey>(key); if (resourceKey == null || resourceKey.GetKeyID() == 0 || TACTKeyService.Keys.ContainsKey(resourceKey.GetReverseKeyID())) { continue; } TACTKeyService.Keys.Add(resourceKey.GetReverseKeyID(), resourceKey.m_key); TankLib.Helpers.Logger.Info("Core", $"Added ResourceKey {resourceKey.GetKeyIDString()}, Value: {resourceKey.GetKeyValueString()}"); } } } #endregion Stopwatch stopwatch = new Stopwatch(); TankLib.Helpers.Logger.Info("Core", "Tooling..."); var dbPath = Flags.ScratchDBPath; if (Flags.Deduplicate) { TankLib.Helpers.Logger.Warn("ScratchDB", "Will attempt to deduplicate files if extracting..."); if (!string.IsNullOrWhiteSpace(Flags.ScratchDBPath)) { TankLib.Helpers.Logger.Warn("ScratchDB", "Loading deduplication database..."); if (!File.Exists(dbPath)) { dbPath = Path.Combine(Path.GetFullPath(Flags.ScratchDBPath), "Scratch.db"); } SaveLogic.Combo.ScratchDBInstance.Load(dbPath); } } stopwatch.Start(); targetTool.Parse(targetToolFlags); stopwatch.Stop(); TankLib.Helpers.Logger.Success("Core", $"Execution finished in {stopwatch.Elapsed} seconds"); if (Flags.Deduplicate && !string.IsNullOrWhiteSpace(dbPath)) { TankLib.Helpers.Logger.Warn("ScratchDB", "Saving deduplication database..."); SaveLogic.Combo.ScratchDBInstance.Save(dbPath); } if (Debugger.IsAttached) { Debugger.Break(); } }