From 0c1b61edce826e750c6f5a5c9e46a141de23b7ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20D=C3=BCrrenberger?= Date: Tue, 17 Mar 2015 19:39:46 +0100 Subject: [PATCH] Initial implementation with original Delphi source code. --- README.md | 12 ++++ bnkextr.cpp | 181 ++++++++++++++++++++++++++++++++++++++++++++++++++++ bnkextr.dpr | 149 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 342 insertions(+) create mode 100644 README.md create mode 100644 bnkextr.cpp create mode 100644 bnkextr.dpr diff --git a/README.md b/README.md new file mode 100644 index 0000000..4d79b37 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +Wwise *.BNK File Extractor +========================== + +This is a C++ rewrite of **bnkextr** originally written by CTPAX-X in Delphi. +It extracts `WEM` files from the ever more popular Wwise `BNK` format. +Use [ww2ogg](https://github.com/hcs64/ww2ogg) to convert `WEM` files to the `OGG` format. + + +``` +Usage: bnkextr filename.bnk [/swap] + /swap - swap byte order (use it for unpacking 'Army of Two') +``` diff --git a/bnkextr.cpp b/bnkextr.cpp new file mode 100644 index 0000000..d299286 --- /dev/null +++ b/bnkextr.cpp @@ -0,0 +1,181 @@ +/* +http://www.geocities.jp/aoyoume/aotuv/index.html +http://rarewares.org/ogg-oggenc.php#oggenc-aotuv +http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1018956 +http://forum.xentax.com/viewtopic.php?f=17&t=3477 +http://wiki.xentax.com/index.php?title=Wwise_SoundBank_(*.bnk) + +.BNK Format specifications + +char {4} - header (BKHD) // BanK HeaDer +uint32 {4} - size of BKHD +uint32 {4} - unknown (version?) +uint32 {4} - unknown +uint32 {4} - unknown +uint32 {4} - unknown +byte {x} - zero padding (if any) + +char {4} - header (DIDX) // Data InDeX +uint32 {4} - size of DIDX +following by records 12 bytes each: + uint32 {4} - unknown + uint32 {4} - relative file offset from start of DATA, 16 bytes aligned + uint32 {4} - file size + +char {4} - header (DATA) +uint32 {4} - size of DATA + +char {4} - header (HIRC) // ??? +uint32 {4} - size of HIRC + +char {4} - header (STID) // Sound Type ID +uint32 {4} - size of STID +uint32 {4} - Always 1? +uint32 {4} - Always 1? +uint32 {4} - unknown +byte {1} - TID Length (TL) +char {TL} - TID string (usually same as filename, but without extension) + +Init.bnk +STMG +HIRC +FXPR +ENVS +*/ + +#include +#include +#include +#include + +struct Index; +struct Section; + +#pragma pack(push, 1) +struct Index +{ + int unknown; + int offset; + unsigned int size; +}; + +#pragma pack(push, 1) +struct Section +{ + char sign[4]; + unsigned int size; +}; +#pragma pack(pop) +#pragma pack(pop) + +int swap32(const int dw) +{ +#ifdef __GNUC__ + return __builtin_bswap32(dw); +#elif _MSC_VER + return _byteswap_ulong(dw); +#endif +} + +std::string zero_padding(unsigned int number) +{ + if(number < 10) + return "00" + std::to_string(number); + else if(number < 100) + return "0" + std::to_string(number); + else + return std::to_string(number); +} + +int main(int argc, char* argv[]) +{ + std::cout << "Wwise *.BNK File Extractor" << std::endl; + std::cout << "(c) CTPAX-X Team 2009-2010 - http://www.CTPAX-X.org" << std::endl; + std::cout << "(c) RAWR 2015 - http://www.rawr4firefall.com" << std::endl; + std::cout << std::endl; + + // Has no argument(s) + if((argc < 2) || (argc > 3)) + { + std::cout << "Usage: bnkextr filename.bnk [/swap]" << std::endl; + std::cout << "/swap - swap byte order (use it for unpacking 'Army of Two')" << std::endl; + return 0; + } + + std::fstream bnkfile; + bnkfile.open(argv[1], std::ios::binary | std::ios::in); + + // Could not open BNK file + if(!bnkfile.is_open()) + { + std::cout << "Can't open input file: " << argv[1] << std::endl; + return 0; + } + + unsigned int data_pos = 0; + std::vector files; + Section content_section; + Index content_index; + + while(bnkfile.read(reinterpret_cast(&content_section), sizeof(content_section))) + { + unsigned int section_pos = bnkfile.tellg(); + + // Was the /swap command used? + if(argc > 3) + content_section.size = swap32(content_section.size); + + if(std::strncmp(content_section.sign, "DIDX", 4) == 0) + { + // Read files + for(unsigned int i = 0; i < content_section.size; i += sizeof(content_index)) + { + bnkfile.read(reinterpret_cast(&content_index), sizeof(content_index)); + files.push_back(content_index); + } + } + else if(std::strncmp(content_section.sign, "STID", 4) == 0) + { + // To be implemented + } + else if(std::strncmp(content_section.sign, "DATA", 4) == 0) + { + // Get DATA offset + data_pos = bnkfile.tellg(); + } + + // Seek to the end of the section + bnkfile.seekg(section_pos + content_section.size); + } + + // Reset EOF + bnkfile.clear(); + + // Extract files + if((data_pos > 0) && (files.size() > 0)) + { + for(std::size_t i = 0; i < files.size(); ++i) + { + std::string filename = zero_padding(i + 1) + ".wem"; + + std::fstream wemfile; + wemfile.open(filename, std::ios::out | std::ios::binary); + + // Was the /swap command used? + if(argc > 3) + { + files[i].size = swap32(files[i].size); + files[i].offset = swap32(files[i].offset); + } + + if(wemfile.is_open()) + { + std::vector data(files[i].size, 0); + + bnkfile.seekg(data_pos + files[i].offset); + bnkfile.read(static_cast(data.data()), files[i].size); + wemfile.write(static_cast(data.data()), files[i].size); + } + } + } +} diff --git a/bnkextr.dpr b/bnkextr.dpr new file mode 100644 index 0000000..709533d --- /dev/null +++ b/bnkextr.dpr @@ -0,0 +1,149 @@ +Program bnktest; +{$APPTYPE CONSOLE} +(* +http://www.geocities.jp/aoyoume/aotuv/index.html +http://rarewares.org/ogg-oggenc.php#oggenc-aotuv +http://www.eveonline.com/ingameboard.asp?a=topic&threadID=1018956 +http://forum.xentax.com/viewtopic.php?f=17&t=3477 + +.BNK Format specifications + +char {4} - header (BKHD) // BanK HeaDer +uint32 {4} - size of BKHD +uint32 {4} - unknow (version?) +uint32 {4} - unknow +uint32 {4} - unknow +uint32 {4} - unknow +byte {x} - zero padding (if any) + +char {4} - header (DIDX) // Data InDeX +uint32 {4} - size of DIDX +following by records 12 bytes each: + uint32 {4} - unknow + uint32 {4} - relative file offset from start of DATA, 16 bytes aligned + uint32 {4} - file size + +char {4} - header (DATA) +uint32 {4} - size of DATA + +char {4} - header (HIRC) // ??? +uint32 {4} - size of HIRC + +char {4} - header (STID) // Sound Type ID +uint32 {4} - size of STID +uint32 {4} - Always 1? +uint32 {4} - Always 1? +uint32 {4} - unknow +byte {1} - TID Length (TL) +char {TL} - TID string (usually same as filename, but without extension) + +Init.bnk +STMG +HIRC +FXPR +ENVS +*) +Type + TSect = Packed Record + Sign: Array[0..3] Of Char; + Size: Integer; + End; + + TIDX = Packed Record + Unkn: Integer; + Offs: Integer; + Size: Integer; + End; + +Var + I, DT: Integer; + S, SI: String; + Fl, F: File; + FR: Array Of TIDX; + CS: TSect; + P: Pointer; + +function swap32(const dw: longint): longint; assembler; +asm + bswap eax +end; + +Function Int03Str(N: Integer): String; +Begin + Str(N, result); + While Length(result) < 3 Do result:='0' + result; +End; + +Begin + WriteLn('Divinity 2: Ego Draconis / Army of Two .BNK extractor'); + WriteLn('(c) CTPAX-X Team 2009-2010'); + WriteLn('http://www.CTPAX-X.org'); + WriteLn; + If ((ParamCount < 1) Or (ParamCount > 2)) Then + Begin + WriteLn('Usage: bnkextr filename.bnk [/swap]'); + WriteLn('/swap - swap byte order (use it for unpacking AoT)'); + Exit; + End; + AssignFile(Fl, ParamStr(1)); + FileMode:=0; + {$I-} + Reset(Fl, 1); + {$I+} + FileMode:=2; + If IOResult <> 0 Then + Begin + WriteLn('Can''t open input file: ' + ParamStr(1)); + Exit; + End; + // parse file structure + SetLength(FR, 0); + DT:=0; + SI:=''; + While Not EOF(Fl) Do + Begin + BlockRead(Fl, CS, SizeOf(TSect)); + If ParamCount > 1 Then CS.Size:=swap32(CS.Size); +// WriteLn(CS.Sign, ': ', CS.Size); + If CS.Sign = 'DIDX' Then + Begin + SetLength(FR, CS.Size Div SizeOf(FR[0])); + BlockRead(Fl, FR[0], CS.Size); + Continue; + End; + If CS.Sign = 'STID' Then + Begin + Seek(Fl, FilePos(Fl) + 12); + I:=0; + BlockRead(Fl, I, 1); + SetLength(SI, I); + BlockRead(Fl, SI[1], I); + Continue; + End; + If CS.Sign = 'DATA' Then DT:=FilePos(Fl); + Seek(Fl, FilePos(Fl) + CS.Size); + End; + // extract files + If ((DT > 0) And (Length(FR) > 0)) Then + For I:=0 To Length(FR)-1 Do + Begin + S:=SI + '.' + Int03Str(I + 1) + '.wav'; + Write(S); + If ParamCount > 1 Then + Begin + FR[I].Size:=swap32(FR[I].Size); + FR[I].Offs:=swap32(FR[I].Offs); + End; + Seek(Fl, DT + FR[I].Offs); + GetMem(P, FR[I].Size); + BlockRead(Fl, P^, FR[I].Size); + AssignFile(F, S); + ReWrite(F, 1); + BlockWrite(F, P^, FR[I].Size); + CloseFile(F); + FreeMem(P, FR[I].Size); + WriteLn; + End; + SetLength(FR, 0); + CloseFile(Fl); +End.