public static void MapCMF(OwRootHandler ow, CASCHandler handler, Dictionary <ulong, PackageRecord> map, Dictionary <ushort, List <ulong> > track, string language) { if (ow == null || handler == null) { return; } foreach (ApplicationPackageManifest apm in ow.APMFiles) { if (!apm.Name.ToLowerInvariant().Contains("rdev")) { continue; } if (language != null && !apm.Name.ToLowerInvariant().Contains("l" + language.ToLowerInvariant())) { continue; } foreach (KeyValuePair <ulong, PackageRecord> pair in apm.FirstOccurence) { ushort id = GUID.Type(pair.Key); if (track != null && track.ContainsKey(id)) { track[id].Add(pair.Value.GUID); } if (map.ContainsKey(pair.Key)) { continue; } map[pair.Key] = pair.Value; } } }
public static void MapCMF(OwRootHandler ow, CASCHandler handler, Dictionary <ulong, Record> map, Dictionary <ushort, List <ulong> > track, string language) { if (ow == null || handler == null) { return; } foreach (APMFile apm in ow.APMFiles) { if (!apm.Name.ToLowerInvariant().Contains("rdev")) { continue; } if (language != null && !apm.Name.ToLowerInvariant().Contains("l" + language.ToLowerInvariant())) { continue; } foreach (KeyValuePair <ulong, CMFHashData> pair in apm.CMFMap) { ushort id = GUID.Type(pair.Key); if (track != null && track.ContainsKey(id)) { track[id].Add(pair.Value.id); } if (map.ContainsKey(pair.Key)) { continue; } Record rec = new Record { record = new PackageIndexRecord() }; rec.record.Flags = 0; rec.record.Key = pair.Value.id; rec.record.ContentKey = pair.Value.HashKey; rec.package = new APMPackage(new APMPackageItem()); rec.index = new PackageIndex(new PackageIndexItem()); EncodingEntry enc; if (handler.Encoding.GetEntry(pair.Value.HashKey, out enc)) { rec.record.Size = enc.Size; map.Add(pair.Key, rec); } } } }
public Highlight(Stream stream, CASCHandler handler, Dictionary <ulong, Record> records = null) { if (records != null) { this.records = records; } this.handler = handler; root = handler?.Root as OwRootHandler; Util.MapCMF(root, handler, records, null, null); using (BinaryReader reader = new BinaryReader(stream)) { header = reader.Read <HighlightHeader>(); if (header.Magic != MAGIC_CONSTANT) { throw new InvalidDataException("Data stream is not a highlight!"); } MemoryStream replayData = new MemoryStream(header.ReplayLength); stream.CopyBytes(replayData, header.ReplayLength); embeddedReplay = new Replay(replayData, handler, records); } }
public Replay(Stream stream, CASCHandler handler, Dictionary <ulong, Record> records = null) { if (records != null) { this.records = records; } this.handler = handler; root = handler?.Root as OwRootHandler; Util.MapCMF(root, handler, records, null, null); using (BinaryReader reader = new BinaryReader(stream)) { header = reader.Read <ReplayHeader>(); if (header.Magic != MAGIC_CONSTANT) { throw new InvalidDataException("Data stream is not a replay!"); } using (Decompressor decompressor = new Decompressor()) { byte[] temp = reader.ReadBytes((int)(stream.Length - stream.Position)); byte[] dec = decompressor.Unwrap(temp); decompressedStream = new MemoryStream(dec); } } }
private static void Main() { Console.OutputEncoding = Encoding.UTF8; Files = new Dictionary <ulong, 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("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 && encryptionKey.LongKey != 0 && !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) { Console.OutputEncoding = Encoding.UTF8; List <IOvertool> tools = new List <IOvertool>(); { Assembly asm = typeof(IOvertool).Assembly; Type t = typeof(IOvertool); List <Type> types = asm.GetTypes().Where(tt => tt != t && t.IsAssignableFrom(tt)).ToList(); foreach (Type tt in types) { if (tt.IsInterface) { continue; } IOvertool toolinst = (IOvertool)Activator.CreateInstance(tt); if (toolinst.Display) { tools.Add(toolinst); } } } OverToolFlags flags = FlagParser.Parse <OverToolFlags>(() => PrintHelp(tools)); if (flags == null) { return; } bool quiet = flags.Quiet; string root = flags.OverwatchDirectory; char opt = flags.Mode[0]; IOvertool tool = null; Dictionary <ushort, List <ulong> > track = new Dictionary <ushort, List <ulong> >(); track[0x90] = new List <ulong>(); // internal requirements toolsMap = new Dictionary <char, IOvertool>(); foreach (IOvertool t in tools) { if (t.Opt == opt) { tool = t; } foreach (ushort tr in t.Track) { if (!track.ContainsKey(tr)) { track[tr] = new List <ulong>(); } } if (toolsMap.ContainsKey(t.Opt)) { Console.Out.WriteLine("Duplicate opt! {0} conflicts with {1}", t.Title, toolsMap[t.Opt].Title); } toolsMap[t.Opt] = t; } if (tool == null || flags.Positionals.Length - 2 < tool.MinimumArgs) { PrintHelp(tools); return; } Dictionary <ulong, Record> map = new Dictionary <ulong, Record>(); Console.Out.WriteLine("{0} v{1}", Assembly.GetExecutingAssembly().GetName().Name, OWLib.Util.GetVersion()); Console.Out.WriteLine("Initializing CASC..."); Console.Out.WriteLine("Set language to {0}", flags.Language); CDNIndexHandler.Cache.Enabled = flags.UseCache; CDNIndexHandler.Cache.CacheData = flags.CacheData; CDNIndexHandler.Cache.Validate = flags.ValidateCache; 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, !flags.SkipKeys, false); } config.Languages = new HashSet <string>(new string[1] { flags.Language }); if (flags.SkipKeys) { Console.Out.WriteLine("Disabling Key auto-detection..."); } Console.Out.WriteLine("Using Overwatch Version {0}", config.BuildName); CASCHandler handler = CASCHandler.OpenStorage(config); OwRootHandler ow = handler.Root as OwRootHandler; if (ow == null) { Console.Error.WriteLine("Not a valid overwatch installation"); return; } // Fail when trying to extract data from a specified language with 2 or less files found. if (ow.APMFiles.Count() == 0) { Console.Error.WriteLine("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); return; } Console.Out.WriteLine("Mapping..."); Util.MapCMF(ow, handler, map, track, flags); if (!flags.SkipKeys) { Console.Out.WriteLine("Adding Encryption Keys..."); foreach (ulong key in track[0x90]) { if (!map.ContainsKey(key)) { continue; } using (Stream stream = Util.OpenFile(map[key], handler)) { if (stream == null) { continue; } STUD stud = new STUD(stream); if (stud.Instances[0].Name != stud.Manager.GetName(typeof(EncryptionKey))) { continue; } EncryptionKey ek = (EncryptionKey)stud.Instances[0]; if (!KeyService.keys.ContainsKey(ek.KeyNameLong)) { KeyService.keys.Add(ek.KeyNameLong, ek.KeyValueText.ToByteArray()); Console.Out.WriteLine("Added Encryption Key {0}, Value: {1}", ek.KeyNameText, ek.KeyValueText); } } } } Console.Out.WriteLine("Tooling..."); tool.Parse(track, map, handler, quiet, flags); if (System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Break(); } }
static void Main(string[] args) { Console.OutputEncoding = Encoding.UTF8; List <IOvertool> tools = new List <IOvertool>(); { Assembly asm = typeof(IOvertool).Assembly; Type t = typeof(IOvertool); List <Type> types = asm.GetTypes().Where(tt => tt != t && t.IsAssignableFrom(tt)).ToList(); foreach (Type tt in types) { if (tt.IsInterface) { continue; } IOvertool toolinst = (IOvertool)Activator.CreateInstance(tt); if (toolinst.Display) { tools.Add(toolinst); } } } OverToolFlags flags = FlagParser.Parse <OverToolFlags>(() => PrintHelp(tools)); if (flags == null) { return; } Logger.EXIT = !flags.GracefulExit; bool quiet = flags.Quiet; string root = flags.OverwatchDirectory; string opt = flags.Mode; IOvertool tool = null; Dictionary <ushort, List <ulong> > track = new Dictionary <ushort, List <ulong> > { [0x90] = new List <ulong>() // internal requirements }; toolsMap = new Dictionary <string, IOvertool>(); foreach (IOvertool t in tools) { if (t.FullOpt == opt || (opt.Length == 1 && t.Opt != (char)0 && t.Opt == opt[0])) { tool = t; } if (t.Track != null) { foreach (ushort tr in t.Track) { if (!track.ContainsKey(tr)) { track[tr] = new List <ulong>(); } } } if (toolsMap.ContainsKey(t.FullOpt)) { Console.Out.WriteLine("Duplicate opt! {0} conflicts with {1}", t.Title, toolsMap[t.FullOpt].Title); } if (t.FullOpt.Length == 1) { Console.Out.WriteLine("FullOpt should not be length 1 for {0}", t.Title); } toolsMap[t.FullOpt] = t; } if (tool == null || flags.Positionals.Length - 2 < tool.MinimumArgs) { PrintHelp(tools); return; } Dictionary <ulong, Record> map = new Dictionary <ulong, Record>(); Console.Out.WriteLine("{0} v{1}", Assembly.GetExecutingAssembly().GetName().Name, OWLib.Util.GetVersion()); Console.Out.WriteLine("Initializing CASC..."); Console.Out.WriteLine("Set language to {0}", flags.Language); CDNIndexHandler.Cache.Enabled = flags.UseCache; CDNIndexHandler.Cache.CacheData = flags.CacheData; CDNIndexHandler.Cache.Validate = flags.ValidateCache; 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, !flags.SkipKeys, false); } config.Languages = new HashSet <string>(new string[1] { flags.Language }); if (flags.SkipKeys) { Console.Out.WriteLine("Disabling Key auto-detection..."); } Regex versionRegex = new Regex(@"\d+\.\d+"); Match versionMatch = versionRegex.Match(config.BuildName); if (versionMatch.Success) { float version = float.Parse(versionMatch.Value); if (version > 1.13) { Console.ForegroundColor = ConsoleColor.Red; Console.Out.WriteLine("==========\nWARNING: Overtool only works with Overwatch version 1.13 and below! You are using {0}!", config.BuildName); Console.Out.WriteLine("You must use DataTool for Overwatch 1.14 and above!\n=========="); Console.ResetColor(); } } Console.Out.WriteLine("Using Overwatch Version {0}", config.BuildName); CASCHandler handler = CASCHandler.OpenStorage(config); OwRootHandler ow = handler.Root as OwRootHandler; if (ow == null) { Console.Error.WriteLine("Not a valid overwatch installation"); return; } // Fail when trying to extract data from a specified language with 2 or less files found. if (ow.APMFiles.Count() == 0) { Console.Error.WriteLine("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; } } Console.Out.WriteLine("Mapping..."); Util.MapCMF(ow, handler, map, track, flags.Language); if (!flags.SkipKeys) { Console.Out.WriteLine("Adding Encryption Keys..."); foreach (ulong key in track[0x90]) { if (!map.ContainsKey(key)) { continue; } using (Stream stream = Util.OpenFile(map[key], handler)) { if (stream == null) { continue; } ISTU stu = ISTU.NewInstance(stream, UInt32.Parse(config.BuildName.Split('.').Last())); if (!(stu.Instances.FirstOrDefault() is STUEncryptionKey)) { continue; } STUEncryptionKey ek = stu.Instances.FirstOrDefault() as STUEncryptionKey; if (ek != null && !KeyService.keys.ContainsKey(ek.LongRevKey)) { KeyService.keys.Add(ek.LongRevKey, ek.KeyValue); Console.Out.WriteLine("Added Encryption Key {0}, Value: {1}", ek.KeyNameProper, ek.Key); } } } } Console.Out.WriteLine("Tooling..."); tool.Parse(track, map, handler, quiet, flags); if (System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Break(); } }
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) { 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(); } } } } }