private static void DumpVPK(Package package, string type, string newType)
        {
            if (!package.Entries.ContainsKey(type))
            {
                Console.WriteLine("There are no files of type \"{0}\".", type);

                return;
            }

            var entries = package.Entries[type];

            foreach (var file in entries)
            {
                var filePath = string.Format("{0}.{1}", file.FileName, file.TypeName);

                if (!string.IsNullOrWhiteSpace(file.DirectoryName))
                {
                    filePath = Path.Combine(file.DirectoryName, filePath);
                }

                filePath = FixPathSlahes(filePath);

                Console.WriteLine("\t[archive index: {0:D3}] {1}", file.ArchiveIndex, filePath);

                byte[] output;
                package.ReadEntry(file, out output);

                if (type.EndsWith("_c", StringComparison.Ordinal))
                {
                    using (var resource = new Resource())
                    {
                        using (var memory = new MemoryStream(output))
                        {
                            resource.Read(memory);
                        }

                        output = ((Panorama)resource.Blocks[BlockType.DATA]).Data;
                    }
                }

                if (Options.OutputFile != null)
                {
                    if (type != newType)
                    {
                        filePath = Path.ChangeExtension(filePath, newType);
                    }

                    DumpFile(filePath, output);
                }
            }
        }
        private void TestVPKExtraction(string path)
        {
            using (var package = new Package())
            {
                package.Read(path);

                Assert.AreEqual(2, package.Entries.Count);
                Assert.Contains("jpg", package.Entries.Keys);
                Assert.Contains("proto", package.Entries.Keys);

                var flatEntries = new Dictionary<string, PackageEntry>();

                using (var sha1 = new SHA1CryptoServiceProvider())
                {
                    var data = new Dictionary<string, string>();

                    foreach (var a in package.Entries)
                    {
                        foreach (var b in a.Value)
                        {
                            Assert.AreEqual(a.Key, b.TypeName);

                            flatEntries.Add(b.FileName, b);

                            byte[] entry;
                            package.ReadEntry(b, out entry);

                            data.Add(b.FileName + '.' + b.TypeName, BitConverter.ToString(sha1.ComputeHash(entry)).Replace("-", ""));
                        }
                    }

                    Assert.AreEqual(3, data.Count);
                    Assert.AreEqual("E0D865F19F0A4A7EA3753FBFCFC624EE8B46928A", data["kitten.jpg"]);
                    Assert.AreEqual("2EFFCB09BE81E8BEE88CB7BA8C18E87D3E1168DB", data["steammessages_base.proto"]);
                    Assert.AreEqual("22741F66442A4DC880725D2CC019E6C9202FD70C", data["steammessages_clientserver.proto"]);
                }

                Assert.AreEqual(flatEntries["kitten"].TotalLength, 16361);
                Assert.AreEqual(flatEntries["steammessages_base"].TotalLength, 2563);
                Assert.AreEqual(flatEntries["steammessages_clientserver"].TotalLength, 39177);
            }
        }
        public static Resource LoadFileByAnyMeansNecessary(string file, string currentFullPath, Package currentPackage)
        {
            Resource resource;

            // TODO: Might conflict where same file name is available in different paths
            if (CachedResources.TryGetValue(file, out resource) && resource.Reader != null)
            {
                return resource;
            }

            resource = new Resource();

            var entry = currentPackage?.FindEntry(file);

            if (entry != null)
            {
                byte[] output;
                currentPackage.ReadEntry(entry, out output);
                resource.Read(new MemoryStream(output));
                CachedResources[file] = resource;

                return resource;
            }

            var paths = Settings.GameSearchPaths.ToList();
            var packages = new List<Package>();

            foreach (var searchPath in paths.Where(searchPath => searchPath.EndsWith(".vpk")).ToList())
            {
                paths.Remove(searchPath);

                Package package;
                if (!CachedPackages.TryGetValue(searchPath, out package))
                {
                    Console.WriteLine("Preloading vpk {0}", searchPath);

                    package = new Package();
                    package.Read(searchPath);
                    CachedPackages[searchPath] = package;
                }

                packages.Add(package);
            }

            foreach (var package in packages)
            {
                entry = package?.FindEntry(file);

                if (entry != null)
                {
                    byte[] output;
                    package.ReadEntry(entry, out output);
                    resource.Read(new MemoryStream(output));
                    CachedResources[file] = resource;

                    return resource;
                }
            }

            var path = FindResourcePath(paths, file, currentFullPath);

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

            resource.Read(path);
            CachedResources[file] = resource;

            return resource;
        }
        private static void ParseVPK(string path)
        {
            lock (ConsoleWriterLock)
            {
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("--- Listing files in package \"{0}\" ---", path);
                Console.ResetColor();
            }

            var sw = Stopwatch.StartNew();

            var package = new Package();

            try
            {
                package.Read(path);
            }
            catch (Exception e)
            {
                lock (ConsoleWriterLock)
                {
                    Console.ForegroundColor = ConsoleColor.Cyan;
                    Console.WriteLine(e);
                    Console.ResetColor();
                }
            }

            if (Options.VerifyVPKChecksums)
            {
                try
                {
                    package.VerifyHashes();

                    Console.WriteLine("VPK verification succeeded");
                }
                catch (Exception e)
                {
                    lock (ConsoleWriterLock)
                    {
                        Console.WriteLine("Failed to verify checksums and signature of given VPK:");
                        Console.ForegroundColor = ConsoleColor.Red;
                        Console.WriteLine(e.Message);
                        Console.ResetColor();
                    }
                }

                return;
            }

            if (Options.OutputFile == null)
            {
                Console.WriteLine("--- Files in package:");

                var orderedEntries = package.Entries.OrderByDescending(x => x.Value.Count).ThenBy(x => x.Key);

                if (Options.CollectStats)
                {
                    TotalFiles += orderedEntries
                        .Where(entry => entry.Key.EndsWith("_c", StringComparison.Ordinal))
                        .Sum(x => x.Value.Count);
                }

                foreach (var entry in orderedEntries)
                {
                    Console.WriteLine("\t{0}: {1} files", entry.Key, entry.Value.Count);

                    if (Options.CollectStats && entry.Key.EndsWith("_c", StringComparison.Ordinal))
                    {
                        foreach (var file in entry.Value)
                        {
                            lock (ConsoleWriterLock)
                            {
                                Console.ForegroundColor = ConsoleColor.Green;
                                Console.WriteLine("[{0}/{1}] {2}", ++CurrentFile, TotalFiles, file.GetFullPath());
                                Console.ResetColor();
                            }

                            byte[] output;
                            package.ReadEntry(file, out output);

                            using (var stream = new MemoryStream(output))
                            {
                                ProcessFile(file.GetFullPath(), stream);
                            }
                        }
                    }
                }
            }
            else
            {
                Console.WriteLine("--- Dumping decompiled files...");

                DumpVPK(package, "vxml_c", "xml");
                DumpVPK(package, "vjs_c", "js");
                DumpVPK(package, "vcss_c", "css");
                DumpVPK(package, "vsndevts_c", "vsndevts");
                DumpVPK(package, "vpcf_c", "vpcf");

                DumpVPK(package, "txt", "txt");
                DumpVPK(package, "cfg", "cfg");
                DumpVPK(package, "res", "res");
            }

            if (Options.OutputVPKDir)
            {
                foreach (var type in package.Entries)
                {
                    foreach (var file in type.Value)
                    {
                        Console.WriteLine(file);
                    }
                }
            }

            sw.Stop();

            Console.WriteLine("Processed in {0}ms", sw.ElapsedMilliseconds);
        }