static void Main(string[] args)
        {
            Options options = null;

            try
            {
                options = CommandLine.Parse<Options>();
            }
            catch (CommandLineException exception)
            {
                Console.WriteLine(exception.ArgumentHelp.Message);
                Console.WriteLine();
                Console.WriteLine(exception.ArgumentHelp.GetHelpText(Console.BufferWidth));

#if DEBUG
                Console.ReadLine();
#endif
                return;
            }

            using (Stream stream = File.OpenRead(options.Source))
            {
                string bnkName = (options.Output != null) ? options.Output : Path.ChangeExtension(Path.GetFileName(options.Source), "bnk_pc");
                string mbnkName = Path.ChangeExtension(bnkName, "mbnk_pc");

                Console.WriteLine("Building {0} from {1}.", bnkName, options.Source);

                using (XmlReader reader = XmlReader.Create(stream))
                {
                    reader.ReadToFollowing("soundbank");
                    uint wwiseId = uint.Parse(reader.GetAttribute("wwiseId"));
                    string gameName = reader.GetAttribute("game");
                    IGameInstance instance = GameInstance.GetFromString(gameName);

                    StreamingSoundbank bank = new StreamingSoundbank();
                    bank.Header.WwiseBankId = wwiseId;

                    string xmlFolder = Path.GetDirectoryName(options.Source);

                    while (reader.ReadToFollowing("file"))
                    {
                        uint fileId = uint.Parse(reader.GetAttribute("id"));
                        string audio = reader.GetAttribute("audio");
                        MemoryStream metadataStream = null;
                        if (reader.ReadToDescendant("metadata"))
                        {
                            AudioMetadata metadata = new AudioMetadata(instance);

                            using (XmlReader metadataReader = reader.ReadSubtree())
                            {
                                metadataReader.Read();
                                uint metadataVersion = uint.Parse(metadataReader.GetAttribute("version"));
                                uint personaId = uint.Parse(metadataReader.GetAttribute("personaid"));
                                uint voicelineId = uint.Parse(metadataReader.GetAttribute("voicelineid"));
                                uint wavLengthMs = uint.Parse(metadataReader.GetAttribute("wavlengthms"));
                                metadata.Header.Version = metadataVersion;
                                metadata.Header.PersonaID = personaId;
                                metadata.Header.VoicelineID = voicelineId;
                                metadata.Header.WavLengthMs = wavLengthMs;

                                if (metadataReader.ReadToFollowing("subtitles"))
                                {
                                    while (metadataReader.ReadToFollowing("subtitle"))
                                    {
                                        string languageString = metadataReader.GetAttribute("language");
                                        metadataReader.Read();
                                        string text = metadataReader.ReadContentAsString();
                                        Language language = LanguageUtility.GetLanguageFromCode(languageString);
                                        metadata.Subtitles.Add(language, text);
                                    }
                                }

                                if (metadataReader.ReadToFollowing("lipsync"))
                                {
                                    string lipsyncBase64 = metadataReader.ReadContentAsString();
                                    metadata.LipsyncData = Convert.FromBase64String(lipsyncBase64);
                                }
                            }
                            metadataStream = new MemoryStream();
                            metadata.Save(metadataStream);
                        }

                        Stream audioStream = File.OpenRead(Path.Combine(xmlFolder, audio));
                        bank.AddFile(fileId, audioStream, metadataStream);
                    }

                    using (Stream outStream = File.Create(bnkName))
                    {
                        using (Stream mbnkOutStream = File.Create(mbnkName))
                        {
                            bank.Save(outStream, mbnkOutStream);
                        }
                    }
                }
            }
        }
        static void Main(string[] args)
        {
            Options options = null;

            try
            {
                options = CommandLine.Parse<Options>();
            }
            catch (CommandLineException exception)
            {
                Console.WriteLine(exception.ArgumentHelp.Message);
                Console.WriteLine();
                Console.WriteLine(exception.ArgumentHelp.GetHelpText(Console.BufferWidth));

#if DEBUG
                Console.ReadLine();
#endif
                return;
            }

            IGameInstance instance = GameInstance.GetFromString(options.Game);

            string ww2ogg = Path.Combine(ExeLocation, "external", "ww2ogg.exe");
            string codebooks = null;
            string codebooksPath = options.Codebooks;

            if (codebooks == null)
            {
                switch (instance.Game)
                {
                    case GameSteamID.SaintsRowTheThird:
                        codebooks = "packed_codebooks.bin";
                        break;
                    case GameSteamID.SaintsRowIV:
                    case GameSteamID.SaintsRowGatOutOfHell:
                        codebooks = "packed_codebooks_aoTuV_603.bin";
                        break;
                    default: throw new NotImplementedException();
                }
                codebooksPath = Path.Combine(ExeLocation, "external", codebooks);
            }


            string revorb = Path.Combine(ExeLocation, "external", "revorb.exe");
            bool failedToFindConversionRequirements = false;
            if (options.ConvertAudio)
            {
                if (!File.Exists(ww2ogg))
                {
                    Console.WriteLine("Could not find ww2ogg.exe at:\n{0}", ww2ogg);
                    failedToFindConversionRequirements = true;
                }

                if (!File.Exists(codebooksPath))
                {
                    if (codebooks != null)
                        Console.WriteLine("Could not find {0} at:\n{1}", codebooks, codebooksPath);
                    else
                        Console.WriteLine("Could not find your specified codebooks at:\n{1}", codebooks, codebooksPath);
                    failedToFindConversionRequirements = true;
                }

                if (!File.Exists(revorb))
                {
                    Console.WriteLine("Could not find revorb.exe at:\n{0}", revorb);
                    failedToFindConversionRequirements = true;
                }

                if (failedToFindConversionRequirements)
                {
                    Console.WriteLine("Can't convert audio.");
                }
            }

            using (Stream stream = File.OpenRead(options.Source))
            {
                var bnk = new StreamingSoundbank(stream);

                string bnkName = Path.GetFileName(options.Source);

                string folderName = (options.Output != null) ? options.Output : "extracted-" + bnkName;

                Console.WriteLine("Extracting {0} to {1}.", options.Source, folderName);

                Directory.CreateDirectory(folderName);

                if (File.Exists(Path.Combine(folderName, String.Format("{0}.xml", bnkName))))
                    File.Delete(Path.Combine(folderName, String.Format("{0}.xml", bnkName)));

                using (Stream xmlStream = File.Create(Path.Combine(folderName, Path.ChangeExtension(bnkName, "xml"))))
                {
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.Indent = true;
                    settings.IndentChars = "\t";
                    settings.NewLineChars = "\r\n";

                    using (XmlWriter writer = XmlWriter.Create(xmlStream, settings))
                    {
                        writer.WriteStartDocument();
                        writer.WriteStartElement("soundbank");
                        writer.WriteAttributeString("game", instance.Game.ToString());
                        writer.WriteAttributeString("wwiseId", bnk.Header.WwiseBankId.ToString());

                        //writer.WriteAttributeString()
                        int currentFile = 0;
                        foreach (SoundbankEntry entry in bnk.Files)
                        {
                            writer.WriteStartElement("file");
                            currentFile++;

                            writer.WriteAttributeString("id", entry.Info.FileId.ToString());

                            Console.Write("[{0}/{1}] Extracting audio... ", currentFile, bnk.Files.Count);
                            string audioFilename = String.Format("{0}_{1:D5}.wem", bnkName, currentFile);
                            using (Stream outputStream = File.Create(Path.Combine(folderName, audioFilename)))
                            {
                                using (Stream inputStream = entry.GetAudioStream())
                                {
                                    inputStream.CopyTo(outputStream);
                                }
                                outputStream.Flush();
                            }
                            Console.WriteLine("done.");
                            writer.WriteAttributeString("audio", audioFilename);

                            if (entry.Info.MetadataLength != 0)
                            {
                                Console.Write("[{0}/{1}] Extracting metadata... ", currentFile, bnk.Files.Count);
                                using (Stream metadataStream = entry.GetMetadataStream())
                                {
                                    writer.WriteStartElement("metadata");
                                    AudioMetadata metadata = new AudioMetadata(metadataStream, instance);

                                    writer.WriteAttributeString("version", metadata.Header.Version.ToString());
                                    writer.WriteAttributeString("personaid", metadata.Header.PersonaID.ToString());
                                    writer.WriteAttributeString("voicelineid", metadata.Header.VoicelineID.ToString());
                                    writer.WriteAttributeString("wavlengthms", metadata.Header.WavLengthMs.ToString());

                                    if (metadata.LipsyncData != null && metadata.LipsyncData.Length > 0)
                                    {
                                        writer.WriteStartElement("lipsync");
                                        writer.WriteString(Convert.ToBase64String(metadata.LipsyncData));
                                        writer.WriteEndElement(); // lipsync
                                    }

                                    if (metadata.Header.SubtitleSize > 0)
                                    {
                                        writer.WriteStartElement("subtitles");
                                        writer.WriteAttributeString("version", metadata.SubtitleHeader.Version.ToString());

                                        foreach (var subtitle in metadata.Subtitles)
                                        {
                                            Language language = subtitle.Key;
                                            string text = subtitle.Value;

                                            writer.WriteStartElement("subtitle");
                                            writer.WriteAttributeString("language", language.ToString());
                                            writer.WriteString(text);
                                            writer.WriteEndElement(); // subtitle
                                        }
                                        writer.WriteEndElement(); // subtitles
                                    }

                                    writer.WriteEndElement(); // metadata
                                }


                                Console.WriteLine("done.");
                            }

                            writer.WriteEndElement(); // file
                        }

                        writer.WriteEndElement(); // soundbank
                        writer.WriteEndDocument();
                    }
                }

                if (options.ConvertAudio)
                {
                    if (failedToFindConversionRequirements)
                    {
                        Console.WriteLine();
                        Console.WriteLine("Unable to convert extracted audio due to missing required files.");
                    }
                    else
                    {
                        Console.WriteLine();
                        Console.WriteLine("Converting extracted audio...");
                        for (int i = 1; i <= bnk.Files.Count; i++)
                        {
                            Console.Write("[{0}/{1}] Converting audio... ", i, bnk.Files.Count);
                            string oggFilename = String.Format("{0}_{1:D5}.ogg", bnkName, i);
                            string oggPath = Path.Combine(folderName, oggFilename);

                            string audioFilename = String.Format("{0}_{1:D5}.wem", bnkName, i);
                            string audioPath = Path.Combine(folderName, audioFilename);

                            ProcessStartInfo ww2oggPsi = new ProcessStartInfo(ww2ogg, String.Format(@"--pcb ""{0}"" -o ""{1}"" ""{2}""", codebooksPath, oggPath, audioPath));
                            ww2oggPsi.WindowStyle = ProcessWindowStyle.Hidden;
                            ww2oggPsi.CreateNoWindow = true;
                            Process ww2oggP = Process.Start(ww2oggPsi);
                            ww2oggP.WaitForExit();
                            Console.Write("revorb... ");

                            ProcessStartInfo revorbPsi = new ProcessStartInfo(revorb, String.Format(@"""{0}""", oggPath));
                            revorbPsi.WindowStyle = ProcessWindowStyle.Hidden;
                            revorbPsi.CreateNoWindow = true;
                            Process revorbP = Process.Start(revorbPsi);
                            revorbP.WaitForExit();
                            Console.WriteLine("done.");
                        }
                    }
                }

                Console.WriteLine();
                Console.WriteLine("Done.");

#if DEBUG
                Console.ReadLine();
#endif
            }
        }