├── .gitignore ├── tests ├── testdata │ └── wem │ │ ├── test1.ogg │ │ └── test1.wem └── wem.cpp ├── include ├── ww2ogg │ ├── packed_codebooks.h │ ├── crc.h │ ├── ww2ogg.h │ ├── errors.h │ ├── wwriff.h │ ├── codebook.h │ └── bitstream.h ├── kaitai │ ├── custom_decoder.h │ ├── kaitaistruct.h │ ├── structs │ │ ├── w3sc.h │ │ ├── vlq.h │ │ └── wem.h │ ├── exceptions.h │ └── kaitaistream.h ├── revorb │ └── revorb.hpp ├── wwtools │ ├── wwtools.hpp │ ├── w3sc.hpp │ ├── bnk.hpp │ └── util │ │ └── write.hpp └── util │ └── rang.hpp ├── .gitmodules ├── src ├── wwtools │ ├── wwtools.cpp │ ├── w3sc.cpp │ └── bnk.cpp ├── ww2ogg │ ├── ww2ogg.cpp │ ├── crc.c │ └── codebook.cpp ├── kaitai │ └── structs │ │ ├── vlq.cpp │ │ ├── w3sc.cpp │ │ └── wem.cpp ├── revorb │ └── revorb.cpp └── main.cpp ├── .github └── workflows │ ├── doxygen-publish.yml │ ├── check-build.yml │ └── release.yml ├── LICENSE ├── ksy ├── bundle.ksy ├── w3sc.ksy ├── vlq.ksy ├── wem.ksy └── bnk.ksy ├── LICENSE-ww2ogg ├── README.md └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | .cache/ 3 | compile_commands.json 4 | docs/* 5 | !docs/Doxyfile 6 | !docs/doxygen-awesome-css 7 | -------------------------------------------------------------------------------- /tests/testdata/wem/test1.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolvenKit/wwise-audio-tools/HEAD/tests/testdata/wem/test1.ogg -------------------------------------------------------------------------------- /tests/testdata/wem/test1.wem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolvenKit/wwise-audio-tools/HEAD/tests/testdata/wem/test1.wem -------------------------------------------------------------------------------- /include/ww2ogg/packed_codebooks.h: -------------------------------------------------------------------------------- 1 | #ifndef WW2OGG_PACKED_CODEBOOKS_H 2 | #define WW2OGG_PACKED_CODEBOOKS_H 3 | 4 | namespace ww2ogg { 5 | extern unsigned char packed_codebooks_bin[]; 6 | extern unsigned int packed_codebooks_bin_len; 7 | } // namespace ww2ogg 8 | 9 | #endif // WW2OGG_PACKED_CODEBOOKS_H -------------------------------------------------------------------------------- /include/ww2ogg/crc.h: -------------------------------------------------------------------------------- 1 | #ifndef WW2OGG_CRC_H 2 | #define WW2OGG_CRC_H 3 | 4 | #include 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif // __cplusplus 9 | 10 | uint32_t checksum(unsigned char *data, int bytes); 11 | 12 | #ifdef __cplusplus 13 | } 14 | #endif // __cplusplus 15 | 16 | #endif // WW2OGG_CRC_H 17 | -------------------------------------------------------------------------------- /include/kaitai/custom_decoder.h: -------------------------------------------------------------------------------- 1 | #ifndef KAITAI_CUSTOM_DECODER_H 2 | #define KAITAI_CUSTOM_DECODER_H 3 | 4 | #include 5 | 6 | namespace kaitai { 7 | 8 | class custom_decoder { 9 | public: 10 | virtual ~custom_decoder() {}; 11 | virtual std::string decode(std::string src) = 0; 12 | }; 13 | 14 | } 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /include/revorb/revorb.hpp: -------------------------------------------------------------------------------- 1 | #ifndef REVORB_REVORB_H 2 | #define REVORB_REVORB_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace revorb { 10 | bool revorb(std::istream &indata, std::stringstream &outdata); 11 | } // namespace revorb 12 | 13 | #endif // REVORB_REVORB_H 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libogg"] 2 | path = libogg 3 | url = https://gitlab.xiph.org/xiph/ogg 4 | [submodule "libvorbis"] 5 | path = libvorbis 6 | url = https://gitlab.xiph.org/xiph/vorbis 7 | [submodule "docs/doxygen-awesome-css"] 8 | path = docs/doxygen-awesome-css 9 | url = https://github.com/jothepro/doxygen-awesome-css.git 10 | -------------------------------------------------------------------------------- /include/kaitai/kaitaistruct.h: -------------------------------------------------------------------------------- 1 | #ifndef KAITAI_STRUCT_H 2 | #define KAITAI_STRUCT_H 3 | 4 | #include 5 | 6 | namespace kaitai { 7 | 8 | class kstruct { 9 | public: 10 | kstruct(kstream *_io) { m__io = _io; } 11 | virtual ~kstruct() {} 12 | protected: 13 | kstream *m__io; 14 | public: 15 | kstream *_io() { return m__io; } 16 | }; 17 | 18 | } 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /src/wwtools/wwtools.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "revorb/revorb.hpp" 4 | #include "ww2ogg/ww2ogg.h" 5 | #include "wwtools/bnk.hpp" 6 | #include "wwtools/wwtools.hpp" 7 | 8 | namespace wwtools { 9 | std::string wem_to_ogg(const std::string &indata) { 10 | std::stringstream wem_out; 11 | std::stringstream revorb_out; 12 | 13 | if (!ww2ogg::ww2ogg(indata, wem_out)) 14 | return ""; 15 | 16 | if (!revorb::revorb(wem_out, revorb_out)) { 17 | return ""; 18 | } 19 | 20 | return revorb_out.str(); 21 | } 22 | } // namespace wwtools 23 | -------------------------------------------------------------------------------- /.github/workflows/doxygen-publish.yml: -------------------------------------------------------------------------------- 1 | name: doxygen-publish 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - uses: actions/checkout@v2 13 | with: 14 | submodules: recursive 15 | 16 | - name: Build documentation 17 | uses: mattnotmitt/doxygen-action@v1.9.1 18 | with: 19 | working-directory: "." 20 | doxyfile-path: "./docs/Doxyfile" 21 | 22 | - name: Publish to GitHub Pages 23 | uses: peaceiris/actions-gh-pages@v3 24 | with: 25 | github_token: ${{ secrets.GITHUB_TOKEN }} 26 | publish_dir: ./docs/html -------------------------------------------------------------------------------- /include/wwtools/wwtools.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file wwtools.hpp 3 | * @author Abheek Dhawan (abheekd at protonmail dot com) 4 | * @brief helper functions for other Wwise file actions 5 | * @date 2022-05-26 6 | * 7 | * @copyright Copyright (c) 2022 RED Modding Tools 8 | * 9 | */ 10 | 11 | #ifndef WWTOOLS_WWTOOLS_HPP 12 | #define WWTOOLS_WWTOOLS_HPP 13 | 14 | #include 15 | 16 | /** 17 | * @namespace wwtools 18 | * @brief parent namespace for specific file type helper functions 19 | * 20 | */ 21 | namespace wwtools { 22 | /** 23 | * @brief get OGG file data from WEM file data 24 | * 25 | * @param indata WEM file data 26 | * @return OGG file data 27 | */ 28 | std::string wem_to_ogg(const std::string &indata); 29 | } // namespace wwtools 30 | 31 | #endif // WWTOOLS_WWTOOLS_HPP 32 | -------------------------------------------------------------------------------- /include/ww2ogg/ww2ogg.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "packed_codebooks.h" 5 | #ifndef WW2OGG_WW2OGG_H 6 | #define WW2OGG_WW2OGG_H 7 | 8 | #include "wwriff.h" 9 | 10 | namespace ww2ogg { 11 | bool ww2ogg(const std::string &indata, std::ostream &outdata, 12 | unsigned char codebooks_data[] = packed_codebooks_bin, 13 | bool inline_codebooks = false, bool full_setup = false, 14 | ForcePacketFormat force_packet_format = kNoForcePacketFormat); 15 | 16 | std::string 17 | wem_info(const std::string &indata, 18 | unsigned char codebooks_data[] = packed_codebooks_bin, 19 | bool inline_codebooks = false, bool full_setup = false, 20 | ForcePacketFormat force_packet_format = kNoForcePacketFormat); 21 | } // namespace ww2ogg 22 | 23 | #endif // WW2OGG_WW2OGG_H 24 | -------------------------------------------------------------------------------- /tests/wem.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "wwtools/wwtools.hpp" 8 | 9 | std::string convert(std::string path) { 10 | std::ifstream filein(path, std::ios::binary); 11 | 12 | // reserve memory upfront 13 | std::string indata; 14 | filein.seekg(0, std::ios::end); 15 | indata.reserve(filein.tellg()); 16 | filein.seekg(0, std::ios::beg); 17 | 18 | indata.assign( 19 | (std::istreambuf_iterator(filein)), 20 | std::istreambuf_iterator() 21 | ); 22 | 23 | std::string outdata = wwtools::wem_to_ogg(indata); 24 | return outdata; 25 | } 26 | 27 | TEST_CASE( "Compare WEM converted with the Wwise Audio Tools to those converted with the individual standalone tools ", "[wwise-audio-tools]") { 28 | std::ifstream ogg_in("testdata/wem/test1.ogg", std::ios::binary); 29 | std::stringstream ogg_in_s; 30 | ogg_in_s << ogg_in.rdbuf(); 31 | 32 | REQUIRE( convert("testdata/wem/test1.wem") == ogg_in_s.str() ); 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 RED Modding tools 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/check-build.yml: -------------------------------------------------------------------------------- 1 | name: "check-build" 2 | on: 3 | push: 4 | branches: 5 | - '*' 6 | pull_request: 7 | branches: 8 | - '*' 9 | 10 | jobs: 11 | linux: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v2 17 | with: 18 | submodules: true 19 | 20 | - name: Install Catch2 for testing 21 | run : | 22 | git clone https://github.com/catchorg/Catch2.git 23 | cd Catch2 24 | cmake -Bbuild -H. -DBUILD_TESTING=OFF 25 | sudo cmake --build build/ --target install 26 | 27 | - name: Configure CMake 28 | run: cmake -Bbuild 29 | 30 | - name: Build 31 | run: cmake --build build 32 | 33 | - name: Test with Catch2 34 | run: cd build/bin && ./tests 35 | 36 | windows: 37 | runs-on: windows-latest 38 | 39 | steps: 40 | - name: Checkout 41 | uses: actions/checkout@v2 42 | with: 43 | submodules: true 44 | 45 | - name: Configure CMake 46 | run: cmake -G "Visual Studio 17 2022" -B build 47 | 48 | - name: Build 49 | run: cmake --build build 50 | -------------------------------------------------------------------------------- /include/wwtools/w3sc.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file w3sc.hpp 3 | * @author Abheek Dhawan (abheekd at protonmail dot com) 4 | * @brief works with sound cache files from The Witcher 3 5 | * @date 2022-05-26 6 | * 7 | * @copyright Copyright (c) 2022 RED Modding Tools 8 | * 9 | */ 10 | 11 | #ifndef WWTOOLS_W3SC_HPP 12 | #define WWTOOLS_W3SC_HPP 13 | 14 | #include 15 | #include 16 | 17 | #include "kaitai/kaitaistream.h" 18 | #include "kaitai/structs/w3sc.h" 19 | 20 | /** 21 | * @namespace wwtools::w3sc 22 | * @brief contains helper functions for The Witcher 3 sound cache files 23 | * 24 | */ 25 | namespace wwtools::w3sc { 26 | /** 27 | * @brief get the info string 28 | * 29 | * @param indata std::string with the file data 30 | * @return a printable info string 31 | */ 32 | std::string get_info(const std::string &indata); 33 | 34 | /** 35 | * @brief create a sound cache file from BNK and WEM files 36 | * 37 | * @param files input files to be embedded into a BNK 38 | * @param os output stream for the file 39 | */ 40 | void create(const std::vector> &files, 41 | std::ostream &os); 42 | } // namespace wwtools::w3sc 43 | 44 | #endif // WWTOOLS_W3SC_HPP 45 | -------------------------------------------------------------------------------- /ksy/bundle.ksy: -------------------------------------------------------------------------------- 1 | meta: 2 | id: bundle 3 | file-extension: bundle 4 | endian: le 5 | 6 | seq: 7 | - id: magic 8 | contents: 'POTATO70' 9 | - id: bundle_size 10 | type: u4 11 | - id: dummy_size 12 | type: u4 13 | - id: data_offset 14 | type: u4 15 | - id: buffer_for_info_off 16 | size: 32 - 8 - (4 * 3) 17 | - id: files 18 | type: embedded_file 19 | repeat: until 20 | repeat-until: _io.pos >= data_offset 21 | 22 | types: 23 | embedded_file: 24 | seq: 25 | - id: name 26 | type: str 27 | size: 0x100 28 | encoding: utf-8 29 | - id: hash 30 | #type: str 31 | size: 0x10 32 | #encoding: utf-8 33 | - id: zero1 34 | contents: [00, 00, 00, 00] 35 | - id: size 36 | type: u4 37 | - id: compressed_size 38 | type: u4 39 | - id: offset 40 | type: u4 41 | - id: timestamp 42 | type: u8 43 | - id: zero2 44 | contents: [00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00] 45 | - id: dummy 46 | type: u4 47 | - id: compression_type 48 | type: u4 49 | enum: compression_type 50 | instances: 51 | data: 52 | pos: offset 53 | size: compressed_size 54 | 55 | enums: 56 | compression_type: 57 | 0: none 58 | 1: zlib 59 | 2: snappy 60 | 3: doboz 61 | 4: lz4 # 4 and 5 62 | 5: lz4 -------------------------------------------------------------------------------- /src/ww2ogg/ww2ogg.cpp: -------------------------------------------------------------------------------- 1 | #define __STDC_CONSTANT_MACROS 2 | #include 3 | #include 4 | 5 | #include "ww2ogg/errors.h" 6 | #include "ww2ogg/packed_codebooks.h" 7 | #include "ww2ogg/wwriff.h" 8 | 9 | namespace ww2ogg { 10 | bool ww2ogg(const std::string &indata, std::ostream &outdata, 11 | unsigned char codebooks_data[], bool inline_codebooks, 12 | bool full_setup, ForcePacketFormat force_packet_format) { 13 | try { 14 | std::string codebooks_data_s = std::string( 15 | reinterpret_cast(codebooks_data), packed_codebooks_bin_len); 16 | Wwise_RIFF_Vorbis ww(indata, codebooks_data_s, inline_codebooks, full_setup, 17 | force_packet_format); 18 | 19 | ww.generate_ogg(outdata); 20 | } catch (const file_open_error &fe) { 21 | return false; 22 | } catch (const parse_error &pe) { 23 | return false; 24 | } 25 | return true; 26 | } 27 | 28 | std::string wem_info(const std::string &indata, unsigned char codebooks_data[], 29 | bool inline_codebooks, bool full_setup, 30 | ForcePacketFormat force_packet_format) { 31 | 32 | std::string codebooks_data_s = std::string( 33 | reinterpret_cast(codebooks_data), packed_codebooks_bin_len); 34 | Wwise_RIFF_Vorbis ww(indata, codebooks_data_s, inline_codebooks, full_setup, 35 | force_packet_format); 36 | return ww.get_info(); 37 | } 38 | } // namespace ww2ogg 39 | -------------------------------------------------------------------------------- /LICENSE-ww2ogg: -------------------------------------------------------------------------------- 1 | Copyright (c) 2002, Xiph.org Foundation 2 | Copyright (c) 2009-2016, Adam Gashlin 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions 6 | are met: 7 | 8 | - Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | - Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | - Neither the name of the Xiph.org Foundation nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION 23 | OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /ksy/w3sc.ksy: -------------------------------------------------------------------------------- 1 | meta: 2 | id: w3sc 3 | file-extension: cache 4 | endian: le 5 | seq: 6 | - id: magic 7 | contents: 'CS3W' 8 | - id: version 9 | type: u4 10 | - id: dummy 11 | type: u8 12 | # The size of this changes based on the version declared previously 13 | # See the witcher3.bms QuickBMS script for more info 14 | - id: info_offset 15 | type: 16 | switch-on: version 17 | cases: 18 | 1: u4 19 | _: u8 20 | - id: files 21 | type: 22 | switch-on: version 23 | cases: 24 | 1: u4 25 | _: u8 26 | - id: names_offset 27 | type: 28 | switch-on: version 29 | cases: 30 | 1: u4 31 | _: u8 32 | - id: names_size 33 | type: 34 | switch-on: version 35 | cases: 36 | 1: u4 37 | _: u8 38 | instances: 39 | file_infos: 40 | pos: info_offset 41 | type: file_info 42 | repeat: expr 43 | repeat-expr: files 44 | types: 45 | file_info: 46 | seq: 47 | - id: name_offset 48 | type: 49 | switch-on: _parent.version 50 | cases: 51 | 1: u4 52 | _: u8 53 | - id: offset 54 | type: 55 | switch-on: _parent.version 56 | cases: 57 | 1: u4 58 | _: u8 59 | - id: size 60 | type: 61 | switch-on: _parent.version 62 | cases: 63 | 1: u4 64 | _: u8 65 | instances: 66 | data: 67 | pos: offset 68 | size: size 69 | name: 70 | pos: _parent.names_offset + name_offset 71 | type: strz 72 | encoding: UTF-8 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wwise Audio Tools 2 | This repository is for a static and dynamic library as well as a simple command line tool used to convert Wwise WEM files to OGG files instead. 3 | 4 | ## About 5 | Both `ww2ogg` and `revorb` are relatively old and annoying to use repetitively so this project is aimed to keep supporting them and making them easy to use and integrate. 6 | 7 | ## Building 8 | This is meant to be built with CMake, which generates both a static and dynamic library, as well as a simple POC command line tool. 9 | 10 | Example build workflow: 11 | ``` 12 | git clone https://github.com/WolvenKit/wwise-audio-tools 13 | cmake -B build 14 | cmake --build build 15 | ``` 16 | > Note: This project *does* require `libogg` and `libvorbis` to build, which can be downloaded [here](https://xiph.org/downloads/) or installed from Linux package managers under names something like `libogg-dev` and `libvorbis-dev` or `libogg-devel` and `libvorbis-devel`. 17 | 18 | This will create the command-line tool in the `bin/` directory and the libraries in `lib/`. 19 | 20 | ## Usage 21 | The command-line tool can be run with `./wwise-audio-converter [NAME].wem` which will generate an easily usable `[NAME].ogg` in the same directory. No need to use `revorb`, no need to have a `packed_codebooks.bin`. The library usage will have further documentation soon. 22 | 23 | ## Credits 24 | Credit for the `ww2ogg` code goes to [`@hcs64`](https://github.com/hcs64), and to [`Jiri Hruska`](https://hydrogenaud.io/index.php/topic,64328.0.html) for the creation of the original `revorb`. 25 | 26 | ## Licensing 27 | Many files here are licensed individually and are taken from other projects, such as [ww2ogg](https://github.com/hcs64/ww2ogg) and [revorb](https://hydrogenaud.io/index.php/topic,64328.0.html). Other than the `ww2ogg` license, everything else is licensed under MIT. 28 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: "release" 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | linux: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | with: 16 | submodules: true 17 | 18 | - name: Configure CMake 19 | run: cmake -Bbuild 20 | 21 | - name: Build 22 | run: cmake --build build 23 | 24 | - uses: actions/upload-artifact@v2 25 | with: 26 | name: Linux Release 27 | path: | 28 | build/bin/wwtools 29 | build/lib/libwwtools.so 30 | build/lib/libwwtools.a 31 | 32 | windows: 33 | runs-on: windows-latest 34 | 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v2 38 | with: 39 | submodules: true 40 | 41 | - name: Configure CMake 42 | run: cmake -G "Visual Studio 17 2022" -B build 43 | 44 | - name: Build 45 | run: cmake --build build --config Release 46 | 47 | - uses: actions/upload-artifact@v2 48 | with: 49 | name: Windows Release 50 | # For some reason the dynamic lib is in the bin directory 51 | path: | 52 | build/bin/Release/wwtools.exe 53 | build/bin/Release/wwtools.dll 54 | build/lib/Release/wwtools.lib 55 | 56 | release: 57 | runs-on: ubuntu-latest 58 | needs: [linux, windows] 59 | 60 | steps: 61 | - name: Download artifacts 62 | uses: actions/download-artifact@v2 63 | - name: List stuff 64 | run: ls -R 65 | - name: Release 66 | uses: ncipollo/release-action@v1 67 | with: 68 | draft: true 69 | generateReleaseNotes: true 70 | artifacts: "*Release/**/*" 71 | token: ${{ secrets.GITHUB_TOKEN }} 72 | 73 | 74 | -------------------------------------------------------------------------------- /include/ww2ogg/errors.h: -------------------------------------------------------------------------------- 1 | #ifndef WW2OGG_ERRORS_H 2 | #define WW2OGG_ERRORS_H 3 | 4 | #include 5 | #include 6 | 7 | namespace ww2ogg { 8 | class Argument_error { 9 | std::string errmsg; 10 | 11 | public: 12 | friend std::ostream &operator<<(std::ostream &os, const Argument_error &ae) { 13 | os << "Argument error: " << ae.errmsg; 14 | return os; 15 | } 16 | 17 | explicit Argument_error(const char *str) : errmsg(str) {} 18 | }; 19 | 20 | class file_open_error { 21 | std::string filename; 22 | 23 | public: 24 | friend std::ostream &operator<<(std::ostream &os, const file_open_error &fe) { 25 | os << "Error opening " << fe.filename; 26 | return os; 27 | } 28 | 29 | explicit file_open_error(const std::string &name) : filename(name) {} 30 | }; 31 | 32 | class parse_error { 33 | public: 34 | virtual void print_self(std::ostream &os) const { os << "unspecified."; } 35 | 36 | friend std::ostream &operator<<(std::ostream &os, const parse_error &pe) { 37 | os << "Parse error: "; 38 | pe.print_self(os); 39 | return os; 40 | } 41 | virtual ~parse_error() {} 42 | }; 43 | 44 | class parse_error_str : public parse_error { 45 | std::string str; 46 | 47 | public: 48 | virtual void print_self(std::ostream &os) const override { os << str; } 49 | 50 | explicit parse_error_str(std::string s) : str(s) {} 51 | }; 52 | 53 | class size_mismatch : public parse_error { 54 | const unsigned long real_size, read_size; 55 | 56 | public: 57 | virtual void print_self(std::ostream &os) const override { 58 | os << "expected " << real_size << " bits, read " << read_size; 59 | } 60 | 61 | size_mismatch(unsigned long real_s, unsigned long read_s) 62 | : real_size(real_s), read_size(read_s) {} 63 | }; 64 | 65 | class invalid_id : public parse_error { 66 | const int id; 67 | 68 | public: 69 | virtual void print_self(std::ostream &os) const override { 70 | os << "invalid codebook id " << id << ", try --inline-codebooks"; 71 | } 72 | 73 | explicit invalid_id(int i) : id(i) {} 74 | }; 75 | } // namespace ww2ogg 76 | 77 | #endif // WW2OGG_ERRORS_H 78 | -------------------------------------------------------------------------------- /include/wwtools/bnk.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file bnk.hpp 3 | * @author Abheek Dhawan (abheekd at protonmail dot com) 4 | * @brief works with Wwise Soundbank files 5 | * @date 2022-05-26 6 | * 7 | * @copyright Copyright (c) 2022 RED Modding Tools 8 | * 9 | */ 10 | 11 | #ifndef WWTOOLS_BNK_HPP 12 | #define WWTOOLS_BNK_HPP 13 | 14 | #include 15 | #include 16 | 17 | #include "kaitai/kaitaistream.h" 18 | #include "kaitai/structs/bnk.h" 19 | 20 | /** 21 | * @namespace wwtools::bnk 22 | * @brief contains helper functions for Wwise Soundbank files 23 | * 24 | */ 25 | namespace wwtools::bnk { 26 | /** 27 | * @brief Extract BNK to array of WEMS 28 | * 29 | * @param indata std::string with the BNK content 30 | * @param outdata vector of std::string that has all the embedded WEM files 31 | */ 32 | void extract(const std::string &indata, std::vector &outdata); 33 | 34 | /** 35 | * @brief get the info string 36 | * 37 | * @param indata std::string with the file data 38 | * @return a printable info string 39 | */ 40 | std::string get_info(const std::string &indata); 41 | 42 | /** 43 | * @brief get WEMs correlating to a BNK and an optional event ID 44 | * 45 | * @param indata std::string with the file data 46 | * @param in_event_id the input event ID 47 | * @return a printable info string 48 | */ 49 | std::string get_event_id_info(const std::string &indata, 50 | const std::string &in_event_id); 51 | 52 | /** 53 | * @brief get the ID of a WEM at an index 54 | * 55 | * @param indata std::string with the file data 56 | * @param index the index to get the ID from 57 | * @return the ID as a string 58 | */ 59 | std::string get_wem_id_at_index(const std::string &indata, const int &index); 60 | 61 | std::string get_event_name_from_id(const std::uint32_t &event_id); 62 | 63 | /** 64 | * @brief get a string with the action type from the enum 65 | * @param action_type an action type to be converted to string 66 | * @return the string name of the action type 67 | */ 68 | std::string get_event_action_type(bnk_t::action_type_t action_type); 69 | } // namespace wwtools::bnk 70 | 71 | #endif // WWTOOLS_BNK_HPP 72 | -------------------------------------------------------------------------------- /include/ww2ogg/wwriff.h: -------------------------------------------------------------------------------- 1 | #ifndef WW2OGG_WWRIFF_H 2 | #define WW2OGG_WWRIFF_H 3 | 4 | #ifndef __STDC_CONSTANT_MACROS 5 | #define __STDC_CONSTANT_MACROS 6 | #endif 7 | #include "bitstream.h" 8 | #include "cstdint" 9 | #include "errors.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define VERSION "0.24" 16 | 17 | namespace ww2ogg { 18 | enum ForcePacketFormat { 19 | kNoForcePacketFormat, 20 | kForceModPackets, 21 | kForceNoModPackets 22 | }; 23 | 24 | class Wwise_RIFF_Vorbis { 25 | std::string _codebooks_data; 26 | std::stringstream _indata; 27 | long _file_size; 28 | 29 | bool _little_endian; 30 | 31 | long _riff_size; 32 | long _fmt_offset, _cue_offset, _LIST_offset, _smpl_offset, _vorb_offset, 33 | _data_offset; 34 | long _fmt_size, _cue_size, _LIST_size, _smpl_size, _vorb_size, _data_size; 35 | 36 | // RIFF fmt 37 | uint16_t _channels; 38 | uint32_t _sample_rate; 39 | uint32_t _avg_bytes_per_second; 40 | 41 | // RIFF extended fmt 42 | uint16_t _ext_unk; 43 | uint32_t _subtype; 44 | 45 | // cue info 46 | uint32_t _cue_count; 47 | 48 | // smpl info 49 | uint32_t _loop_count, _loop_start, _loop_end; 50 | 51 | // vorbis info 52 | uint32_t _sample_count; 53 | uint32_t _setup_packet_offset; 54 | uint32_t _first_audio_packet_offset; 55 | uint32_t _uid; 56 | uint8_t _blocksize_0_pow; 57 | uint8_t _blocksize_1_pow; 58 | 59 | const bool _inline_codebooks, _full_setup; 60 | bool _header_triad_present, _old_packet_headers; 61 | bool _no_granule, _mod_packets; 62 | 63 | uint16_t (*_read_16)(std::istream &is); 64 | uint32_t (*_read_32)(std::istream &is); 65 | 66 | public: 67 | Wwise_RIFF_Vorbis(const std::string &indata, 68 | const std::string &_codebooks_data, bool inline_codebooks, 69 | bool full_setup, ForcePacketFormat force_packet_format); 70 | 71 | std::string get_info(void); 72 | 73 | void generate_ogg(std::ostream &os); 74 | void generate_ogg_header(bitoggstream &os, bool *&mode_blockflag, 75 | int &mode_bits); 76 | void generate_ogg_header_with_triad(bitoggstream &os); 77 | }; 78 | } // namespace ww2ogg 79 | 80 | #endif // WW2OGG_WWRIFF_H 81 | -------------------------------------------------------------------------------- /include/wwtools/util/write.hpp: -------------------------------------------------------------------------------- 1 | #ifndef WWTOOLS_UTIL_WRITE_HPP 2 | #define WWTOOLS_UTIL_WRITE_HPP 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * @namespace wwtools::util::write 9 | * @brief helper functions for writing data 10 | */ 11 | namespace wwtools::util::write { 12 | /** 13 | * @brief write data in reverse 14 | * 15 | * @tparam T type to detect size of data 16 | * @param data data to be written 17 | * @param os ostream to write data 18 | */ 19 | template 20 | inline void big_endian(const char *data, std::ostream &os) { 21 | char arr_data[sizeof(T)]; 22 | std::memcpy(arr_data, data, sizeof(T)); 23 | for (int i = sizeof(T) - 1; i >= 0; i--) 24 | os.put(arr_data[i]); 25 | } 26 | 27 | /** 28 | * @brief write data of a certain type in reverse order 29 | * 30 | * @tparam T type for data and size 31 | * @param data data to be written 32 | * @param os location for data to be written 33 | */ 34 | template inline void big_endian(const T &data, std::ostream &os) { 35 | big_endian(reinterpret_cast(&data), os); 36 | } 37 | 38 | /** 39 | * @brief write data to a stream 40 | * 41 | * @tparam T type do determine size of data 42 | * @param data data to be written 43 | * @param os location for data to be written 44 | */ 45 | template 46 | inline void little_endian(const char *data, std::ostream &os) { 47 | char arr_data[sizeof(T)]; 48 | std::memcpy(arr_data, data, sizeof(T)); 49 | for (int i = 0; i < sizeof(T); i++) 50 | os.put(arr_data[i]); 51 | } 52 | 53 | /** 54 | * @brief write data of a certain type to a stream 55 | * 56 | * @tparam T type for data and size 57 | * @param data data to be written 58 | * @param os location for data to be written 59 | */ 60 | template 61 | inline void little_endian(const T &data, std::ostream &os) { 62 | little_endian(reinterpret_cast(&data), os); 63 | } 64 | 65 | /** 66 | * @brief wrapper around os::write 67 | * 68 | * @param data data to be written 69 | * @param size the size of the data 70 | * @param os output stream to write to 71 | */ 72 | inline void raw_data(const char *data, size_t size, std::ostream &os) { 73 | os.write(data, size); 74 | } 75 | } // namespace wwtools::util::write 76 | 77 | #endif // WWTOOLS_UTIL_WRITE_HPP 78 | -------------------------------------------------------------------------------- /include/ww2ogg/codebook.h: -------------------------------------------------------------------------------- 1 | #ifndef WW2OGG_CODEBOOK_H 2 | #define WW2OGG_CODEBOOK_H 3 | 4 | #include "bitstream.h" 5 | #include "errors.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace std; 13 | 14 | /* stuff from Tremor (lowmem) */ 15 | namespace { 16 | int ilog(unsigned int v) { 17 | int ret = 0; 18 | while (v) { 19 | ret++; 20 | v >>= 1; 21 | } 22 | return (ret); 23 | } 24 | 25 | unsigned int _book_maptype1_quantvals(unsigned int entries, 26 | unsigned int dimensions) { 27 | /* get us a starting hint, we'll polish it below */ 28 | int bits = ilog(entries); 29 | int vals = entries >> ((bits - 1) * (dimensions - 1) / dimensions); 30 | 31 | while (1) { 32 | unsigned long acc = 1; 33 | unsigned long acc1 = 1; 34 | unsigned int i; 35 | for (i = 0; i < dimensions; i++) { 36 | acc *= vals; 37 | acc1 *= vals + 1; 38 | } 39 | if (acc <= entries && acc1 > entries) { 40 | return (vals); 41 | } else { 42 | if (acc > entries) { 43 | vals--; 44 | } else { 45 | vals++; 46 | } 47 | } 48 | } 49 | } 50 | 51 | } // namespace 52 | 53 | namespace ww2ogg { 54 | class codebook_library { 55 | char *codebook_data; 56 | long *codebook_offsets; 57 | long codebook_count; 58 | 59 | // Intentionally undefined 60 | codebook_library &operator=(const codebook_library &rhs); 61 | codebook_library(const codebook_library &rhs); 62 | 63 | public: 64 | codebook_library(std::string indata); 65 | codebook_library(void); 66 | 67 | ~codebook_library() { 68 | delete[] codebook_data; 69 | delete[] codebook_offsets; 70 | } 71 | 72 | const char *get_codebook(int i) const { 73 | if (!codebook_data || !codebook_offsets) { 74 | throw parse_error_str("codebook library not loaded"); 75 | } 76 | if (i >= codebook_count - 1 || i < 0) 77 | return NULL; 78 | return &codebook_data[codebook_offsets[i]]; 79 | } 80 | 81 | long get_codebook_size(int i) const { 82 | if (!codebook_data || !codebook_offsets) { 83 | throw parse_error_str("codebook library not loaded"); 84 | } 85 | if (i >= codebook_count - 1 || i < 0) 86 | return -1; 87 | return codebook_offsets[i + 1] - codebook_offsets[i]; 88 | } 89 | 90 | void rebuild(int i, bitoggstream &bos); 91 | 92 | void rebuild(bitstream &bis, unsigned long cb_size, bitoggstream &bos); 93 | 94 | void copy(bitstream &bis, bitoggstream &bos); 95 | }; 96 | } // namespace ww2ogg 97 | 98 | #endif // WW2OGG_CODEBOOH_H 99 | -------------------------------------------------------------------------------- /ksy/vlq.ksy: -------------------------------------------------------------------------------- 1 | meta: 2 | id: vlq_base128_le 3 | title: Variable length quantity, unsigned/signed integer, base128, little-endian 4 | license: CC0-1.0 5 | ks-version: 0.7 6 | doc: | 7 | A variable-length unsigned/signed integer using base128 encoding. 1-byte groups 8 | consist of 1-bit flag of continuation and 7-bit value chunk, and are ordered 9 | "least significant group first", i.e. in "little-endian" manner. 10 | 11 | This particular encoding is specified and used in: 12 | 13 | * DWARF debug file format, where it's dubbed "unsigned LEB128" or "ULEB128". 14 | http://dwarfstd.org/doc/dwarf-2.0.0.pdf - page 139 15 | * Google Protocol Buffers, where it's called "Base 128 Varints". 16 | https://developers.google.com/protocol-buffers/docs/encoding?csw=1#varints 17 | * Apache Lucene, where it's called "VInt" 18 | https://lucene.apache.org/core/3_5_0/fileformats.html#VInt 19 | * Apache Avro uses this as a basis for integer encoding, adding ZigZag on 20 | top of it for signed ints 21 | https://avro.apache.org/docs/current/spec.html#binary_encode_primitive 22 | 23 | More information on this encoding is available at https://en.wikipedia.org/wiki/LEB128 24 | 25 | This particular implementation supports serialized values to up 8 bytes long. 26 | -webide-representation: '{value:dec}' 27 | seq: 28 | - id: groups 29 | type: group 30 | repeat: until 31 | repeat-until: not _.has_next 32 | types: 33 | group: 34 | -webide-representation: '{value}' 35 | doc: | 36 | One byte group, clearly divided into 7-bit "value" chunk and 1-bit "continuation" flag. 37 | seq: 38 | - id: b 39 | type: u1 40 | instances: 41 | has_next: 42 | value: (b & 0b1000_0000) != 0 43 | doc: If true, then we have more bytes to read 44 | value: 45 | value: b & 0b0111_1111 46 | doc: The 7-bit (base128) numeric value chunk of this group 47 | instances: 48 | len: 49 | value: groups.size 50 | value: 51 | value: >- 52 | groups[0].value 53 | + (len >= 2 ? (groups[1].value << 7) : 0) 54 | + (len >= 3 ? (groups[2].value << 14) : 0) 55 | + (len >= 4 ? (groups[3].value << 21) : 0) 56 | + (len >= 5 ? (groups[4].value << 28) : 0) 57 | + (len >= 6 ? (groups[5].value << 35) : 0) 58 | + (len >= 7 ? (groups[6].value << 42) : 0) 59 | + (len >= 8 ? (groups[7].value << 49) : 0) 60 | doc: Resulting unsigned value as normal integer 61 | sign_bit: 62 | value: '1 << (7 * len - 1)' 63 | value_signed: 64 | value: '(value ^ sign_bit) - sign_bit' 65 | doc-ref: https://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend 66 | -------------------------------------------------------------------------------- /include/kaitai/structs/w3sc.h: -------------------------------------------------------------------------------- 1 | #ifndef W3SC_H_ 2 | #define W3SC_H_ 3 | 4 | // This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild 5 | 6 | #include "kaitai/kaitaistruct.h" 7 | #include 8 | #include 9 | 10 | #if KAITAI_STRUCT_VERSION < 11000L 11 | #error "Incompatible Kaitai Struct C++/STL API: version 0.11 or later is required" 12 | #endif 13 | 14 | class w3sc_t : public kaitai::kstruct { 15 | 16 | public: 17 | class file_info_t; 18 | 19 | w3sc_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, w3sc_t* p__root = 0); 20 | 21 | private: 22 | void _read(); 23 | void _clean_up(); 24 | 25 | public: 26 | ~w3sc_t(); 27 | 28 | class file_info_t : public kaitai::kstruct { 29 | 30 | public: 31 | 32 | file_info_t(kaitai::kstream* p__io, w3sc_t* p__parent = 0, w3sc_t* p__root = 0); 33 | 34 | private: 35 | void _read(); 36 | void _clean_up(); 37 | 38 | public: 39 | ~file_info_t(); 40 | 41 | private: 42 | bool f_data; 43 | std::string m_data; 44 | 45 | public: 46 | std::string data(); 47 | 48 | private: 49 | bool f_name; 50 | std::string m_name; 51 | 52 | public: 53 | std::string name(); 54 | 55 | private: 56 | uint64_t m_name_offset; 57 | uint64_t m_offset; 58 | uint64_t m_size; 59 | w3sc_t* m__root; 60 | w3sc_t* m__parent; 61 | 62 | public: 63 | uint64_t name_offset() const { return m_name_offset; } 64 | uint64_t offset() const { return m_offset; } 65 | uint64_t size() const { return m_size; } 66 | w3sc_t* _root() const { return m__root; } 67 | w3sc_t* _parent() const { return m__parent; } 68 | }; 69 | 70 | private: 71 | bool f_file_infos; 72 | std::vector* m_file_infos; 73 | 74 | public: 75 | std::vector* file_infos(); 76 | 77 | private: 78 | std::string m_magic; 79 | uint32_t m_version; 80 | uint64_t m_dummy; 81 | uint64_t m_info_offset; 82 | uint64_t m_files; 83 | uint64_t m_names_offset; 84 | uint64_t m_names_size; 85 | w3sc_t* m__root; 86 | kaitai::kstruct* m__parent; 87 | 88 | public: 89 | std::string magic() const { return m_magic; } 90 | uint32_t version() const { return m_version; } 91 | uint64_t dummy() const { return m_dummy; } 92 | uint64_t info_offset() const { return m_info_offset; } 93 | uint64_t files() const { return m_files; } 94 | uint64_t names_offset() const { return m_names_offset; } 95 | uint64_t names_size() const { return m_names_size; } 96 | w3sc_t* _root() const { return m__root; } 97 | kaitai::kstruct* _parent() const { return m__parent; } 98 | }; 99 | 100 | #endif // W3SC_H_ 101 | -------------------------------------------------------------------------------- /src/kaitai/structs/vlq.cpp: -------------------------------------------------------------------------------- 1 | // This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild 2 | 3 | #include "kaitai/structs/vlq.h" 4 | 5 | vlq_t::vlq_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, vlq_t* p__root) : kaitai::kstruct(p__io) { 6 | m__parent = p__parent; 7 | m__root = this; 8 | m_groups = 0; 9 | f_len = false; 10 | f_value = false; 11 | f_sign_bit = false; 12 | f_value_signed = false; 13 | 14 | try { 15 | _read(); 16 | } catch(...) { 17 | _clean_up(); 18 | throw; 19 | } 20 | } 21 | 22 | void vlq_t::_read() { 23 | m_groups = new std::vector(); 24 | { 25 | int i = 0; 26 | group_t* _; 27 | do { 28 | _ = new group_t(m__io, this, m__root); 29 | m_groups->push_back(_); 30 | i++; 31 | } while (!(!(_->has_next()))); 32 | } 33 | } 34 | 35 | vlq_t::~vlq_t() { 36 | _clean_up(); 37 | } 38 | 39 | void vlq_t::_clean_up() { 40 | if (m_groups) { 41 | for (std::vector::iterator it = m_groups->begin(); it != m_groups->end(); ++it) { 42 | delete *it; 43 | } 44 | delete m_groups; m_groups = 0; 45 | } 46 | } 47 | 48 | vlq_t::group_t::group_t(kaitai::kstream* p__io, vlq_t* p__parent, vlq_t* p__root) : kaitai::kstruct(p__io) { 49 | m__parent = p__parent; 50 | m__root = p__root; 51 | f_has_next = false; 52 | f_value = false; 53 | 54 | try { 55 | _read(); 56 | } catch(...) { 57 | _clean_up(); 58 | throw; 59 | } 60 | } 61 | 62 | void vlq_t::group_t::_read() { 63 | m_b = m__io->read_u1(); 64 | } 65 | 66 | vlq_t::group_t::~group_t() { 67 | _clean_up(); 68 | } 69 | 70 | void vlq_t::group_t::_clean_up() { 71 | } 72 | 73 | bool vlq_t::group_t::has_next() { 74 | if (f_has_next) 75 | return m_has_next; 76 | m_has_next = (b() & 128) != 0; 77 | f_has_next = true; 78 | return m_has_next; 79 | } 80 | 81 | int32_t vlq_t::group_t::value() { 82 | if (f_value) 83 | return m_value; 84 | m_value = (b() & 127); 85 | f_value = true; 86 | return m_value; 87 | } 88 | 89 | int32_t vlq_t::len() { 90 | if (f_len) 91 | return m_len; 92 | m_len = groups()->size(); 93 | f_len = true; 94 | return m_len; 95 | } 96 | 97 | int32_t vlq_t::value() { 98 | if (f_value) 99 | return m_value; 100 | m_value = (((((((groups()->at(0)->value() + ((len() >= 2) ? ((groups()->at(1)->value() << 7)) : (0))) + ((len() >= 3) ? ((groups()->at(2)->value() << 14)) : (0))) + ((len() >= 4) ? ((groups()->at(3)->value() << 21)) : (0))) + ((len() >= 5) ? ((groups()->at(4)->value() << 28)) : (0))) + ((len() >= 6) ? ((groups()->at(5)->value() << 35)) : (0))) + ((len() >= 7) ? ((groups()->at(6)->value() << 42)) : (0))) + ((len() >= 8) ? ((groups()->at(7)->value() << 49)) : (0))); 101 | f_value = true; 102 | return m_value; 103 | } 104 | 105 | int32_t vlq_t::sign_bit() { 106 | if (f_sign_bit) 107 | return m_sign_bit; 108 | m_sign_bit = (1 << ((7 * len()) - 1)); 109 | f_sign_bit = true; 110 | return m_sign_bit; 111 | } 112 | 113 | int32_t vlq_t::value_signed() { 114 | if (f_value_signed) 115 | return m_value_signed; 116 | m_value_signed = ((value() ^ sign_bit()) - sign_bit()); 117 | f_value_signed = true; 118 | return m_value_signed; 119 | } 120 | -------------------------------------------------------------------------------- /ksy/wem.ksy: -------------------------------------------------------------------------------- 1 | meta: 2 | id: wem 3 | file-extension: wem 4 | endian: le 5 | 6 | seq: 7 | - id: riff_header 8 | type: riff_header 9 | - id: chunks 10 | type: chunk 11 | repeat: eos 12 | 13 | types: 14 | riff_header: 15 | seq: 16 | - id: magic 17 | contents: 'RIFF' 18 | - id: chunk_size 19 | type: u4 20 | - id: wav_id 21 | type: u4 22 | riff_chunk: 23 | seq: 24 | - id: dummy 25 | size: _io.pos % 2 # add dummy byte if chunk doesn't start on even 26 | - id: type 27 | # No longer using a string because the type can have some non-standard chars 28 | #type: str 29 | size: 4 30 | #encoding: UTF-8 31 | - id: size 32 | type: u4 33 | fmt_chunk: 34 | seq: 35 | - id: compression_code 36 | type: u2 37 | - id: channels 38 | type: u2 39 | - id: samples_per_sec 40 | type: u4 41 | - id: avg_bytes_per_sec 42 | type: u4 43 | - id: block_align 44 | type: u2 45 | - id: bits_per_sample 46 | type: u2 47 | - id: extra_byte_count 48 | type: u2 49 | - id: valid_bits_per_sample 50 | type: u2 51 | - id: channel_mask 52 | type: u4 53 | - id: guid 54 | type: str 55 | size: 42 56 | encoding: utf-8 57 | cue_chunk: 58 | seq: 59 | - id: cue_point_count 60 | type: u4 61 | - id: cue_points 62 | type: cue_point_subchunk 63 | repeat: expr 64 | repeat-expr: cue_point_count 65 | cue_point_subchunk: 66 | seq: 67 | - id: id 68 | type: u4 69 | - id: position 70 | type: u4 71 | - id: data_chunk_id 72 | type: u4 73 | - id: chunk_start 74 | type: u4 75 | - id: block_start 76 | type: u4 77 | - id: sample_start 78 | type: u4 79 | list_chunk: 80 | params: 81 | - id: size 82 | type: u4 83 | seq: 84 | - id: adtl 85 | contents: 'adtl' 86 | - id: labl 87 | type: list_labl_subchunk 88 | #- id: data 89 | # size: size - 4 90 | list_labl_subchunk: 91 | seq: 92 | - id: magic 93 | contents: 'labl' 94 | - id: size 95 | type: u4 96 | - id: cue_point_id 97 | type: u4 98 | - id: data 99 | type: str 100 | size: size - 4 # size without cue point ID 101 | encoding: utf-8 102 | junk_chunk: 103 | seq: 104 | - id: junk 105 | type: str 106 | size: 26 107 | encoding: utf-8 108 | data_chunk: 109 | params: 110 | - id: size 111 | type: u4 112 | seq: 113 | - id: data 114 | type: str 115 | size: size 116 | encoding: utf-8 117 | random_blank_bytes: 118 | seq: 119 | - id: dummy 120 | type: u1 121 | repeat: until 122 | repeat-until: _ != 0 or _io.eof 123 | chunk: 124 | seq: 125 | - id: riff_chunk 126 | type: riff_chunk 127 | - id: data 128 | type: 129 | switch-on: riff_chunk.type 130 | cases: 131 | '[0x66, 0x6d, 0x74, 0x20]': fmt_chunk 132 | '[0x4a, 0x55, 0x4e, 0x4b]': junk_chunk 133 | '[0x63, 0x75, 0x65, 0x20]': cue_chunk 134 | '[0x4c, 0x49, 0x53, 0x54]': list_chunk(riff_chunk.size) 135 | '[0x64, 0x61, 0x74, 0x61]': data_chunk(riff_chunk.size) 136 | '[0x00, 0x00, 0x00, 0x00]': random_blank_bytes 137 | 138 | -------------------------------------------------------------------------------- /src/ww2ogg/crc.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ww2ogg/crc.h" 4 | 5 | /* from Tremor (lowmem) */ 6 | static uint32_t crc_lookup[256] = { 7 | 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, 8 | 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, 9 | 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, 10 | 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, 11 | 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, 12 | 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, 13 | 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, 14 | 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, 15 | 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, 16 | 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, 17 | 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, 18 | 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, 19 | 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, 20 | 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, 21 | 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, 22 | 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, 23 | 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, 24 | 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, 25 | 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, 26 | 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, 27 | 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, 28 | 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, 29 | 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, 30 | 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, 31 | 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, 32 | 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, 33 | 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, 34 | 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, 35 | 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, 36 | 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, 37 | 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, 38 | 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, 39 | 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, 40 | 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, 41 | 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, 42 | 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, 43 | 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, 44 | 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, 45 | 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, 46 | 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, 47 | 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, 48 | 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, 49 | 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4}; 50 | 51 | uint32_t checksum(unsigned char *data, int bytes) { 52 | uint32_t crc_reg = 0; 53 | int i; 54 | 55 | for (i = 0; i < bytes; ++i) 56 | crc_reg = (crc_reg << 8) ^ crc_lookup[((crc_reg >> 24) & 0xff) ^ data[i]]; 57 | 58 | return crc_reg; 59 | } 60 | -------------------------------------------------------------------------------- /include/kaitai/structs/vlq.h: -------------------------------------------------------------------------------- 1 | #ifndef VLQ_H_ 2 | #define VLQ_H_ 3 | 4 | // This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild 5 | 6 | #include "kaitai/kaitaistruct.h" 7 | #include 8 | #include 9 | 10 | #if KAITAI_STRUCT_VERSION < 11000L 11 | #error "Incompatible Kaitai Struct C++/STL API: version 0.11 or later is required" 12 | #endif 13 | 14 | /** 15 | * A variable-length unsigned/signed integer using base128 encoding. 1-byte groups 16 | * consist of 1-bit flag of continuation and 7-bit value chunk, and are ordered 17 | * "least significant group first", i.e. in "little-endian" manner. 18 | * 19 | * This particular encoding is specified and used in: 20 | * 21 | * * DWARF debug file format, where it's dubbed "unsigned LEB128" or "ULEB128". 22 | * http://dwarfstd.org/doc/dwarf-2.0.0.pdf - page 139 23 | * * Google Protocol Buffers, where it's called "Base 128 Varints". 24 | * https://developers.google.com/protocol-buffers/docs/encoding?csw=1#varints 25 | * * Apache Lucene, where it's called "VInt" 26 | * https://lucene.apache.org/core/3_5_0/fileformats.html#VInt 27 | * * Apache Avro uses this as a basis for integer encoding, adding ZigZag on 28 | * top of it for signed ints 29 | * https://avro.apache.org/docs/current/spec.html#binary_encode_primitive 30 | * 31 | * More information on this encoding is available at https://en.wikipedia.org/wiki/LEB128 32 | * 33 | * This particular implementation supports serialized values to up 8 bytes long. 34 | */ 35 | 36 | class vlq_t : public kaitai::kstruct { 37 | 38 | public: 39 | class group_t; 40 | 41 | vlq_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, vlq_t* p__root = 0); 42 | 43 | private: 44 | void _read(); 45 | void _clean_up(); 46 | 47 | public: 48 | ~vlq_t(); 49 | 50 | /** 51 | * One byte group, clearly divided into 7-bit "value" chunk and 1-bit "continuation" flag. 52 | */ 53 | 54 | class group_t : public kaitai::kstruct { 55 | 56 | public: 57 | 58 | group_t(kaitai::kstream* p__io, vlq_t* p__parent = 0, vlq_t* p__root = 0); 59 | 60 | private: 61 | void _read(); 62 | void _clean_up(); 63 | 64 | public: 65 | ~group_t(); 66 | 67 | private: 68 | bool f_has_next; 69 | bool m_has_next; 70 | 71 | public: 72 | 73 | /** 74 | * If true, then we have more bytes to read 75 | */ 76 | bool has_next(); 77 | 78 | private: 79 | bool f_value; 80 | int32_t m_value; 81 | 82 | public: 83 | 84 | /** 85 | * The 7-bit (base128) numeric value chunk of this group 86 | */ 87 | int32_t value(); 88 | 89 | private: 90 | uint8_t m_b; 91 | vlq_t* m__root; 92 | vlq_t* m__parent; 93 | 94 | public: 95 | uint8_t b() const { return m_b; } 96 | vlq_t* _root() const { return m__root; } 97 | vlq_t* _parent() const { return m__parent; } 98 | }; 99 | 100 | private: 101 | bool f_len; 102 | int32_t m_len; 103 | 104 | public: 105 | int32_t len(); 106 | 107 | private: 108 | bool f_value; 109 | int32_t m_value; 110 | 111 | public: 112 | 113 | /** 114 | * Resulting unsigned value as normal integer 115 | */ 116 | int32_t value(); 117 | 118 | private: 119 | bool f_sign_bit; 120 | int32_t m_sign_bit; 121 | 122 | public: 123 | int32_t sign_bit(); 124 | 125 | private: 126 | bool f_value_signed; 127 | int32_t m_value_signed; 128 | 129 | public: 130 | 131 | /** 132 | * \sa https://graphics.stanford.edu/~seander/bithacks.html#VariableSignExtend Source 133 | */ 134 | int32_t value_signed(); 135 | 136 | private: 137 | std::vector* m_groups; 138 | vlq_t* m__root; 139 | kaitai::kstruct* m__parent; 140 | 141 | public: 142 | std::vector* groups() const { return m_groups; } 143 | vlq_t* _root() const { return m__root; } 144 | kaitai::kstruct* _parent() const { return m__parent; } 145 | }; 146 | 147 | #endif // VLQ_H_ 148 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(wwise-audio-tools) 3 | 4 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") 5 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") 6 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") 7 | 8 | file(REMOVE_RECURSE "${CMAKE_BINARY_DIR}/lib") 9 | 10 | set(CMAKE_CXX_STANDARD 17) 11 | set(CMAKE_POSITION_INDEPENDENT_CODE ON) 12 | 13 | include_directories( 14 | include 15 | libogg/include 16 | libvorbis/include 17 | ${CMAKE_CURRENT_BINARY_DIR}/libogg/include 18 | ) 19 | 20 | add_definitions(-DKS_STR_ENCODING_NONE) 21 | 22 | set(SOURCE 23 | src/ww2ogg/codebook.cpp 24 | src/ww2ogg/crc.c 25 | #src/ww2ogg/packed_codebooks.cpp 26 | src/ww2ogg/ww2ogg.cpp 27 | src/ww2ogg/wwriff.cpp 28 | src/revorb/revorb.cpp 29 | src/wwtools/wwtools.cpp 30 | src/wwtools/w3sc.cpp 31 | src/wwtools/bnk.cpp 32 | src/kaitai/kaitaistream.cpp 33 | src/kaitai/structs/bnk.cpp 34 | src/kaitai/structs/vlq.cpp 35 | src/kaitai/structs/w3sc.cpp 36 | src/kaitai/structs/wem.cpp 37 | ) 38 | 39 | option(PACKED_CODEBOOKS_AOTUV "Use data from packed_codebooks_aoTuV_603.bin instead of regular packed_codebooks.bin" OFF) 40 | 41 | if(PACKED_CODEBOOKS_AOTUV) 42 | message("PACKED_CODEBOOKS_AOTUV set as on. Using data from packed_codebooks_aoTuV_603.bin.") 43 | list(APPEND SOURCE src/ww2ogg/packed_codebooks_aoTuV_603.cpp) 44 | else() 45 | message("PACKED_CODEBOOKS_AOTUV not set or set as off. Using data from packed_codebooks.bin.") 46 | list(APPEND SOURCE src/ww2ogg/packed_codebooks_aoTuV_603.cpp) 47 | endif() 48 | 49 | add_subdirectory(libogg EXCLUDE_FROM_ALL) 50 | add_subdirectory(libvorbis EXCLUDE_FROM_ALL) 51 | 52 | add_library(wwtools-shared-lib SHARED ${SOURCE}) 53 | add_library(wwtools-static-lib STATIC ${SOURCE}) 54 | add_executable(wwtools-bin ${SOURCE} src/main.cpp) 55 | 56 | target_link_libraries(wwtools-shared-lib ogg vorbis) 57 | target_link_libraries(wwtools-static-lib ogg vorbis) 58 | 59 | set_target_properties(wwtools-bin PROPERTIES OUTPUT_NAME wwtools) 60 | set_target_properties(wwtools-shared-lib PROPERTIES OUTPUT_NAME wwtools) 61 | set_target_properties(wwtools-static-lib PROPERTIES OUTPUT_NAME wwtools) 62 | 63 | # Standard filesystem library needs additional linking options on older compilers 64 | # https://en.cppreference.com/w/cpp/filesystem#Notes 65 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 66 | if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.1") 67 | target_link_libraries(wwtools-bin ogg vorbis stdc++fs) 68 | else() 69 | target_link_libraries(wwtools-bin ogg vorbis) 70 | endif() 71 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") 72 | if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0") 73 | target_link_libraries(wwtools-bin ogg vorbis c++fs) 74 | else() 75 | target_link_libraries(wwtools-bin ogg vorbis) 76 | endif() 77 | else() 78 | target_link_libraries(wwtools-bin ogg vorbis) 79 | endif() 80 | 81 | # To install to the default location, destination directive is unnecessary 82 | # https://cmake.org/cmake/help/latest/command/install.html#installing-targets 83 | install(TARGETS wwtools-bin) 84 | 85 | option(DOWNLOAD_CATCH2 "Download Catch2 framework if it won't be found locally" OFF) 86 | 87 | find_package(Catch2 3) 88 | 89 | if(NOT Catch2_FOUND AND DOWNLOAD_CATCH2) 90 | include(FetchContent) 91 | 92 | FetchContent_Declare( 93 | catch2 94 | GIT_REPOSITORY https://github.com/catchorg/Catch2 95 | GIT_TAG v3.0.0-preview5 96 | ) 97 | 98 | message("Catch2 v3 not found locally. Fetching it from the git repository...") 99 | FetchContent_MakeAvailable(catch2) 100 | set(FETCHCONTENT_UPDATES_DISCONNECTED_CATCH2 ON) # don't update 101 | 102 | set(Catch2_FOUND TRUE) 103 | endif() 104 | 105 | if(Catch2_FOUND) 106 | add_executable(tests ${SOURCE} tests/wem.cpp) 107 | target_link_libraries(tests PRIVATE Catch2::Catch2WithMain ogg vorbis) 108 | file(COPY ${CMAKE_CURRENT_SOURCE_DIR}/tests/testdata DESTINATION ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) 109 | else() 110 | message("Catch2 v3 was not found and not dowloaded. Testing target won't be available.") 111 | endif() 112 | -------------------------------------------------------------------------------- /src/kaitai/structs/w3sc.cpp: -------------------------------------------------------------------------------- 1 | // This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild 2 | 3 | #include "kaitai/structs/w3sc.h" 4 | #include "kaitai/exceptions.h" 5 | 6 | w3sc_t::w3sc_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, w3sc_t* p__root) : kaitai::kstruct(p__io) { 7 | m__parent = p__parent; 8 | m__root = this; 9 | m_file_infos = 0; 10 | f_file_infos = false; 11 | 12 | try { 13 | _read(); 14 | } catch(...) { 15 | _clean_up(); 16 | throw; 17 | } 18 | } 19 | 20 | void w3sc_t::_read() { 21 | m_magic = m__io->read_bytes(4); 22 | if (!(magic() == std::string("\x43\x53\x33\x57", 4))) { 23 | throw kaitai::validation_not_equal_error(std::string("\x43\x53\x33\x57", 4), magic(), _io(), std::string("/seq/0")); 24 | } 25 | m_version = m__io->read_u4le(); 26 | m_dummy = m__io->read_u8le(); 27 | switch (version()) { 28 | case 1: { 29 | m_info_offset = m__io->read_u4le(); 30 | break; 31 | } 32 | default: { 33 | m_info_offset = m__io->read_u8le(); 34 | break; 35 | } 36 | } 37 | switch (version()) { 38 | case 1: { 39 | m_files = m__io->read_u4le(); 40 | break; 41 | } 42 | default: { 43 | m_files = m__io->read_u8le(); 44 | break; 45 | } 46 | } 47 | switch (version()) { 48 | case 1: { 49 | m_names_offset = m__io->read_u4le(); 50 | break; 51 | } 52 | default: { 53 | m_names_offset = m__io->read_u8le(); 54 | break; 55 | } 56 | } 57 | switch (version()) { 58 | case 1: { 59 | m_names_size = m__io->read_u4le(); 60 | break; 61 | } 62 | default: { 63 | m_names_size = m__io->read_u8le(); 64 | break; 65 | } 66 | } 67 | } 68 | 69 | w3sc_t::~w3sc_t() { 70 | _clean_up(); 71 | } 72 | 73 | void w3sc_t::_clean_up() { 74 | if (f_file_infos) { 75 | if (m_file_infos) { 76 | for (std::vector::iterator it = m_file_infos->begin(); it != m_file_infos->end(); ++it) { 77 | delete *it; 78 | } 79 | delete m_file_infos; m_file_infos = 0; 80 | } 81 | } 82 | } 83 | 84 | w3sc_t::file_info_t::file_info_t(kaitai::kstream* p__io, w3sc_t* p__parent, w3sc_t* p__root) : kaitai::kstruct(p__io) { 85 | m__parent = p__parent; 86 | m__root = p__root; 87 | f_data = false; 88 | f_name = false; 89 | 90 | try { 91 | _read(); 92 | } catch(...) { 93 | _clean_up(); 94 | throw; 95 | } 96 | } 97 | 98 | void w3sc_t::file_info_t::_read() { 99 | switch (_parent()->version()) { 100 | case 1: { 101 | m_name_offset = m__io->read_u4le(); 102 | break; 103 | } 104 | default: { 105 | m_name_offset = m__io->read_u8le(); 106 | break; 107 | } 108 | } 109 | switch (_parent()->version()) { 110 | case 1: { 111 | m_offset = m__io->read_u4le(); 112 | break; 113 | } 114 | default: { 115 | m_offset = m__io->read_u8le(); 116 | break; 117 | } 118 | } 119 | switch (_parent()->version()) { 120 | case 1: { 121 | m_size = m__io->read_u4le(); 122 | break; 123 | } 124 | default: { 125 | m_size = m__io->read_u8le(); 126 | break; 127 | } 128 | } 129 | } 130 | 131 | w3sc_t::file_info_t::~file_info_t() { 132 | _clean_up(); 133 | } 134 | 135 | void w3sc_t::file_info_t::_clean_up() { 136 | if (f_data) { 137 | } 138 | if (f_name) { 139 | } 140 | } 141 | 142 | std::string w3sc_t::file_info_t::data() { 143 | if (f_data) 144 | return m_data; 145 | std::streampos _pos = m__io->pos(); 146 | m__io->seek(offset()); 147 | m_data = m__io->read_bytes(size()); 148 | m__io->seek(_pos); 149 | f_data = true; 150 | return m_data; 151 | } 152 | 153 | std::string w3sc_t::file_info_t::name() { 154 | if (f_name) 155 | return m_name; 156 | std::streampos _pos = m__io->pos(); 157 | m__io->seek((_parent()->names_offset() + name_offset())); 158 | m_name = kaitai::kstream::bytes_to_str(m__io->read_bytes_term(0, false, true, true), "UTF-8"); 159 | m__io->seek(_pos); 160 | f_name = true; 161 | return m_name; 162 | } 163 | 164 | std::vector* w3sc_t::file_infos() { 165 | if (f_file_infos) 166 | return m_file_infos; 167 | std::streampos _pos = m__io->pos(); 168 | m__io->seek(info_offset()); 169 | m_file_infos = new std::vector(); 170 | const int l_file_infos = files(); 171 | for (int i = 0; i < l_file_infos; i++) { 172 | m_file_infos->push_back(new file_info_t(m__io, this, m__root)); 173 | } 174 | m__io->seek(_pos); 175 | f_file_infos = true; 176 | return m_file_infos; 177 | } 178 | -------------------------------------------------------------------------------- /src/wwtools/w3sc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "kaitai/kaitaistream.h" 8 | #include "kaitai/structs/w3sc.h" 9 | #include "wwtools/util/write.hpp" 10 | #include "wwtools/w3sc.hpp" 11 | 12 | const uint8_t HEADER_LENGTH = 48; 13 | const uint16_t CACHE_BUFFER_SIZE = 4096; 14 | int get_names_offset( 15 | const std::vector> &files) { 16 | uint32_t ret = 0; 17 | for (const auto &file : files) { 18 | const std::string file_contents = file.second; 19 | ret += file_contents.size(); 20 | } 21 | ret += HEADER_LENGTH; 22 | return ret; 23 | } 24 | 25 | int get_info_offset( 26 | const std::vector> &files) { 27 | uint32_t ret = get_names_offset(files); 28 | for (const auto &file : files) { 29 | const std::string file_name = file.first; 30 | ret += file_name.size() + 1; // add 1 for '\0' char 31 | } 32 | return ret; 33 | } 34 | 35 | std::string get_footer_names( 36 | const std::vector> &files) { 37 | std::string ret; 38 | for (const auto &file : files) 39 | ret += file.first + '\0'; 40 | return ret; 41 | } 42 | 43 | std::string get_footer_infos( 44 | const std::vector> &files) { 45 | std::stringstream ss; 46 | std::vector data_offsets; 47 | data_offsets.resize(files.size(), 48 | HEADER_LENGTH); // offset starts after header 49 | for (size_t i = 0; i < files.size(); i++) { 50 | if (i == 0) 51 | continue; 52 | data_offsets[i] = data_offsets[i - 1] + files[i].second.size(); 53 | } 54 | // names 55 | std::vector name_offsets; 56 | name_offsets.resize(files.size(), 0); 57 | // uint32_t name_offsets[static_cast(files.size())]; 58 | for (size_t i = 0; i < files.size(); i++) { 59 | if (i == 0) 60 | continue; // only populate name_offsets starting at index 1 61 | name_offsets[i] = name_offsets[i - 1] + files[i].first.size() + 1; 62 | } 63 | for (size_t i = 0; i < files.size(); i++) { 64 | wwtools::util::write::little_endian(name_offsets[i], ss); 65 | wwtools::util::write::little_endian(data_offsets[i], ss); 66 | wwtools::util::write::little_endian(files[i].second.size(), ss); 67 | } 68 | 69 | return ss.str(); 70 | } 71 | 72 | // https://github.com/ADawesomeguy/witcher-3-sound-editing-tools/blob/82365dc8c7721ce6cc47cd57fabf065d805a5f74/create_sounds_cache.py#L19-L26 73 | uint64_t calculate_checksum(std::string data) { 74 | const uint64_t FNV_64_PRIME = 0x100000001b3; 75 | const uint64_t FNV1_64_INIT = 0xcbf29ce484222325; 76 | 77 | uint64_t ret = FNV1_64_INIT; 78 | for (const uint8_t &byte : data) { 79 | ret ^= byte; 80 | ret *= FNV_64_PRIME; 81 | } 82 | 83 | return ret; 84 | } 85 | 86 | uint64_t calculate_buffer_size( 87 | const std::vector> &files) { 88 | uint64_t ret = 0; 89 | for (int i = 0; i < files.size(); i++) { 90 | uint64_t file_size = files.at(i).second.size(); 91 | ret = std::max(ret, file_size); 92 | } 93 | 94 | // round up to nearest multiple of cache buffer size 95 | ret += CACHE_BUFFER_SIZE - (ret % CACHE_BUFFER_SIZE); 96 | return ret; 97 | } 98 | 99 | namespace wwtools::w3sc { 100 | const char MAGIC[4] = {'W', '3', 'S', 'C'}; 101 | const uint32_t VERSION = 1; // TODO: stuff changes from 102 | // 32-bit to 64-bit based on version 103 | const uint64_t DUMMY = 0; 104 | 105 | std::string get_info(const std::string &indata) { 106 | // Create a Kaitai stream with the input data 107 | kaitai::kstream ks(indata); 108 | 109 | // Create a BNK object from the stream 110 | w3sc_t cache(&ks); 111 | 112 | // Add data from header 113 | std::stringstream data_ss; 114 | data_ss << "Cache Version: " << cache.version() << std::endl; 115 | 116 | // Add WEM indexes and count 117 | data_ss << cache.files() << " embedded files:" << std::endl; 118 | for (auto file_info : *cache.file_infos()) { 119 | data_ss << " " << file_info->name() << std::endl; 120 | } 121 | 122 | return data_ss.str(); 123 | } 124 | 125 | // TODO: change vector string pair weird stuff to a class 126 | // also i think i have little endian/big endian mixed up and stuff 127 | void create(const std::vector> &files, 128 | std::ostream &os) { 129 | wwtools::util::write::big_endian(MAGIC, os); 130 | wwtools::util::write::little_endian(VERSION, os); 131 | wwtools::util::write::little_endian(DUMMY, os); 132 | wwtools::util::write::little_endian( 133 | static_cast(get_info_offset(files)), os); 134 | wwtools::util::write::little_endian( 135 | static_cast(files.size()), os); 136 | wwtools::util::write::little_endian( 137 | static_cast(get_names_offset(files)), os); 138 | wwtools::util::write::little_endian( 139 | (get_info_offset(files) - get_names_offset(files)), os); // names size 140 | 141 | // weird 8 bytes (buffer size?) 142 | wwtools::util::write::little_endian(DUMMY, os); 143 | // next weird 8 bytes (checksum?) 144 | wwtools::util::write::little_endian(DUMMY, os); 145 | // all data 146 | std::vector data_offsets; 147 | data_offsets.resize(files.size(), 48); 148 | for (size_t i = 0; i < files.size(); i++) { 149 | os.write(files[i].second.c_str(), files[i].second.size()); 150 | if (i == 0) 151 | continue; 152 | data_offsets[i] = data_offsets[i - 1] + files[i].second.size(); 153 | } 154 | // names 155 | std::vector name_offsets; 156 | name_offsets.resize(files.size(), 0); 157 | // uint32_t name_offsets[static_cast(files.size())]; 158 | for (size_t i = 0; i < files.size(); i++) { 159 | os.write(files[i].first.c_str(), 160 | files[i].first.size() + 1 /* include \0 */); 161 | if (i == 0) 162 | continue; // only populate name_offsets starting at index 1 163 | name_offsets[i] = name_offsets[i - 1] + files[i].first.size(); 164 | } 165 | // info 166 | for (size_t i = 0; i < files.size(); i++) { 167 | wwtools::util::write::little_endian(name_offsets[i], os); 168 | wwtools::util::write::little_endian(data_offsets[i], os); 169 | wwtools::util::write::little_endian(files[i].second.size(), os); 170 | } 171 | 172 | // buffer size 173 | os.seekp(32); 174 | wwtools::util::write::little_endian(calculate_buffer_size(files), 175 | os); 176 | // checksum and stuff 177 | os.seekp(40); 178 | wwtools::util::write::little_endian( 179 | calculate_checksum(get_footer_names(files) + get_footer_infos(files)), 180 | os); 181 | } 182 | } // namespace wwtools::w3sc 183 | -------------------------------------------------------------------------------- /src/revorb/revorb.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * REVORB - Recomputes page granule positions in Ogg Vorbis files. 3 | * version 0.2 (2008/06/29) 4 | * 5 | * Copyright (c) 2008, Jiri Hruska 6 | * 7 | * Permission to use, copy, modify, and/or distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | /*# INCLUDE=.\include #*/ 21 | /*# LIB=.\lib #*/ 22 | /*# CFLAGS=/D_UNICODE #*/ 23 | /*# LFLAGS=/NODEFAULTLIB:MSVCRT /LTCG /OPT:REF /MANIFEST:NO #*/ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | 37 | bool g_failed; 38 | 39 | namespace revorb { 40 | bool copy_headers(std::stringstream &fi, ogg_sync_state *si, 41 | ogg_stream_state *is, std::stringstream &outdata, 42 | ogg_stream_state *os, vorbis_info *vi) { 43 | char *buffer = ogg_sync_buffer(si, 4096); 44 | 45 | fi.read(buffer, 4096); 46 | auto numread = fi.gcount(); 47 | 48 | ogg_sync_wrote(si, numread); 49 | 50 | ogg_page page; 51 | int result = ogg_sync_pageout(si, &page); 52 | if (result != 1) { 53 | return false; 54 | } 55 | 56 | ogg_stream_init(is, ogg_page_serialno(&page)); 57 | ogg_stream_init(os, ogg_page_serialno(&page)); 58 | 59 | if (ogg_stream_pagein(is, &page) < 0) { 60 | ogg_stream_clear(is); 61 | ogg_stream_clear(os); 62 | return false; 63 | } 64 | 65 | ogg_packet packet; 66 | if (ogg_stream_packetout(is, &packet) != 1) { 67 | ogg_stream_clear(is); 68 | ogg_stream_clear(os); 69 | return false; 70 | } 71 | 72 | vorbis_comment vc; 73 | vorbis_comment_init(&vc); 74 | if (vorbis_synthesis_headerin(vi, &vc, &packet) < 0) { 75 | vorbis_comment_clear(&vc); 76 | ogg_stream_clear(is); 77 | ogg_stream_clear(os); 78 | return false; 79 | } 80 | 81 | ogg_stream_packetin(os, &packet); 82 | 83 | int i = 0; 84 | while (i < 2) { 85 | int res = ogg_sync_pageout(si, &page); 86 | 87 | if (res == 0) { 88 | buffer = ogg_sync_buffer(si, 4096); 89 | fi.read(buffer, 4096); 90 | numread = fi.gcount(); 91 | if (numread == 0 && i < 2) { 92 | ogg_stream_clear(is); 93 | ogg_stream_clear(os); 94 | return false; 95 | } 96 | ogg_sync_wrote(si, 4096); 97 | continue; 98 | } 99 | 100 | if (res == 1) { 101 | ogg_stream_pagein(is, &page); 102 | while (i < 2) { 103 | res = ogg_stream_packetout(is, &packet); 104 | if (res == 0) 105 | break; 106 | if (res < 0) { 107 | vorbis_comment_clear(&vc); 108 | ogg_stream_clear(is); 109 | ogg_stream_clear(os); 110 | return false; 111 | } 112 | vorbis_synthesis_headerin(vi, &vc, &packet); 113 | ogg_stream_packetin(os, &packet); 114 | i++; 115 | } 116 | } 117 | } 118 | 119 | vorbis_comment_clear(&vc); 120 | 121 | while (ogg_stream_flush(os, &page)) { 122 | outdata.write(reinterpret_cast(page.header), page.header_len); 123 | outdata.write(reinterpret_cast(page.body), page.body_len); 124 | } 125 | 126 | return true; 127 | } 128 | 129 | // Returns true if successful and false if not successful 130 | bool revorb(std::istream &indata, std::stringstream &outdata) { 131 | std::stringstream indata_ss; 132 | indata_ss << indata.rdbuf(); 133 | 134 | ogg_sync_state sync_in, sync_out; 135 | ogg_sync_init(&sync_in); 136 | ogg_sync_init(&sync_out); 137 | 138 | ogg_stream_state stream_in, stream_out; 139 | vorbis_info vi; 140 | vorbis_info_init(&vi); 141 | 142 | ogg_packet packet; 143 | ogg_page page; 144 | 145 | if (copy_headers(indata_ss, &sync_in, &stream_in, outdata, &stream_out, 146 | &vi)) { 147 | ogg_int64_t granpos = 0, packetnum = 0; 148 | int lastbs = 0; 149 | 150 | while (true) { 151 | int eos = 0; 152 | while (!eos) { 153 | int res = ogg_sync_pageout(&sync_in, &page); 154 | if (res == 0) { 155 | char *buffer = ogg_sync_buffer(&sync_in, 4096); 156 | indata_ss.read(buffer, 4096); 157 | auto numread = indata_ss.gcount(); 158 | if (numread > 0) 159 | ogg_sync_wrote(&sync_in, numread); 160 | else 161 | eos = 2; 162 | continue; 163 | } 164 | 165 | if (res < 0) { 166 | g_failed = true; 167 | } else { 168 | if (ogg_page_eos(&page)) 169 | eos = 1; 170 | ogg_stream_pagein(&stream_in, &page); 171 | 172 | while (true) { 173 | res = ogg_stream_packetout(&stream_in, &packet); 174 | if (res == 0) 175 | break; 176 | if (res < 0) { 177 | g_failed = true; 178 | continue; 179 | } 180 | 181 | int bs = vorbis_packet_blocksize(&vi, &packet); 182 | if (lastbs) 183 | granpos += (lastbs + bs) / 4; 184 | lastbs = bs; 185 | 186 | packet.granulepos = granpos; 187 | packet.packetno = packetnum++; 188 | if (!packet.e_o_s) { 189 | ogg_stream_packetin(&stream_out, &packet); 190 | 191 | ogg_page opage; 192 | while (ogg_stream_pageout(&stream_out, &opage)) { 193 | outdata.write(reinterpret_cast(opage.header), 194 | opage.header_len); 195 | outdata.write(reinterpret_cast(opage.body), 196 | opage.body_len); 197 | } 198 | } 199 | } 200 | } 201 | } 202 | 203 | if (eos == 2) 204 | break; 205 | 206 | { 207 | packet.e_o_s = 1; 208 | ogg_stream_packetin(&stream_out, &packet); 209 | ogg_page opage; 210 | while (ogg_stream_flush(&stream_out, &opage)) { 211 | outdata.write(reinterpret_cast(opage.header), 212 | opage.header_len); 213 | outdata.write(reinterpret_cast(opage.body), opage.body_len); 214 | } 215 | ogg_stream_clear(&stream_in); 216 | break; 217 | } 218 | } 219 | 220 | ogg_stream_clear(&stream_out); 221 | } else { 222 | g_failed = true; 223 | } 224 | 225 | vorbis_info_clear(&vi); 226 | 227 | ogg_sync_clear(&sync_in); 228 | ogg_sync_clear(&sync_out); 229 | 230 | return !g_failed; 231 | } 232 | } // namespace revorb 233 | -------------------------------------------------------------------------------- /include/kaitai/exceptions.h: -------------------------------------------------------------------------------- 1 | #ifndef KAITAI_EXCEPTIONS_H 2 | #define KAITAI_EXCEPTIONS_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // We need to use "noexcept" in virtual destructor of our exceptions 10 | // subclasses. Different compilers have different ideas on how to 11 | // achieve that: C++98 compilers prefer `throw()`, C++11 and later 12 | // use `noexcept`. We define KS_NOEXCEPT macro for that. 13 | 14 | #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) 15 | #define KS_NOEXCEPT noexcept 16 | #else 17 | #define KS_NOEXCEPT throw() 18 | #endif 19 | 20 | namespace kaitai { 21 | 22 | /** 23 | * Common ancestor for all errors related to `bytes_to_str` operation. Also used 24 | * to signal misc non-specific `bytes_to_str` failures. 25 | */ 26 | class bytes_to_str_error: public std::runtime_error { 27 | public: 28 | bytes_to_str_error(const std::string what): 29 | std::runtime_error(std::string("bytes_to_str error: ") + what) {} 30 | 31 | virtual ~bytes_to_str_error() KS_NOEXCEPT {}; 32 | }; 33 | 34 | /** 35 | * Exception to signal that `bytes_to_str` operation was requested to use some encoding 36 | * that is not available in given runtime environment. 37 | */ 38 | class unknown_encoding: public bytes_to_str_error { 39 | public: 40 | unknown_encoding(const std::string enc_name): 41 | bytes_to_str_error(std::string("unknown encoding: `") + enc_name + std::string("`")) {} 42 | 43 | virtual ~unknown_encoding() KS_NOEXCEPT {}; 44 | }; 45 | 46 | /** 47 | * Exception to signal that `bytes_to_str` operation failed to decode given byte sequence. 48 | */ 49 | class illegal_seq_in_encoding: public bytes_to_str_error { 50 | public: 51 | illegal_seq_in_encoding(const std::string what): 52 | bytes_to_str_error("illegal sequence: " + what) {} 53 | 54 | virtual ~illegal_seq_in_encoding() KS_NOEXCEPT {}; 55 | }; 56 | 57 | /** 58 | * Common ancestor for all error originating from Kaitai Struct usage. 59 | * Stores KSY source path, pointing to an element supposedly guilty of 60 | * an error. 61 | */ 62 | class kstruct_error: public std::runtime_error { 63 | public: 64 | kstruct_error(const std::string what, const std::string src_path): 65 | std::runtime_error(src_path + ": " + what), 66 | m_src_path(src_path) 67 | { 68 | } 69 | 70 | virtual ~kstruct_error() KS_NOEXCEPT {}; 71 | 72 | protected: 73 | const std::string m_src_path; 74 | }; 75 | 76 | /** 77 | * Error that occurs when default endianness should be decided with 78 | * a switch, but nothing matches (although using endianness expression 79 | * implies that there should be some positive result). 80 | */ 81 | class undecided_endianness_error: public kstruct_error { 82 | public: 83 | undecided_endianness_error(const std::string src_path): 84 | kstruct_error("unable to decide on endianness for a type", src_path) 85 | { 86 | } 87 | 88 | virtual ~undecided_endianness_error() KS_NOEXCEPT {}; 89 | }; 90 | 91 | /** 92 | * Common ancestor for all validation failures. Stores pointer to 93 | * KaitaiStream IO object which was involved in an error. 94 | */ 95 | class validation_failed_error: public kstruct_error { 96 | public: 97 | validation_failed_error(const std::string what, kstream* io, const std::string src_path): 98 | kstruct_error("at pos " + kstream::to_string(io->pos()) + ": validation failed: " + what, src_path), 99 | m_io(io) 100 | { 101 | } 102 | 103 | // "at pos #{io.pos}: validation failed: #{msg}" 104 | 105 | virtual ~validation_failed_error() KS_NOEXCEPT {}; 106 | 107 | protected: 108 | kstream* m_io; 109 | }; 110 | 111 | /** 112 | * Signals validation failure: we required "actual" value to be equal to 113 | * "expected", but it turned out that it's not. 114 | */ 115 | template 116 | class validation_not_equal_error: public validation_failed_error { 117 | public: 118 | validation_not_equal_error(const T& expected, const T& actual, kstream* io, const std::string src_path): 119 | validation_failed_error("not equal", io, src_path), 120 | m_expected(expected), 121 | m_actual(actual) 122 | { 123 | } 124 | 125 | // "not equal, expected #{expected.inspect}, but got #{actual.inspect}" 126 | 127 | virtual ~validation_not_equal_error() KS_NOEXCEPT {}; 128 | 129 | protected: 130 | const T& m_expected; 131 | const T& m_actual; 132 | }; 133 | 134 | /** 135 | * Signals validation failure: we required "actual" value to be greater 136 | * than or equal to "min", but it turned out that it's not. 137 | */ 138 | template 139 | class validation_less_than_error: public validation_failed_error { 140 | public: 141 | validation_less_than_error(const T& min, const T& actual, kstream* io, const std::string src_path): 142 | validation_failed_error("not in range", io, src_path), 143 | m_min(min), 144 | m_actual(actual) 145 | { 146 | } 147 | 148 | // "not in range, min #{min.inspect}, but got #{actual.inspect}" 149 | 150 | virtual ~validation_less_than_error() KS_NOEXCEPT {}; 151 | 152 | protected: 153 | const T& m_min; 154 | const T& m_actual; 155 | }; 156 | 157 | /** 158 | * Signals validation failure: we required "actual" value to be less 159 | * than or equal to "max", but it turned out that it's not. 160 | */ 161 | template 162 | class validation_greater_than_error: public validation_failed_error { 163 | public: 164 | validation_greater_than_error(const T& max, const T& actual, kstream* io, const std::string src_path): 165 | validation_failed_error("not in range", io, src_path), 166 | m_max(max), 167 | m_actual(actual) 168 | { 169 | } 170 | 171 | // "not in range, max #{max.inspect}, but got #{actual.inspect}" 172 | 173 | virtual ~validation_greater_than_error() KS_NOEXCEPT {}; 174 | 175 | protected: 176 | const T& m_max; 177 | const T& m_actual; 178 | }; 179 | 180 | /** 181 | * Signals validation failure: we required "actual" value to be from 182 | * the list, but it turned out that it's not. 183 | */ 184 | template 185 | class validation_not_any_of_error: public validation_failed_error { 186 | public: 187 | validation_not_any_of_error(const T& actual, kstream* io, const std::string src_path): 188 | validation_failed_error("not any of the list", io, src_path), 189 | m_actual(actual) 190 | { 191 | } 192 | 193 | // "not any of the list, got #{actual.inspect}" 194 | 195 | virtual ~validation_not_any_of_error() KS_NOEXCEPT {}; 196 | 197 | protected: 198 | const T& m_actual; 199 | }; 200 | 201 | /** 202 | * Signals validation failure: we required "actual" value to match 203 | * the expression, but it turned out that it doesn't. 204 | */ 205 | template 206 | class validation_expr_error: public validation_failed_error { 207 | public: 208 | validation_expr_error(const T& actual, kstream* io, const std::string src_path): 209 | validation_failed_error("not matching the expression", io, src_path), 210 | m_actual(actual) 211 | { 212 | } 213 | 214 | // "not matching the expression, got #{actual.inspect}" 215 | 216 | virtual ~validation_expr_error() KS_NOEXCEPT {}; 217 | 218 | protected: 219 | const T& m_actual; 220 | }; 221 | 222 | } 223 | 224 | #endif -------------------------------------------------------------------------------- /src/ww2ogg/codebook.cpp: -------------------------------------------------------------------------------- 1 | #define __STDC_CONSTANT_MACROS 2 | #include 3 | 4 | #include "ww2ogg/codebook.h" 5 | 6 | namespace ww2ogg { 7 | codebook_library::codebook_library(void) 8 | : codebook_data(NULL), codebook_offsets(NULL), codebook_count(0) {} 9 | 10 | codebook_library::codebook_library(std::string indata) 11 | : codebook_data(NULL), codebook_offsets(NULL), codebook_count(0) { 12 | std::stringstream is(indata); 13 | 14 | is.seekg(0, ios::end); 15 | long file_size = is.tellg(); 16 | 17 | is.seekg(file_size - 4, ios::beg); 18 | long offset_offset = read_32_le(is); 19 | codebook_count = (file_size - offset_offset) / 4; 20 | 21 | codebook_data = new char[offset_offset]; 22 | codebook_offsets = new long[codebook_count]; 23 | 24 | is.seekg(0, ios::beg); 25 | for (long i = 0; i < offset_offset; i++) { 26 | codebook_data[i] = is.get(); 27 | } 28 | 29 | for (long i = 0; i < codebook_count; i++) { 30 | codebook_offsets[i] = read_32_le(is); 31 | } 32 | } 33 | 34 | void codebook_library::rebuild(int i, bitoggstream &bos) { 35 | const char *cb = get_codebook(i); 36 | unsigned long cb_size; 37 | 38 | { 39 | long signed_cb_size = get_codebook_size(i); 40 | 41 | if (!cb || -1 == signed_cb_size) 42 | throw invalid_id(i); 43 | 44 | cb_size = signed_cb_size; 45 | } 46 | 47 | array_streambuf asb(cb, cb_size); 48 | istream is(&asb); 49 | bitstream bis(is); 50 | 51 | rebuild(bis, cb_size, bos); 52 | } 53 | 54 | /* cb_size == 0 to not check size (for an inline bitstream) */ 55 | void codebook_library::copy(bitstream &bis, bitoggstream &bos) { 56 | /* IN: 24 bit identifier, 16 bit dimensions, 24 bit entry count */ 57 | 58 | Bit_uint<24> id; 59 | Bit_uint<16> dimensions; 60 | Bit_uint<24> entries; 61 | 62 | bis >> id >> dimensions >> entries; 63 | 64 | if (0x564342 != id) { 65 | throw parse_error_str("invalid codebook identifier"); 66 | } 67 | 68 | // cout << "Codebook with " << dimensions << " dimensions, " << entries << " 69 | // entries" << endl; 70 | 71 | /* OUT: 24 bit identifier, 16 bit dimensions, 24 bit entry count */ 72 | bos << id << Bit_uint<16>(dimensions) << Bit_uint<24>(entries); 73 | 74 | // gather codeword lengths 75 | 76 | /* IN/OUT: 1 bit ordered flag */ 77 | Bit_uint<1> ordered; 78 | bis >> ordered; 79 | bos << ordered; 80 | if (ordered) { 81 | // cout << "Ordered " << endl; 82 | 83 | /* IN/OUT: 5 bit initial length */ 84 | Bit_uint<5> initial_length; 85 | bis >> initial_length; 86 | bos << initial_length; 87 | 88 | unsigned int current_entry = 0; 89 | while (current_entry < entries) { 90 | /* IN/OUT: ilog(entries-current_entry) bit count w/ given length */ 91 | Bit_uintv number(ilog(entries - current_entry)); 92 | bis >> number; 93 | bos << number; 94 | current_entry += number; 95 | } 96 | if (current_entry > entries) 97 | throw parse_error_str("current_entry out of range"); 98 | } else { 99 | /* IN/OUT: 1 bit sparse flag */ 100 | Bit_uint<1> sparse; 101 | bis >> sparse; 102 | bos << sparse; 103 | 104 | // cout << "Unordered, "; 105 | 106 | // if (sparse) 107 | //{ 108 | // cout << "Sparse" << endl; 109 | //} 110 | // else 111 | //{ 112 | // cout << "Nonsparse" << endl; 113 | //} 114 | 115 | for (unsigned int i = 0; i < entries; i++) { 116 | bool present_bool = true; 117 | 118 | if (sparse) { 119 | /* IN/OUT 1 bit sparse presence flag */ 120 | Bit_uint<1> present; 121 | bis >> present; 122 | bos << present; 123 | 124 | present_bool = (0 != present); 125 | } 126 | 127 | if (present_bool) { 128 | /* IN/OUT: 5 bit codeword length-1 */ 129 | Bit_uint<5> codeword_length; 130 | bis >> codeword_length; 131 | bos << codeword_length; 132 | } 133 | } 134 | } // done with lengths 135 | 136 | // lookup table 137 | 138 | /* IN/OUT: 4 bit lookup type */ 139 | Bit_uint<4> lookup_type; 140 | bis >> lookup_type; 141 | bos << lookup_type; 142 | 143 | if (0 == lookup_type) { 144 | // cout << "no lookup table" << endl; 145 | } else if (1 == lookup_type) { 146 | // cout << "lookup type 1" << endl; 147 | 148 | /* IN/OUT: 32 bit minimum length, 32 bit maximum length, 4 bit value 149 | * length-1, 1 bit sequence flag */ 150 | Bit_uint<32> min, max; 151 | Bit_uint<4> value_length; 152 | Bit_uint<1> sequence_flag; 153 | bis >> min >> max >> value_length >> sequence_flag; 154 | bos << min << max << value_length << sequence_flag; 155 | 156 | unsigned int quantvals = _book_maptype1_quantvals(entries, dimensions); 157 | for (unsigned int i = 0; i < quantvals; i++) { 158 | /* IN/OUT: n bit value */ 159 | Bit_uintv val(value_length + 1); 160 | bis >> val; 161 | bos << val; 162 | } 163 | } else if (2 == lookup_type) { 164 | throw parse_error_str("didn't expect lookup type 2"); 165 | } else { 166 | throw parse_error_str("invalid lookup type"); 167 | } 168 | 169 | // cout << "total bits read = " << bis.get_total_bits_read() << endl; 170 | } 171 | 172 | /* cb_size == 0 to not check size (for an inline bitstream) */ 173 | void codebook_library::rebuild(bitstream &bis, unsigned long cb_size, 174 | bitoggstream &bos) { 175 | /* IN: 4 bit dimensions, 14 bit entry count */ 176 | 177 | Bit_uint<4> dimensions; 178 | Bit_uint<14> entries; 179 | 180 | bis >> dimensions >> entries; 181 | 182 | // cout << "Codebook " << i << ", " << dimensions << " dimensions, " << 183 | // entries << " entries" << endl; cout << "Codebook with " << dimensions << " 184 | // dimensions, " << entries << " entries" << endl; 185 | 186 | /* OUT: 24 bit identifier, 16 bit dimensions, 24 bit entry count */ 187 | bos << Bit_uint<24>(0x564342) << Bit_uint<16>(dimensions) 188 | << Bit_uint<24>(entries); 189 | 190 | // gather codeword lengths 191 | 192 | /* IN/OUT: 1 bit ordered flag */ 193 | Bit_uint<1> ordered; 194 | bis >> ordered; 195 | bos << ordered; 196 | if (ordered) { 197 | // cout << "Ordered " << endl; 198 | 199 | /* IN/OUT: 5 bit initial length */ 200 | Bit_uint<5> initial_length; 201 | bis >> initial_length; 202 | bos << initial_length; 203 | 204 | unsigned int current_entry = 0; 205 | while (current_entry < entries) { 206 | /* IN/OUT: ilog(entries-current_entry) bit count w/ given length */ 207 | Bit_uintv number(ilog(entries - current_entry)); 208 | bis >> number; 209 | bos << number; 210 | current_entry += number; 211 | } 212 | if (current_entry > entries) 213 | throw parse_error_str("current_entry out of range"); 214 | } else { 215 | /* IN: 3 bit codeword length length, 1 bit sparse flag */ 216 | Bit_uint<3> codeword_length_length; 217 | Bit_uint<1> sparse; 218 | bis >> codeword_length_length >> sparse; 219 | 220 | // cout << "Unordered, " << codeword_length_length << " bit lengths, "; 221 | 222 | if (0 == codeword_length_length || 5 < codeword_length_length) { 223 | throw parse_error_str("nonsense codeword length"); 224 | } 225 | 226 | /* OUT: 1 bit sparse flag */ 227 | bos << sparse; 228 | // if (sparse) 229 | //{ 230 | // cout << "Sparse" << endl; 231 | //} 232 | // else 233 | //{ 234 | // cout << "Nonsparse" << endl; 235 | //} 236 | 237 | for (unsigned int i = 0; i < entries; i++) { 238 | bool present_bool = true; 239 | 240 | if (sparse) { 241 | /* IN/OUT 1 bit sparse presence flag */ 242 | Bit_uint<1> present; 243 | bis >> present; 244 | bos << present; 245 | 246 | present_bool = (0 != present); 247 | } 248 | 249 | if (present_bool) { 250 | /* IN: n bit codeword length-1 */ 251 | Bit_uintv codeword_length(codeword_length_length); 252 | bis >> codeword_length; 253 | 254 | /* OUT: 5 bit codeword length-1 */ 255 | bos << Bit_uint<5>(codeword_length); 256 | } 257 | } 258 | } // done with lengths 259 | 260 | // lookup table 261 | 262 | /* IN: 1 bit lookup type */ 263 | Bit_uint<1> lookup_type; 264 | bis >> lookup_type; 265 | /* OUT: 4 bit lookup type */ 266 | bos << Bit_uint<4>(lookup_type); 267 | 268 | if (0 == lookup_type) { 269 | // cout << "no lookup table" << endl; 270 | } else if (1 == lookup_type) { 271 | // cout << "lookup type 1" << endl; 272 | 273 | /* IN/OUT: 32 bit minimum length, 32 bit maximum length, 4 bit value 274 | * length-1, 1 bit sequence flag */ 275 | Bit_uint<32> min, max; 276 | Bit_uint<4> value_length; 277 | Bit_uint<1> sequence_flag; 278 | bis >> min >> max >> value_length >> sequence_flag; 279 | bos << min << max << value_length << sequence_flag; 280 | 281 | unsigned int quantvals = _book_maptype1_quantvals(entries, dimensions); 282 | for (unsigned int i = 0; i < quantvals; i++) { 283 | /* IN/OUT: n bit value */ 284 | Bit_uintv val(value_length + 1); 285 | bis >> val; 286 | bos << val; 287 | } 288 | } else if (2 == lookup_type) { 289 | throw parse_error_str("didn't expect lookup type 2"); 290 | } else { 291 | throw parse_error_str("invalid lookup type"); 292 | } 293 | 294 | // cout << "total bits read = " << bis.get_total_bits_read() << endl; 295 | 296 | /* check that we used exactly all bytes */ 297 | /* note: if all bits are used in the last byte there will be one extra 0 byte 298 | */ 299 | if (0 != cb_size && bis.get_total_bits_read() / 8 + 1 != cb_size) { 300 | throw size_mismatch(cb_size, bis.get_total_bits_read() / 8 + 1); 301 | } 302 | } 303 | } // namespace ww2ogg 304 | -------------------------------------------------------------------------------- /src/wwtools/bnk.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "kaitai/kaitaistream.h" 7 | #include "kaitai/structs/bnk.h" 8 | #include "wwtools/bnk.hpp" 9 | 10 | // #include "wwtools/util/event_name_to_id.hpp" 11 | 12 | // not in namespace, only used to keep 13 | // trace of events and corresponding 14 | // SFX for get_event_id_info 15 | struct EventSFX { 16 | bnk_t::action_type_t action_type; 17 | bnk_t::sound_effect_or_voice_t *sfx; 18 | bool is_child; 19 | }; 20 | 21 | namespace wwtools::bnk { 22 | void extract(const std::string &indata, std::vector &outdata) { 23 | // create a Kaitai stream with the input data 24 | kaitai::kstream ks(indata); 25 | 26 | // create a BNK object from the stream 27 | bnk_t bnk(&ks); 28 | 29 | // loop through each section to find the DATA section 30 | for (const auto §ion : *bnk.data()) { 31 | if (section->type() == "DATA") { 32 | // cast the section to a DATA section 33 | auto *data_section = (bnk_t::data_data_t *)(section->section_data()); 34 | // reserve the necessary amount 35 | outdata.reserve(data_section->didx_data()->num_files()); 36 | // loop through each data object in the section 37 | for (const auto &file_data : *data_section->data_obj_section()->data()) { 38 | // append the file vector with the file data 39 | outdata.push_back(file_data->file()); 40 | } 41 | } 42 | } 43 | } 44 | 45 | std::string get_info(const std::string &indata) { 46 | // create a Kaitai stream with the input data 47 | kaitai::kstream ks(indata); 48 | 49 | // create a BNK object from the stream 50 | bnk_t bnk(&ks); 51 | 52 | // add data from header 53 | std::stringstream data_ss; 54 | 55 | // loop through each section to find the BKHD (Bank Header) section 56 | for (const auto §ion : *bnk.data()) { 57 | if (section->type() == "BKHD") { 58 | bnk_t::bkhd_data_t *bkhd_section = 59 | (bnk_t::bkhd_data_t *)(section->section_data()); 60 | auto version = bkhd_section->version(); 61 | auto id = bkhd_section->id(); 62 | data_ss << "Version: " << version << '\n'; 63 | data_ss << "Soundbank ID: " << id << '\n'; 64 | } 65 | } 66 | 67 | // loop through each section to find the DIDX (Data Index) section 68 | for (const auto §ion : *bnk.data()) { 69 | if (section->type() == "DIDX") { 70 | auto *didx_section = (bnk_t::didx_data_t *)(section->section_data()); 71 | data_ss << didx_section->num_files() << " embedded WEM files:\n"; 72 | for (auto index : *didx_section->objs()) { 73 | data_ss << '\t' << index->id() << '\n'; 74 | } 75 | } 76 | } 77 | 78 | return data_ss.str(); 79 | } 80 | 81 | std::string get_event_id_info(const std::string &indata, 82 | const std::string &in_event_id) { 83 | kaitai::kstream ks(indata); 84 | bnk_t bnk(&ks); 85 | 86 | std::stringstream data_ss; 87 | // loop through each section to get to HIRC and find event ID 88 | for (const auto §ion : *bnk.data()) { 89 | int num_events = 0; 90 | // read HIRC 91 | if (section->type() == "HIRC") { 92 | // cast section to HIRC data 93 | bnk_t::hirc_data_t *hirc_data = 94 | (bnk_t::hirc_data_t *)(section->section_data()); 95 | // create a map between events and the corresponding event actions 96 | std::map /* event action (with gobj id) */> 99 | event_to_event_actions; 100 | // loop through all HIRC objects 101 | for (const auto &obj_i : *hirc_data->objs()) { 102 | if (obj_i->type() == bnk_t::OBJECT_TYPE_EVENT) { // get event 103 | num_events++; // increment the number of events 104 | bnk_t::event_t *event = 105 | (bnk_t::event_t *)(obj_i->object_data()); // cast to event 106 | bool all_event_ids = false; 107 | if (in_event_id.empty()) 108 | all_event_ids = true; 109 | // compare ID to command line input ID 110 | if (std::to_string(obj_i->id()) == in_event_id) { 111 | // get all event action IDs for this event 112 | for (const auto &event_action_id : *event->event_actions()) { 113 | // loop to get corresponding event actions from IDs 114 | for (const auto &obj_j : *hirc_data->objs()) { 115 | // check if it's an event actions, and if it is, check if it 116 | // matches up with an event_action_id corresponding to the 117 | // inputted event 118 | if (obj_j->type() == bnk_t::OBJECT_TYPE_EVENT_ACTION) { 119 | bnk_t::event_action_t *event_action = 120 | (bnk_t::event_action_t *)(obj_j->object_data()); 121 | if (obj_j->id() == event_action_id) { 122 | // if it points to a game object (sound or container) 123 | if (event_action->game_object_id() != 0) { 124 | // add it to the vector in the map corresponding to the 125 | // event ID 126 | event_to_event_actions[obj_i->id()].push_back( 127 | event_action); 128 | } 129 | } 130 | } 131 | } 132 | } 133 | } else if (all_event_ids) { 134 | // get all event action IDs for this event 135 | for (const auto &event_action_id : *event->event_actions()) { 136 | // loop to get corresponding event actions from IDs 137 | for (const auto &obj_j : *hirc_data->objs()) { 138 | // check if it's an event actions, and if it is, check if it 139 | // matches up with an event_action_id corresponding to the 140 | // inputted event 141 | if (obj_j->type() == bnk_t::OBJECT_TYPE_EVENT_ACTION) { 142 | bnk_t::event_action_t *event_action = 143 | (bnk_t::event_action_t *)(obj_j->object_data()); 144 | if (obj_j->id() == event_action_id) { 145 | // if it points to a game object (sound or container) 146 | if (event_action->game_object_id() != 0) { 147 | // add it to the vector in the map corresponding to the 148 | // event ID 149 | event_to_event_actions[obj_i->id()].push_back( 150 | event_action); 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | } 159 | 160 | std::map> 161 | event_to_event_sfxs; 162 | // loop through all objects again, this time looking for SFX 163 | for (const auto &obj : *hirc_data->objs()) { 164 | // TODO: figure out music tracks 165 | if (obj->type() == bnk_t::OBJECT_TYPE_SOUND_EFFECT_OR_VOICE) { 166 | bnk_t::sound_effect_or_voice_t *sound_effect_or_voice = 167 | (bnk_t::sound_effect_or_voice_t *)(obj->object_data()); 168 | 169 | // get parent ID since it can also be used to check if this is a child 170 | // of an object that is manipulated by the event 171 | uint32_t parent_id_offset = 6; 172 | uint8_t num_effects = sound_effect_or_voice->sound_structure().at(1); 173 | if (num_effects > 0) { 174 | parent_id_offset++; // bit mask for bypassed effects 175 | parent_id_offset += num_effects * 7; // 7 bytes for each effect 176 | } 177 | uint32_t parent_id = 0; 178 | std::stringstream ss; 179 | ss.write(sound_effect_or_voice->sound_structure().c_str(), 180 | sound_effect_or_voice->sound_structure().size()); 181 | ss.seekg(parent_id_offset); 182 | ss.read(reinterpret_cast(&parent_id), 4); 183 | 184 | // loop through each event ID and its corresponding event actions 185 | for (const auto &[event, event_actions] : event_to_event_actions) { 186 | for (const auto &event_action : event_actions) { 187 | if (event_action->game_object_id() == obj->id() || 188 | event_action->game_object_id() == parent_id) { 189 | EventSFX current_event_sfx{}; 190 | current_event_sfx.action_type = event_action->type(); 191 | current_event_sfx.sfx = sound_effect_or_voice; 192 | if (event_action->game_object_id() == parent_id) 193 | current_event_sfx.is_child = true; 194 | else 195 | current_event_sfx.is_child = false; 196 | event_to_event_sfxs[event].push_back(current_event_sfx); 197 | } 198 | } 199 | } 200 | } 201 | } 202 | data_ss << "Found " << num_events << " event(s)\n"; 203 | data_ss << event_to_event_sfxs.size() 204 | << " of them point to files in this BNK\n\n"; 205 | for (const auto &[event_id, event_sfxs] : event_to_event_sfxs) { 206 | data_ss << event_id << " (" 207 | << (get_event_name_from_id(event_id).empty() 208 | ? "can't find name" 209 | : get_event_name_from_id(event_id)) 210 | << ")\n"; 211 | for (const auto &event_sfx : event_sfxs) { 212 | data_ss << '\t' 213 | << wwtools::bnk::get_event_action_type(event_sfx.action_type) 214 | << ' ' << event_sfx.sfx->audio_file_id(); 215 | if (event_sfx.is_child) 216 | data_ss << " (child)\n"; 217 | else 218 | data_ss << '\n'; 219 | } 220 | data_ss << std::endl; // newline + flush 221 | } 222 | } 223 | } 224 | 225 | return data_ss.str(); 226 | } 227 | 228 | std::string get_wem_id_at_index(const std::string &indata, const int &index) { 229 | // create a Kaitai stream with the input data 230 | kaitai::kstream ks(indata); 231 | 232 | // create a BNK object from the stream 233 | bnk_t bnk(&ks); 234 | 235 | // add data from header 236 | std::stringstream data_ss; 237 | 238 | // loop through each section to find the DIDX (Data Index) section 239 | for (const auto §ion : *bnk.data()) { 240 | if (section->type() == "DIDX") { 241 | auto *didx_section = (bnk_t::didx_data_t *)(section->section_data()); 242 | return std::to_string(didx_section->objs()->at(index)->id()); 243 | } 244 | } 245 | 246 | return ""; 247 | } 248 | 249 | std::string get_event_action_type(bnk_t::action_type_t action_type) { 250 | std::string ret; 251 | switch (action_type) { 252 | case bnk_t::ACTION_TYPE_PLAY: 253 | ret = "play"; 254 | break; 255 | case bnk_t::ACTION_TYPE_PAUSE: 256 | ret = "pause"; 257 | break; 258 | case bnk_t::ACTION_TYPE_STOP: 259 | ret = "stop"; 260 | break; 261 | case bnk_t::ACTION_TYPE_RESUME: 262 | ret = "resume"; 263 | break; 264 | default: 265 | ret = std::to_string(action_type); 266 | break; 267 | } 268 | return ret; 269 | } 270 | 271 | std::string get_event_name_from_id(const std::uint32_t &event_id) { 272 | // std::string ret; 273 | // for (const auto &event : events) { 274 | // if (event_id == event.id) 275 | // ret = event.name; 276 | // } 277 | // return ret; // is empty if not found 278 | return ""; // temporary because this is just too much right now 279 | } 280 | } // namespace wwtools::bnk 281 | -------------------------------------------------------------------------------- /include/ww2ogg/bitstream.h: -------------------------------------------------------------------------------- 1 | #ifndef WW2OGG_BIT_STREAM_H 2 | #define WW2OGG_BIT_STREAM_H 3 | 4 | #ifndef __STDC_CONSTANT_MACROS 5 | #define __STDC_CONSTANT_MACROS 6 | #endif 7 | #include 8 | #include 9 | #include 10 | 11 | #include "crc.h" 12 | #include "errors.h" 13 | 14 | // host-endian-neutral integer reading 15 | namespace { 16 | uint32_t read_32_le(unsigned char b[4]) { 17 | uint32_t v = 0; 18 | for (int i = 3; i >= 0; i--) { 19 | v <<= 8; 20 | v |= b[i]; 21 | } 22 | 23 | return v; 24 | } 25 | 26 | uint32_t read_32_le(std::istream &is) { 27 | char b[4]; 28 | is.read(b, 4); 29 | 30 | return read_32_le(reinterpret_cast(b)); 31 | } 32 | 33 | void write_32_le(unsigned char b[4], uint32_t v) { 34 | for (int i = 0; i < 4; i++) { 35 | b[i] = v & 0xFF; 36 | v >>= 8; 37 | } 38 | } 39 | 40 | void write_32_le(std::ostream &os, uint32_t v) { 41 | char b[4]; 42 | 43 | write_32_le(reinterpret_cast(b), v); 44 | 45 | os.write(b, 4); 46 | } 47 | 48 | uint16_t read_16_le(unsigned char b[2]) { 49 | uint16_t v = 0; 50 | for (int i = 1; i >= 0; i--) { 51 | v <<= 8; 52 | v |= b[i]; 53 | } 54 | 55 | return v; 56 | } 57 | 58 | uint16_t read_16_le(std::istream &is) { 59 | char b[2]; 60 | is.read(b, 2); 61 | 62 | return read_16_le(reinterpret_cast(b)); 63 | } 64 | 65 | void write_16_le(unsigned char b[2], uint16_t v) { 66 | for (int i = 0; i < 2; i++) { 67 | b[i] = v & 0xFF; 68 | v >>= 8; 69 | } 70 | } 71 | 72 | void write_16_le(std::ostream &os, uint16_t v) { 73 | char b[2]; 74 | 75 | write_16_le(reinterpret_cast(b), v); 76 | 77 | os.write(b, 2); 78 | } 79 | 80 | uint32_t read_32_be(unsigned char b[4]) { 81 | uint32_t v = 0; 82 | for (int i = 0; i < 4; i++) { 83 | v <<= 8; 84 | v |= b[i]; 85 | } 86 | 87 | return v; 88 | } 89 | 90 | uint32_t read_32_be(std::istream &is) { 91 | char b[4]; 92 | is.read(b, 4); 93 | 94 | return read_32_be(reinterpret_cast(b)); 95 | } 96 | 97 | void write_32_be(unsigned char b[4], uint32_t v) { 98 | for (int i = 3; i >= 0; i--) { 99 | b[i] = v & 0xFF; 100 | v >>= 8; 101 | } 102 | } 103 | 104 | void write_32_be(std::ostream &os, uint32_t v) { 105 | char b[4]; 106 | 107 | write_32_be(reinterpret_cast(b), v); 108 | 109 | os.write(b, 4); 110 | } 111 | 112 | uint16_t read_16_be(unsigned char b[2]) { 113 | uint16_t v = 0; 114 | for (int i = 0; i < 2; i++) { 115 | v <<= 8; 116 | v |= b[i]; 117 | } 118 | 119 | return v; 120 | } 121 | 122 | uint16_t read_16_be(std::istream &is) { 123 | char b[2]; 124 | is.read(b, 2); 125 | 126 | return read_16_be(reinterpret_cast(b)); 127 | } 128 | 129 | void write_16_be(unsigned char b[2], uint16_t v) { 130 | for (int i = 1; i >= 0; i--) { 131 | b[i] = v & 0xFF; 132 | v >>= 8; 133 | } 134 | } 135 | 136 | void write_16_be(std::ostream &os, uint16_t v) { 137 | char b[2]; 138 | 139 | write_16_be(reinterpret_cast(b), v); 140 | 141 | os.write(b, 2); 142 | } 143 | 144 | } // namespace 145 | 146 | namespace ww2ogg { 147 | // using an istream, pull off individual bits with get_bit (LSB first) 148 | class bitstream { 149 | std::istream &is; 150 | 151 | unsigned char bit_buffer; 152 | unsigned int bits_left; 153 | unsigned long total_bits_read; 154 | 155 | public: 156 | class Weird_char_size {}; 157 | class Out_of_bits {}; 158 | 159 | bitstream(std::istream &_is) 160 | : is(_is), bit_buffer(0), bits_left(0), total_bits_read(0) { 161 | if (std::numeric_limits::digits != 8) 162 | throw Weird_char_size(); 163 | } 164 | bool get_bit() { 165 | if (bits_left == 0) { 166 | 167 | int c = is.get(); 168 | if (c == EOF) 169 | throw Out_of_bits(); 170 | bit_buffer = c; 171 | bits_left = 8; 172 | } 173 | total_bits_read++; 174 | bits_left--; 175 | return ((bit_buffer & (0x80 >> bits_left)) != 0); 176 | } 177 | 178 | unsigned long get_total_bits_read(void) const { return total_bits_read; } 179 | }; 180 | 181 | class bitoggstream { 182 | std::ostream &os; 183 | 184 | unsigned char bit_buffer; 185 | unsigned int bits_stored; 186 | 187 | enum { header_bytes = 27, max_segments = 255, segment_size = 255 }; 188 | 189 | unsigned int payload_bytes; 190 | bool first, continued; 191 | unsigned char 192 | page_buffer[header_bytes + max_segments + segment_size * max_segments]; 193 | uint32_t granule; 194 | uint32_t seqno; 195 | 196 | public: 197 | class Weird_char_size {}; 198 | 199 | bitoggstream(std::ostream &_os) 200 | : os(_os), bit_buffer(0), bits_stored(0), payload_bytes(0), first(true), 201 | continued(false), granule(0), seqno(0) { 202 | if (std::numeric_limits::digits != 8) 203 | throw Weird_char_size(); 204 | } 205 | 206 | void put_bit(bool bit) { 207 | if (bit) 208 | bit_buffer |= 1 << bits_stored; 209 | 210 | bits_stored++; 211 | if (bits_stored == 8) { 212 | flush_bits(); 213 | } 214 | } 215 | 216 | void set_granule(uint32_t g) { granule = g; } 217 | 218 | void flush_bits(void) { 219 | if (bits_stored != 0) { 220 | if (payload_bytes == segment_size * max_segments) { 221 | throw parse_error_str("ran out of space in an Ogg packet"); 222 | flush_page(true); 223 | } 224 | 225 | page_buffer[header_bytes + max_segments + payload_bytes] = bit_buffer; 226 | payload_bytes++; 227 | 228 | bits_stored = 0; 229 | bit_buffer = 0; 230 | } 231 | } 232 | 233 | void flush_page(bool next_continued = false, bool last = false) { 234 | if (payload_bytes != segment_size * max_segments) { 235 | flush_bits(); 236 | } 237 | 238 | if (payload_bytes != 0) { 239 | unsigned int segments = (payload_bytes + segment_size) / 240 | segment_size; // intentionally round up 241 | if (segments == max_segments + 1) 242 | segments = max_segments; // at max eschews the final 0 243 | 244 | // move payload back 245 | for (unsigned int i = 0; i < payload_bytes; i++) { 246 | page_buffer[header_bytes + segments + i] = 247 | page_buffer[header_bytes + max_segments + i]; 248 | } 249 | 250 | page_buffer[0] = 'O'; 251 | page_buffer[1] = 'g'; 252 | page_buffer[2] = 'g'; 253 | page_buffer[3] = 'S'; 254 | page_buffer[4] = 0; // stream_structure_version 255 | page_buffer[5] = (continued ? 1 : 0) | (first ? 2 : 0) | 256 | (last ? 4 : 0); // header_type_flag 257 | write_32_le(&page_buffer[6], granule); // granule low bits 258 | write_32_le(&page_buffer[10], 0); // granule high bits 259 | if (granule == UINT32_C(0xFFFFFFFF)) 260 | write_32_le(&page_buffer[10], UINT32_C(0xFFFFFFFF)); 261 | write_32_le(&page_buffer[14], 1); // stream serial number 262 | write_32_le(&page_buffer[18], seqno); // page sequence number 263 | write_32_le(&page_buffer[22], 0); // checksum (0 for now) 264 | page_buffer[26] = segments; // segment count 265 | 266 | // lacing values 267 | for (unsigned int i = 0, bytes_left = payload_bytes; i < segments; i++) { 268 | if (bytes_left >= segment_size) { 269 | bytes_left -= segment_size; 270 | page_buffer[27 + i] = segment_size; 271 | } else { 272 | page_buffer[27 + i] = bytes_left; 273 | } 274 | } 275 | 276 | // checksum 277 | write_32_le( 278 | &page_buffer[22], 279 | checksum(page_buffer, header_bytes + segments + payload_bytes)); 280 | 281 | // output to ostream 282 | for (unsigned int i = 0; i < header_bytes + segments + payload_bytes; 283 | i++) { 284 | os.put(page_buffer[i]); 285 | } 286 | 287 | seqno++; 288 | first = false; 289 | continued = next_continued; 290 | payload_bytes = 0; 291 | } 292 | } 293 | 294 | ~bitoggstream() { flush_page(); } 295 | }; 296 | 297 | // integer of a certain number of bits, to allow reading just that many 298 | // bits from the bitstream 299 | template class Bit_uint { 300 | unsigned int total; 301 | 302 | public: 303 | class Too_many_bits {}; 304 | class Int_too_big {}; 305 | 306 | Bit_uint() : total(0) { 307 | if (BIT_SIZE > 308 | static_cast(std::numeric_limits::digits)) 309 | throw Too_many_bits(); 310 | } 311 | 312 | explicit Bit_uint(unsigned int v) : total(v) { 313 | if (BIT_SIZE > 314 | static_cast(std::numeric_limits::digits)) 315 | throw Too_many_bits(); 316 | if ((v >> (BIT_SIZE - 1U)) > 1U) 317 | throw Int_too_big(); 318 | } 319 | 320 | Bit_uint &operator=(unsigned int v) { 321 | if ((v >> (BIT_SIZE - 1U)) > 1U) 322 | throw Int_too_big(); 323 | total = v; 324 | return *this; 325 | } 326 | 327 | operator unsigned int() const { return total; } 328 | 329 | friend bitstream &operator>>(bitstream &bstream, Bit_uint &bui) { 330 | bui.total = 0; 331 | for (unsigned int i = 0; i < BIT_SIZE; i++) { 332 | if (bstream.get_bit()) 333 | bui.total |= (1U << i); 334 | } 335 | return bstream; 336 | } 337 | 338 | friend bitoggstream &operator<<(bitoggstream &bstream, const Bit_uint &bui) { 339 | for (unsigned int i = 0; i < BIT_SIZE; i++) { 340 | bstream.put_bit((bui.total & (1U << i)) != 0); 341 | } 342 | return bstream; 343 | } 344 | }; 345 | 346 | // integer of a run-time specified number of bits 347 | // bits from the bitstream 348 | class Bit_uintv { 349 | unsigned int size; 350 | unsigned int total; 351 | 352 | public: 353 | class Too_many_bits {}; 354 | class Int_too_big {}; 355 | 356 | explicit Bit_uintv(unsigned int s) : size(s), total(0) { 357 | if (s > 358 | static_cast(std::numeric_limits::digits)) 359 | throw Too_many_bits(); 360 | } 361 | 362 | Bit_uintv(unsigned int s, unsigned int v) : size(s), total(v) { 363 | if (size > 364 | static_cast(std::numeric_limits::digits)) 365 | throw Too_many_bits(); 366 | if ((v >> (size - 1U)) > 1U) 367 | throw Int_too_big(); 368 | } 369 | 370 | Bit_uintv &operator=(unsigned int v) { 371 | if ((v >> (size - 1U)) > 1U) 372 | throw Int_too_big(); 373 | total = v; 374 | return *this; 375 | } 376 | 377 | operator unsigned int() const { return total; } 378 | 379 | friend bitstream &operator>>(bitstream &bstream, Bit_uintv &bui) { 380 | bui.total = 0; 381 | for (unsigned int i = 0; i < bui.size; i++) { 382 | if (bstream.get_bit()) 383 | bui.total |= (1U << i); 384 | } 385 | return bstream; 386 | } 387 | 388 | friend bitoggstream &operator<<(bitoggstream &bstream, const Bit_uintv &bui) { 389 | for (unsigned int i = 0; i < bui.size; i++) { 390 | bstream.put_bit((bui.total & (1U << i)) != 0); 391 | } 392 | return bstream; 393 | } 394 | }; 395 | 396 | class array_streambuf : public std::streambuf { 397 | // Intentionally undefined 398 | array_streambuf &operator=(const array_streambuf &rhs); 399 | array_streambuf(const array_streambuf &rhs); 400 | 401 | char *arr; 402 | 403 | public: 404 | array_streambuf(const char *a, int l) : arr(0) { 405 | arr = new char[l]; 406 | for (int i = 0; i < l; i++) 407 | arr[i] = a[i]; 408 | setg(arr, arr, arr + l); 409 | } 410 | ~array_streambuf() { delete[] arr; } 411 | }; 412 | } // namespace ww2ogg 413 | 414 | #endif // WW2OGG_BIT_STREAM_H 415 | -------------------------------------------------------------------------------- /include/kaitai/structs/wem.h: -------------------------------------------------------------------------------- 1 | #ifndef WEM_H_ 2 | #define WEM_H_ 3 | 4 | // This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild 5 | 6 | #include "kaitai/kaitaistruct.h" 7 | #include 8 | #include 9 | 10 | #if KAITAI_STRUCT_VERSION < 11000L 11 | #error "Incompatible Kaitai Struct C++/STL API: version 0.11 or later is required" 12 | #endif 13 | 14 | class wem_t : public kaitai::kstruct { 15 | 16 | public: 17 | class chunk_t; 18 | class fmt_chunk_t; 19 | class junk_chunk_t; 20 | class list_labl_subchunk_t; 21 | class riff_header_t; 22 | class list_chunk_t; 23 | class data_chunk_t; 24 | class cue_chunk_t; 25 | class riff_chunk_t; 26 | class cue_point_subchunk_t; 27 | class random_blank_bytes_t; 28 | 29 | wem_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, wem_t* p__root = 0); 30 | 31 | private: 32 | void _read(); 33 | void _clean_up(); 34 | 35 | public: 36 | ~wem_t(); 37 | 38 | class chunk_t : public kaitai::kstruct { 39 | 40 | public: 41 | 42 | chunk_t(kaitai::kstream* p__io, wem_t* p__parent = 0, wem_t* p__root = 0); 43 | 44 | private: 45 | void _read(); 46 | void _clean_up(); 47 | 48 | public: 49 | ~chunk_t(); 50 | 51 | private: 52 | riff_chunk_t* m_riff_chunk; 53 | kaitai::kstruct* m_data; 54 | bool n_data; 55 | 56 | public: 57 | bool _is_null_data() { data(); return n_data; }; 58 | 59 | private: 60 | wem_t* m__root; 61 | wem_t* m__parent; 62 | 63 | public: 64 | riff_chunk_t* riff_chunk() const { return m_riff_chunk; } 65 | kaitai::kstruct* data() const { return m_data; } 66 | wem_t* _root() const { return m__root; } 67 | wem_t* _parent() const { return m__parent; } 68 | }; 69 | 70 | class fmt_chunk_t : public kaitai::kstruct { 71 | 72 | public: 73 | 74 | fmt_chunk_t(kaitai::kstream* p__io, wem_t::chunk_t* p__parent = 0, wem_t* p__root = 0); 75 | 76 | private: 77 | void _read(); 78 | void _clean_up(); 79 | 80 | public: 81 | ~fmt_chunk_t(); 82 | 83 | private: 84 | uint16_t m_compression_code; 85 | uint16_t m_channels; 86 | uint32_t m_samples_per_sec; 87 | uint32_t m_avg_bytes_per_sec; 88 | uint16_t m_block_align; 89 | uint16_t m_bits_per_sample; 90 | uint16_t m_extra_byte_count; 91 | uint16_t m_valid_bits_per_sample; 92 | uint32_t m_channel_mask; 93 | std::string m_guid; 94 | wem_t* m__root; 95 | wem_t::chunk_t* m__parent; 96 | 97 | public: 98 | uint16_t compression_code() const { return m_compression_code; } 99 | uint16_t channels() const { return m_channels; } 100 | uint32_t samples_per_sec() const { return m_samples_per_sec; } 101 | uint32_t avg_bytes_per_sec() const { return m_avg_bytes_per_sec; } 102 | uint16_t block_align() const { return m_block_align; } 103 | uint16_t bits_per_sample() const { return m_bits_per_sample; } 104 | uint16_t extra_byte_count() const { return m_extra_byte_count; } 105 | uint16_t valid_bits_per_sample() const { return m_valid_bits_per_sample; } 106 | uint32_t channel_mask() const { return m_channel_mask; } 107 | std::string guid() const { return m_guid; } 108 | wem_t* _root() const { return m__root; } 109 | wem_t::chunk_t* _parent() const { return m__parent; } 110 | }; 111 | 112 | class junk_chunk_t : public kaitai::kstruct { 113 | 114 | public: 115 | 116 | junk_chunk_t(kaitai::kstream* p__io, wem_t::chunk_t* p__parent = 0, wem_t* p__root = 0); 117 | 118 | private: 119 | void _read(); 120 | void _clean_up(); 121 | 122 | public: 123 | ~junk_chunk_t(); 124 | 125 | private: 126 | std::string m_junk; 127 | wem_t* m__root; 128 | wem_t::chunk_t* m__parent; 129 | 130 | public: 131 | std::string junk() const { return m_junk; } 132 | wem_t* _root() const { return m__root; } 133 | wem_t::chunk_t* _parent() const { return m__parent; } 134 | }; 135 | 136 | class list_labl_subchunk_t : public kaitai::kstruct { 137 | 138 | public: 139 | 140 | list_labl_subchunk_t(kaitai::kstream* p__io, wem_t::list_chunk_t* p__parent = 0, wem_t* p__root = 0); 141 | 142 | private: 143 | void _read(); 144 | void _clean_up(); 145 | 146 | public: 147 | ~list_labl_subchunk_t(); 148 | 149 | private: 150 | std::string m_magic; 151 | uint32_t m_size; 152 | uint32_t m_cue_point_id; 153 | std::string m_data; 154 | wem_t* m__root; 155 | wem_t::list_chunk_t* m__parent; 156 | 157 | public: 158 | std::string magic() const { return m_magic; } 159 | uint32_t size() const { return m_size; } 160 | uint32_t cue_point_id() const { return m_cue_point_id; } 161 | std::string data() const { return m_data; } 162 | wem_t* _root() const { return m__root; } 163 | wem_t::list_chunk_t* _parent() const { return m__parent; } 164 | }; 165 | 166 | class riff_header_t : public kaitai::kstruct { 167 | 168 | public: 169 | 170 | riff_header_t(kaitai::kstream* p__io, wem_t* p__parent = 0, wem_t* p__root = 0); 171 | 172 | private: 173 | void _read(); 174 | void _clean_up(); 175 | 176 | public: 177 | ~riff_header_t(); 178 | 179 | private: 180 | std::string m_magic; 181 | uint32_t m_chunk_size; 182 | uint32_t m_wav_id; 183 | wem_t* m__root; 184 | wem_t* m__parent; 185 | 186 | public: 187 | std::string magic() const { return m_magic; } 188 | uint32_t chunk_size() const { return m_chunk_size; } 189 | uint32_t wav_id() const { return m_wav_id; } 190 | wem_t* _root() const { return m__root; } 191 | wem_t* _parent() const { return m__parent; } 192 | }; 193 | 194 | class list_chunk_t : public kaitai::kstruct { 195 | 196 | public: 197 | 198 | list_chunk_t(uint32_t p_size, kaitai::kstream* p__io, wem_t::chunk_t* p__parent = 0, wem_t* p__root = 0); 199 | 200 | private: 201 | void _read(); 202 | void _clean_up(); 203 | 204 | public: 205 | ~list_chunk_t(); 206 | 207 | private: 208 | std::string m_adtl; 209 | list_labl_subchunk_t* m_labl; 210 | uint32_t m_size; 211 | wem_t* m__root; 212 | wem_t::chunk_t* m__parent; 213 | 214 | public: 215 | std::string adtl() const { return m_adtl; } 216 | list_labl_subchunk_t* labl() const { return m_labl; } 217 | uint32_t size() const { return m_size; } 218 | wem_t* _root() const { return m__root; } 219 | wem_t::chunk_t* _parent() const { return m__parent; } 220 | }; 221 | 222 | class data_chunk_t : public kaitai::kstruct { 223 | 224 | public: 225 | 226 | data_chunk_t(uint32_t p_size, kaitai::kstream* p__io, wem_t::chunk_t* p__parent = 0, wem_t* p__root = 0); 227 | 228 | private: 229 | void _read(); 230 | void _clean_up(); 231 | 232 | public: 233 | ~data_chunk_t(); 234 | 235 | private: 236 | std::string m_data; 237 | uint32_t m_size; 238 | wem_t* m__root; 239 | wem_t::chunk_t* m__parent; 240 | 241 | public: 242 | std::string data() const { return m_data; } 243 | uint32_t size() const { return m_size; } 244 | wem_t* _root() const { return m__root; } 245 | wem_t::chunk_t* _parent() const { return m__parent; } 246 | }; 247 | 248 | class cue_chunk_t : public kaitai::kstruct { 249 | 250 | public: 251 | 252 | cue_chunk_t(kaitai::kstream* p__io, wem_t::chunk_t* p__parent = 0, wem_t* p__root = 0); 253 | 254 | private: 255 | void _read(); 256 | void _clean_up(); 257 | 258 | public: 259 | ~cue_chunk_t(); 260 | 261 | private: 262 | uint32_t m_cue_point_count; 263 | std::vector* m_cue_points; 264 | wem_t* m__root; 265 | wem_t::chunk_t* m__parent; 266 | 267 | public: 268 | uint32_t cue_point_count() const { return m_cue_point_count; } 269 | std::vector* cue_points() const { return m_cue_points; } 270 | wem_t* _root() const { return m__root; } 271 | wem_t::chunk_t* _parent() const { return m__parent; } 272 | }; 273 | 274 | class riff_chunk_t : public kaitai::kstruct { 275 | 276 | public: 277 | 278 | riff_chunk_t(kaitai::kstream* p__io, wem_t::chunk_t* p__parent = 0, wem_t* p__root = 0); 279 | 280 | private: 281 | void _read(); 282 | void _clean_up(); 283 | 284 | public: 285 | ~riff_chunk_t(); 286 | 287 | private: 288 | std::string m_dummy; 289 | std::string m_type; 290 | uint32_t m_size; 291 | wem_t* m__root; 292 | wem_t::chunk_t* m__parent; 293 | 294 | public: 295 | std::string dummy() const { return m_dummy; } 296 | std::string type() const { return m_type; } 297 | uint32_t size() const { return m_size; } 298 | wem_t* _root() const { return m__root; } 299 | wem_t::chunk_t* _parent() const { return m__parent; } 300 | }; 301 | 302 | class cue_point_subchunk_t : public kaitai::kstruct { 303 | 304 | public: 305 | 306 | cue_point_subchunk_t(kaitai::kstream* p__io, wem_t::cue_chunk_t* p__parent = 0, wem_t* p__root = 0); 307 | 308 | private: 309 | void _read(); 310 | void _clean_up(); 311 | 312 | public: 313 | ~cue_point_subchunk_t(); 314 | 315 | private: 316 | uint32_t m_id; 317 | uint32_t m_position; 318 | uint32_t m_data_chunk_id; 319 | uint32_t m_chunk_start; 320 | uint32_t m_block_start; 321 | uint32_t m_sample_start; 322 | wem_t* m__root; 323 | wem_t::cue_chunk_t* m__parent; 324 | 325 | public: 326 | uint32_t id() const { return m_id; } 327 | uint32_t position() const { return m_position; } 328 | uint32_t data_chunk_id() const { return m_data_chunk_id; } 329 | uint32_t chunk_start() const { return m_chunk_start; } 330 | uint32_t block_start() const { return m_block_start; } 331 | uint32_t sample_start() const { return m_sample_start; } 332 | wem_t* _root() const { return m__root; } 333 | wem_t::cue_chunk_t* _parent() const { return m__parent; } 334 | }; 335 | 336 | class random_blank_bytes_t : public kaitai::kstruct { 337 | 338 | public: 339 | 340 | random_blank_bytes_t(kaitai::kstream* p__io, wem_t::chunk_t* p__parent = 0, wem_t* p__root = 0); 341 | 342 | private: 343 | void _read(); 344 | void _clean_up(); 345 | 346 | public: 347 | ~random_blank_bytes_t(); 348 | 349 | private: 350 | std::vector* m_dummy; 351 | wem_t* m__root; 352 | wem_t::chunk_t* m__parent; 353 | 354 | public: 355 | std::vector* dummy() const { return m_dummy; } 356 | wem_t* _root() const { return m__root; } 357 | wem_t::chunk_t* _parent() const { return m__parent; } 358 | }; 359 | 360 | private: 361 | riff_header_t* m_riff_header; 362 | std::vector* m_chunks; 363 | wem_t* m__root; 364 | kaitai::kstruct* m__parent; 365 | 366 | public: 367 | riff_header_t* riff_header() const { return m_riff_header; } 368 | std::vector* chunks() const { return m_chunks; } 369 | wem_t* _root() const { return m__root; } 370 | kaitai::kstruct* _parent() const { return m__parent; } 371 | }; 372 | 373 | #endif // WEM_H_ 374 | -------------------------------------------------------------------------------- /src/kaitai/structs/wem.cpp: -------------------------------------------------------------------------------- 1 | // This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild 2 | 3 | #include "kaitai/structs/wem.h" 4 | #include "kaitai/exceptions.h" 5 | 6 | wem_t::wem_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 7 | m__parent = p__parent; 8 | m__root = this; 9 | m_riff_header = 0; 10 | m_chunks = 0; 11 | 12 | try { 13 | _read(); 14 | } catch(...) { 15 | _clean_up(); 16 | throw; 17 | } 18 | } 19 | 20 | void wem_t::_read() { 21 | m_riff_header = new riff_header_t(m__io, this, m__root); 22 | m_chunks = new std::vector(); 23 | { 24 | int i = 0; 25 | while (!m__io->is_eof()) { 26 | m_chunks->push_back(new chunk_t(m__io, this, m__root)); 27 | i++; 28 | } 29 | } 30 | } 31 | 32 | wem_t::~wem_t() { 33 | _clean_up(); 34 | } 35 | 36 | void wem_t::_clean_up() { 37 | if (m_riff_header) { 38 | delete m_riff_header; m_riff_header = 0; 39 | } 40 | if (m_chunks) { 41 | for (std::vector::iterator it = m_chunks->begin(); it != m_chunks->end(); ++it) { 42 | delete *it; 43 | } 44 | delete m_chunks; m_chunks = 0; 45 | } 46 | } 47 | 48 | wem_t::chunk_t::chunk_t(kaitai::kstream* p__io, wem_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 49 | m__parent = p__parent; 50 | m__root = p__root; 51 | m_riff_chunk = 0; 52 | 53 | try { 54 | _read(); 55 | } catch(...) { 56 | _clean_up(); 57 | throw; 58 | } 59 | } 60 | 61 | void wem_t::chunk_t::_read() { 62 | m_riff_chunk = new riff_chunk_t(m__io, this, m__root); 63 | n_data = true; 64 | { 65 | std::string on = riff_chunk()->type(); 66 | if (on == std::string("\x64\x61\x74\x61", 4)) { 67 | n_data = false; 68 | m_data = new data_chunk_t(riff_chunk()->size(), m__io, this, m__root); 69 | } 70 | else if (on == std::string("\x00\x00\x00\x00", 4)) { 71 | n_data = false; 72 | m_data = new random_blank_bytes_t(m__io, this, m__root); 73 | } 74 | else if (on == std::string("\x4C\x49\x53\x54", 4)) { 75 | n_data = false; 76 | m_data = new list_chunk_t(riff_chunk()->size(), m__io, this, m__root); 77 | } 78 | else if (on == std::string("\x4A\x55\x4E\x4B", 4)) { 79 | n_data = false; 80 | m_data = new junk_chunk_t(m__io, this, m__root); 81 | } 82 | else if (on == std::string("\x63\x75\x65\x20", 4)) { 83 | n_data = false; 84 | m_data = new cue_chunk_t(m__io, this, m__root); 85 | } 86 | else if (on == std::string("\x66\x6D\x74\x20", 4)) { 87 | n_data = false; 88 | m_data = new fmt_chunk_t(m__io, this, m__root); 89 | } 90 | } 91 | } 92 | 93 | wem_t::chunk_t::~chunk_t() { 94 | _clean_up(); 95 | } 96 | 97 | void wem_t::chunk_t::_clean_up() { 98 | if (m_riff_chunk) { 99 | delete m_riff_chunk; m_riff_chunk = 0; 100 | } 101 | if (!n_data) { 102 | if (m_data) { 103 | delete m_data; m_data = 0; 104 | } 105 | } 106 | } 107 | 108 | wem_t::fmt_chunk_t::fmt_chunk_t(kaitai::kstream* p__io, wem_t::chunk_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 109 | m__parent = p__parent; 110 | m__root = p__root; 111 | 112 | try { 113 | _read(); 114 | } catch(...) { 115 | _clean_up(); 116 | throw; 117 | } 118 | } 119 | 120 | void wem_t::fmt_chunk_t::_read() { 121 | m_compression_code = m__io->read_u2le(); 122 | m_channels = m__io->read_u2le(); 123 | m_samples_per_sec = m__io->read_u4le(); 124 | m_avg_bytes_per_sec = m__io->read_u4le(); 125 | m_block_align = m__io->read_u2le(); 126 | m_bits_per_sample = m__io->read_u2le(); 127 | m_extra_byte_count = m__io->read_u2le(); 128 | m_valid_bits_per_sample = m__io->read_u2le(); 129 | m_channel_mask = m__io->read_u4le(); 130 | m_guid = kaitai::kstream::bytes_to_str(m__io->read_bytes(42), "UTF-8"); 131 | } 132 | 133 | wem_t::fmt_chunk_t::~fmt_chunk_t() { 134 | _clean_up(); 135 | } 136 | 137 | void wem_t::fmt_chunk_t::_clean_up() { 138 | } 139 | 140 | wem_t::junk_chunk_t::junk_chunk_t(kaitai::kstream* p__io, wem_t::chunk_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 141 | m__parent = p__parent; 142 | m__root = p__root; 143 | 144 | try { 145 | _read(); 146 | } catch(...) { 147 | _clean_up(); 148 | throw; 149 | } 150 | } 151 | 152 | void wem_t::junk_chunk_t::_read() { 153 | m_junk = kaitai::kstream::bytes_to_str(m__io->read_bytes(26), "UTF-8"); 154 | } 155 | 156 | wem_t::junk_chunk_t::~junk_chunk_t() { 157 | _clean_up(); 158 | } 159 | 160 | void wem_t::junk_chunk_t::_clean_up() { 161 | } 162 | 163 | wem_t::list_labl_subchunk_t::list_labl_subchunk_t(kaitai::kstream* p__io, wem_t::list_chunk_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 164 | m__parent = p__parent; 165 | m__root = p__root; 166 | 167 | try { 168 | _read(); 169 | } catch(...) { 170 | _clean_up(); 171 | throw; 172 | } 173 | } 174 | 175 | void wem_t::list_labl_subchunk_t::_read() { 176 | m_magic = m__io->read_bytes(4); 177 | if (!(magic() == std::string("\x6C\x61\x62\x6C", 4))) { 178 | throw kaitai::validation_not_equal_error(std::string("\x6C\x61\x62\x6C", 4), magic(), _io(), std::string("/types/list_labl_subchunk/seq/0")); 179 | } 180 | m_size = m__io->read_u4le(); 181 | m_cue_point_id = m__io->read_u4le(); 182 | m_data = kaitai::kstream::bytes_to_str(m__io->read_bytes((size() - 4)), "UTF-8"); 183 | } 184 | 185 | wem_t::list_labl_subchunk_t::~list_labl_subchunk_t() { 186 | _clean_up(); 187 | } 188 | 189 | void wem_t::list_labl_subchunk_t::_clean_up() { 190 | } 191 | 192 | wem_t::riff_header_t::riff_header_t(kaitai::kstream* p__io, wem_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 193 | m__parent = p__parent; 194 | m__root = p__root; 195 | 196 | try { 197 | _read(); 198 | } catch(...) { 199 | _clean_up(); 200 | throw; 201 | } 202 | } 203 | 204 | void wem_t::riff_header_t::_read() { 205 | m_magic = m__io->read_bytes(4); 206 | if (!(magic() == std::string("\x52\x49\x46\x46", 4))) { 207 | throw kaitai::validation_not_equal_error(std::string("\x52\x49\x46\x46", 4), magic(), _io(), std::string("/types/riff_header/seq/0")); 208 | } 209 | m_chunk_size = m__io->read_u4le(); 210 | m_wav_id = m__io->read_u4le(); 211 | } 212 | 213 | wem_t::riff_header_t::~riff_header_t() { 214 | _clean_up(); 215 | } 216 | 217 | void wem_t::riff_header_t::_clean_up() { 218 | } 219 | 220 | wem_t::list_chunk_t::list_chunk_t(uint32_t p_size, kaitai::kstream* p__io, wem_t::chunk_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 221 | m__parent = p__parent; 222 | m__root = p__root; 223 | m_size = p_size; 224 | m_labl = 0; 225 | 226 | try { 227 | _read(); 228 | } catch(...) { 229 | _clean_up(); 230 | throw; 231 | } 232 | } 233 | 234 | void wem_t::list_chunk_t::_read() { 235 | m_adtl = m__io->read_bytes(4); 236 | if (!(adtl() == std::string("\x61\x64\x74\x6C", 4))) { 237 | throw kaitai::validation_not_equal_error(std::string("\x61\x64\x74\x6C", 4), adtl(), _io(), std::string("/types/list_chunk/seq/0")); 238 | } 239 | m_labl = new list_labl_subchunk_t(m__io, this, m__root); 240 | } 241 | 242 | wem_t::list_chunk_t::~list_chunk_t() { 243 | _clean_up(); 244 | } 245 | 246 | void wem_t::list_chunk_t::_clean_up() { 247 | if (m_labl) { 248 | delete m_labl; m_labl = 0; 249 | } 250 | } 251 | 252 | wem_t::data_chunk_t::data_chunk_t(uint32_t p_size, kaitai::kstream* p__io, wem_t::chunk_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 253 | m__parent = p__parent; 254 | m__root = p__root; 255 | m_size = p_size; 256 | 257 | try { 258 | _read(); 259 | } catch(...) { 260 | _clean_up(); 261 | throw; 262 | } 263 | } 264 | 265 | void wem_t::data_chunk_t::_read() { 266 | m_data = kaitai::kstream::bytes_to_str(m__io->read_bytes(size()), "UTF-8"); 267 | } 268 | 269 | wem_t::data_chunk_t::~data_chunk_t() { 270 | _clean_up(); 271 | } 272 | 273 | void wem_t::data_chunk_t::_clean_up() { 274 | } 275 | 276 | wem_t::cue_chunk_t::cue_chunk_t(kaitai::kstream* p__io, wem_t::chunk_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 277 | m__parent = p__parent; 278 | m__root = p__root; 279 | m_cue_points = 0; 280 | 281 | try { 282 | _read(); 283 | } catch(...) { 284 | _clean_up(); 285 | throw; 286 | } 287 | } 288 | 289 | void wem_t::cue_chunk_t::_read() { 290 | m_cue_point_count = m__io->read_u4le(); 291 | m_cue_points = new std::vector(); 292 | const int l_cue_points = cue_point_count(); 293 | for (int i = 0; i < l_cue_points; i++) { 294 | m_cue_points->push_back(new cue_point_subchunk_t(m__io, this, m__root)); 295 | } 296 | } 297 | 298 | wem_t::cue_chunk_t::~cue_chunk_t() { 299 | _clean_up(); 300 | } 301 | 302 | void wem_t::cue_chunk_t::_clean_up() { 303 | if (m_cue_points) { 304 | for (std::vector::iterator it = m_cue_points->begin(); it != m_cue_points->end(); ++it) { 305 | delete *it; 306 | } 307 | delete m_cue_points; m_cue_points = 0; 308 | } 309 | } 310 | 311 | wem_t::riff_chunk_t::riff_chunk_t(kaitai::kstream* p__io, wem_t::chunk_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 312 | m__parent = p__parent; 313 | m__root = p__root; 314 | 315 | try { 316 | _read(); 317 | } catch(...) { 318 | _clean_up(); 319 | throw; 320 | } 321 | } 322 | 323 | void wem_t::riff_chunk_t::_read() { 324 | m_dummy = m__io->read_bytes(kaitai::kstream::mod(_io()->pos(), 2)); 325 | m_type = m__io->read_bytes(4); 326 | m_size = m__io->read_u4le(); 327 | } 328 | 329 | wem_t::riff_chunk_t::~riff_chunk_t() { 330 | _clean_up(); 331 | } 332 | 333 | void wem_t::riff_chunk_t::_clean_up() { 334 | } 335 | 336 | wem_t::cue_point_subchunk_t::cue_point_subchunk_t(kaitai::kstream* p__io, wem_t::cue_chunk_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 337 | m__parent = p__parent; 338 | m__root = p__root; 339 | 340 | try { 341 | _read(); 342 | } catch(...) { 343 | _clean_up(); 344 | throw; 345 | } 346 | } 347 | 348 | void wem_t::cue_point_subchunk_t::_read() { 349 | m_id = m__io->read_u4le(); 350 | m_position = m__io->read_u4le(); 351 | m_data_chunk_id = m__io->read_u4le(); 352 | m_chunk_start = m__io->read_u4le(); 353 | m_block_start = m__io->read_u4le(); 354 | m_sample_start = m__io->read_u4le(); 355 | } 356 | 357 | wem_t::cue_point_subchunk_t::~cue_point_subchunk_t() { 358 | _clean_up(); 359 | } 360 | 361 | void wem_t::cue_point_subchunk_t::_clean_up() { 362 | } 363 | 364 | wem_t::random_blank_bytes_t::random_blank_bytes_t(kaitai::kstream* p__io, wem_t::chunk_t* p__parent, wem_t* p__root) : kaitai::kstruct(p__io) { 365 | m__parent = p__parent; 366 | m__root = p__root; 367 | m_dummy = 0; 368 | 369 | try { 370 | _read(); 371 | } catch(...) { 372 | _clean_up(); 373 | throw; 374 | } 375 | } 376 | 377 | void wem_t::random_blank_bytes_t::_read() { 378 | m_dummy = new std::vector(); 379 | { 380 | int i = 0; 381 | uint8_t _; 382 | do { 383 | _ = m__io->read_u1(); 384 | m_dummy->push_back(_); 385 | i++; 386 | } while (!( ((_ != 0) || (_io()->is_eof())) )); 387 | } 388 | } 389 | 390 | wem_t::random_blank_bytes_t::~random_blank_bytes_t() { 391 | _clean_up(); 392 | } 393 | 394 | void wem_t::random_blank_bytes_t::_clean_up() { 395 | if (m_dummy) { 396 | delete m_dummy; m_dummy = 0; 397 | } 398 | } 399 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "kaitai/kaitaistream.h" 10 | #include "kaitai/structs/bnk.h" 11 | #include "kaitai/structs/w3sc.h" 12 | #include "revorb/revorb.hpp" 13 | #include "util/rang.hpp" 14 | #include "ww2ogg/ww2ogg.h" 15 | #include "ww2ogg/wwriff.h" 16 | #include "wwtools/bnk.hpp" 17 | #include "wwtools/util/write.hpp" 18 | #include "wwtools/w3sc.hpp" 19 | #include "wwtools/wwtools.hpp" 20 | 21 | namespace fs = std::filesystem; 22 | bool convert(const std::string &indata, const std::string &outpath) { 23 | std::string outdata = wwtools::wem_to_ogg(indata); 24 | if (outdata.empty()) { 25 | return false; 26 | } 27 | 28 | std::ofstream fout(outpath, std::ios::binary); 29 | fout << outdata; 30 | return true; 31 | } 32 | 33 | void print_help(const std::string &extra_message = "", 34 | const std::string &filename = "wwtools") { 35 | if (!extra_message.empty()) { 36 | std::cout << rang::fg::red << extra_message << rang::fg::reset << std::endl 37 | << std::endl; 38 | } 39 | std::cout 40 | << "Please use the command in one of the following ways:\n" 41 | << " " << filename << " wem [input.wem] (--info)\n" 42 | << " " << filename 43 | << " bnk [event|extract] (input.bnk) (event ID) (--info) (--no-convert)\n" 44 | << " " << filename 45 | << " cache [read|write] [file/directory name] (--info) " 46 | "(--no-convert-wem) (--no-convert-bnk)\n" 47 | << "Or run it without arguments to find and convert all WEMs in " 48 | "the current directory." 49 | << std::endl; 50 | } 51 | 52 | std::pair, bool> get_flags(int argc, char *argv[]) { 53 | std::vector flags; 54 | flags.reserve(argc); 55 | bool flag_found = false; 56 | for (int i = 0; i < argc; i++) { 57 | std::string arg(argv[i]); 58 | // TODO: Change to starts_with with C++20 59 | if (arg.rfind("--", 0) == 0 && arg.length() > 2) { 60 | flag_found = true; 61 | flags.push_back(arg.substr(2, std::string::npos)); 62 | } else { 63 | // If current arg is not a flag but comes after a flag... 64 | if (flag_found) { 65 | print_help("Please place all flags after other args!", argv[0]); 66 | return {{}, true}; 67 | } 68 | } 69 | } 70 | return {flags, false}; 71 | } 72 | 73 | bool has_flag(const std::vector &flags, 74 | const std::string &wanted_flag) { 75 | for (auto flag : flags) { 76 | if (flag == wanted_flag) { 77 | return true; 78 | } 79 | } 80 | return false; 81 | } 82 | 83 | int main(int argc, char *argv[]) { 84 | // TODO: Add more descriptive comments regarding as much as possible 85 | std::pair, bool> flags_raw = get_flags(argc, argv); 86 | if (flags_raw.second) { // If there's an error... 87 | return 1; 88 | } 89 | std::vector flags = flags_raw.first; 90 | if (has_flag(flags, "help")) { 91 | print_help(); 92 | return 0; 93 | } 94 | if (argc < 2) { 95 | // If there is no input file, convert all WEM files in the current directory 96 | bool wemExists = false; 97 | for (const auto &file : fs::directory_iterator(fs::current_path())) { 98 | if (file.path().extension() == ".wem") { 99 | wemExists = true; 100 | std::cout << "Converting " << file.path() << "..." << std::endl; 101 | 102 | std::ifstream filein(file.path().string(), std::ios::binary); 103 | std::stringstream indata; 104 | indata << filein.rdbuf(); 105 | 106 | // TODO: Add function for changing extension 107 | std::string outpath = file.path().string().substr( 108 | 0, file.path().string().find_last_of(".")) + 109 | ".ogg"; 110 | 111 | auto success = convert(indata.str(), outpath); 112 | if (!success) { 113 | std::cerr << "Failed to convert " << file.path() << std::endl; 114 | return 1; 115 | } 116 | } 117 | } 118 | if (!wemExists) { 119 | print_help("No WEM files found in the current directory!", argv[0]); 120 | } 121 | } else { 122 | if (argc < 3) { 123 | print_help("Missing arguments!", argv[0]); 124 | return 1; 125 | } else { 126 | #pragma region WEM 127 | if (strcmp(argv[1], "wem") == 0) { 128 | auto path = std::string(argv[2]); 129 | 130 | std::ifstream filein(path, std::ios::binary); 131 | std::stringstream indata; 132 | indata << filein.rdbuf(); 133 | if (has_flag(flags, "info")) { 134 | std::cout << ww2ogg::wem_info(indata.str()); 135 | return 0; 136 | } 137 | std::string outpath = path.substr(0, path.find_last_of(".")) + ".ogg"; 138 | 139 | std::cout << "Extracting " << outpath << "..." << std::endl; 140 | auto success = convert(indata.str(), outpath); 141 | if (!success) { 142 | std::cerr << "Failed to convert " << path << std::endl; 143 | return 1; 144 | } 145 | #pragma endregion WEM 146 | #pragma region BNK 147 | } else if (strcmp(argv[1], "bnk") == 0) { 148 | auto path = std::string(argv[3]); 149 | 150 | std::ifstream filein(path, std::ios::binary); 151 | std::stringstream indata; 152 | indata << filein.rdbuf(); 153 | 154 | if (has_flag(flags, "info")) { 155 | std::cout << wwtools::bnk::get_info(indata.str()); 156 | return EXIT_SUCCESS; 157 | } 158 | 159 | if (argc < 4) { 160 | print_help("You must specify whether to extract or find an event as " 161 | "well as the input!", 162 | argv[0]); 163 | return EXIT_FAILURE; 164 | } 165 | 166 | if (strcmp(argv[2], "event") != 0 && strcmp(argv[2], "extract") != 0) { 167 | print_help("Incorrect value for read or write!", argv[0]); 168 | return EXIT_FAILURE; 169 | } 170 | 171 | auto bnk_path = std::string(argv[3]); 172 | 173 | if (strcmp(argv[2], "event") == 0) { 174 | if (argc < 4) { 175 | print_help("Not enough arguments for finding an event!"); 176 | return EXIT_FAILURE; 177 | } 178 | 179 | std::string in_event_id; 180 | if (argc >= 5) 181 | in_event_id = argv[4]; 182 | 183 | std::ifstream bnk_in(bnk_path, std::ios::binary); 184 | std::stringstream indata; 185 | indata << bnk_in.rdbuf(); 186 | 187 | std::cout << wwtools::bnk::get_event_id_info(indata.str(), 188 | in_event_id); 189 | } else if (strcmp(argv[2], "extract") == 0) { 190 | std::vector wems; 191 | // populate WEMs vector with data 192 | wwtools::bnk::extract(indata.str(), wems); 193 | kaitai::kstream ks(indata.str()); 194 | bnk_t bnk(&ks); 195 | // create directory with name of bnk file, no extension 196 | fs::create_directory(path.substr(0, path.find_last_of("."))); 197 | int idx = 0; 198 | for (auto wem : wems) { 199 | fs::path outdir(path.substr(0, path.find_last_of("."))); 200 | std::ifstream bnk_in(bnk_path, std::ios::binary); 201 | std::stringstream indata; 202 | indata << bnk_in.rdbuf(); 203 | bool noconvert = has_flag(flags, "no-convert"); 204 | // TODO: maybe make a function to return an array of IDs at index 205 | // instead of parsing the file every loop 206 | fs::path filename( 207 | wwtools::bnk::get_wem_id_at_index(indata.str(), idx)); 208 | fs::path outpath = outdir / filename; 209 | std::string file_extension = noconvert ? ".wem" : ".ogg"; 210 | std::cout << rang::fg::cyan << "[" << idx + 1 << "/" << wems.size() 211 | << "] " << rang::fg::reset << "Extracting " 212 | << outpath.string() + file_extension << "..." 213 | << std::endl; 214 | if (noconvert) { 215 | std::ofstream of(outpath.string() + file_extension, std::ios::binary); 216 | of << wem; 217 | of.close(); 218 | idx++; 219 | continue; 220 | } 221 | auto success = convert(wem, outpath.string() + file_extension); 222 | if (!success) { 223 | std::cout << "Failed to convert " 224 | << outpath.string() + file_extension << std::endl; 225 | // Don't return error because others may succeed 226 | } 227 | idx++; 228 | } 229 | } 230 | #pragma endregion BNK 231 | #pragma region CACHE 232 | } else if (strcmp(argv[1], "cache") == 0) { 233 | if (argc < 4) { 234 | print_help( 235 | "You must specify whether to read or write as well as the input!", 236 | argv[0]); 237 | return EXIT_FAILURE; 238 | } 239 | 240 | if (strcmp(argv[2], "read") != 0 && strcmp(argv[2], "write") != 0) { 241 | print_help("Incorrect value for read or write!", argv[0]); 242 | return EXIT_FAILURE; 243 | } 244 | 245 | auto path = std::string(argv[3]); 246 | 247 | if (strcmp(argv[2], "read") == 0) { 248 | std::ifstream filein(path, std::ios::binary); 249 | std::stringstream indata; 250 | indata << filein.rdbuf(); 251 | if (has_flag(flags, "info")) { 252 | std::cout << wwtools::w3sc::get_info(indata.str()); 253 | return 0; 254 | } 255 | 256 | kaitai::kstream ks(indata.str()); 257 | 258 | w3sc_t cache(&ks); 259 | 260 | int file_index = 0; 261 | int file_count = cache.file_infos()->size(); 262 | for (auto file : *cache.file_infos()) { 263 | file_index++; 264 | if (file->name().substr(file->name().find_last_of(".")) == ".bnk") { 265 | std::cout << rang::fg::cyan << "[" << file_index << "/" 266 | << file_count << "] " << rang::fg::reset 267 | << "Extracting " << file->name() << "..." << std::endl; 268 | // Currently unable to read music files 269 | if (file->name().find("music_") != std::string::npos || 270 | file->name().find("vo_") != std::string::npos || 271 | file->name().find("qu_") != std::string::npos || 272 | file->name().find("mutations_") != std::string::npos) { 273 | continue; 274 | } 275 | 276 | kaitai::kstream bnk_ks(file->data()); 277 | bnk_t bnk(&bnk_ks); 278 | std::vector wems; 279 | // Populate WEMs vector with data 280 | wwtools::bnk::extract(file->data(), wems); 281 | // Create directory with name of bnk file, no extension 282 | fs::create_directory( 283 | file->name().substr(0, file->name().find_last_of("."))); 284 | int idx = 0; 285 | for (auto wem : wems) { 286 | fs::path outdir( 287 | file->name().substr(0, file->name().find_last_of("."))); 288 | std::stringstream ss; 289 | // ss << bnk.data_index()->data()->indices()->at(idx)->id(); 290 | bool noconvert = has_flag(flags, "no-convert-bnk"); 291 | fs::path filename(ss.str()); 292 | fs::path outpath = outdir / filename; 293 | std::string file_extension = noconvert ? ".wem" : ".ogg"; 294 | if (noconvert) { 295 | std::ofstream of(outpath.string() + file_extension, std::ios::binary); 296 | of << wem; 297 | of.close(); 298 | idx++; 299 | continue; 300 | } 301 | auto success = convert(wem, outpath.string() + file_extension); 302 | if (!success) { 303 | std::cout 304 | << rang::fg::yellow << "Failed to convert " 305 | << outpath.string() + file_extension 306 | << " (Likely a metadata-only file. This WEM will likely " 307 | "be found in the cache itself and not the soundbank.)" 308 | << rang::fg::reset << std::endl; 309 | // Don't return error because the rest may succeed 310 | } 311 | idx++; 312 | } 313 | } else if (file->name().substr(file->name().find_last_of(".")) == 314 | ".wem") { 315 | bool noconvert = has_flag(flags, "no-convert-wem"); 316 | if (noconvert) { 317 | std::cout << rang::fg::cyan << "[" << file_index << "/" 318 | << file_count << "] " << rang::fg::reset 319 | << "Extracting " << file->name() << "..." 320 | << rang::fg::reset << std::endl; 321 | std::ofstream fout(file->name(), std::ios::binary); 322 | fout << file->data(); 323 | continue; 324 | } 325 | std::string outpath = 326 | file->name().substr(0, file->name().find_last_of(".")) + 327 | ".ogg"; 328 | std::cout << rang::fg::cyan << "[" << file_index << "/" 329 | << file_count << "] " << rang::fg::reset 330 | << "Extracting " 331 | << file->name().substr(0, 332 | file->name().find_last_of(".")) + 333 | ".ogg" 334 | << rang::fg::reset << std::endl; 335 | auto success = convert(file->data(), outpath); 336 | if (!success) { 337 | // TODO: Add more rang usage 338 | std::cout << "Failed to convert " << path << std::endl; 339 | } 340 | } 341 | } 342 | } else { // write cache file 343 | std::ofstream of("soundspc.cache", std::ios::binary); 344 | std::vector> v; 345 | for (const auto &file : fs::directory_iterator(path)) { 346 | std::ifstream ifs(file.path().string(), std::ios::binary); 347 | std::stringstream ss; 348 | ss << ifs.rdbuf(); 349 | v.push_back({file.path().filename().string(), ss.str()}); 350 | } 351 | wwtools::w3sc::create(v, of); 352 | } 353 | #pragma endregion CACHE 354 | } else { 355 | print_help(argv[0]); 356 | return 1; 357 | } 358 | } 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /include/kaitai/kaitaistream.h: -------------------------------------------------------------------------------- 1 | #ifndef KAITAI_STREAM_H 2 | #define KAITAI_STREAM_H 3 | 4 | // Kaitai Struct runtime API version: x.y.z = 'xxxyyyzzz' decimal 5 | #define KAITAI_STRUCT_VERSION 11000L 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace kaitai { 16 | 17 | /** 18 | * Kaitai Stream class (kaitai::kstream) is an implementation of 19 | * Kaitai Struct stream API 20 | * for C++/STL. It's implemented as a wrapper over generic STL std::istream. 21 | * 22 | * It provides a wide variety of simple methods to read (parse) binary 23 | * representations of primitive types, such as integer and floating 24 | * point numbers, byte arrays and strings, and also provides stream 25 | * positioning / navigation methods with unified cross-language and 26 | * cross-toolkit semantics. 27 | * 28 | * Typically, end users won't access Kaitai Stream class manually, but would 29 | * describe a binary structure format using .ksy language and then would use 30 | * Kaitai Struct compiler to generate source code in desired target language. 31 | * That code, in turn, would use this class and API to do the actual parsing 32 | * job. 33 | */ 34 | class kstream { 35 | public: 36 | /** 37 | * Constructs new Kaitai Stream object, wrapping a given std::istream. 38 | * \param io istream object to use for this Kaitai Stream 39 | */ 40 | kstream(std::istream* io); 41 | 42 | /** 43 | * Constructs new Kaitai Stream object, wrapping a given in-memory data 44 | * buffer. 45 | * \param data data buffer to use for this Kaitai Stream 46 | */ 47 | kstream(const std::string& data); 48 | 49 | void close(); 50 | 51 | /** @name Stream positioning */ 52 | //@{ 53 | /** 54 | * Check if stream pointer is at the end of stream. Note that the semantics 55 | * are different from traditional STL semantics: one does *not* need to do a 56 | * read (which will fail) after the actual end of the stream to trigger EOF 57 | * flag, which can be accessed after that read. It is sufficient to just be 58 | * at the end of the stream for this method to return true. 59 | * \return "true" if we are located at the end of the stream. 60 | */ 61 | bool is_eof() const; 62 | 63 | /** 64 | * Set stream pointer to designated position. 65 | * \param pos new position (offset in bytes from the beginning of the stream) 66 | */ 67 | void seek(uint64_t pos); 68 | 69 | /** 70 | * Get current position of a stream pointer. 71 | * \return pointer position, number of bytes from the beginning of the stream 72 | */ 73 | uint64_t pos(); 74 | 75 | /** 76 | * Get total size of the stream in bytes. 77 | * \return size of the stream in bytes 78 | */ 79 | uint64_t size(); 80 | //@} 81 | 82 | /** @name Integer numbers */ 83 | //@{ 84 | 85 | // ------------------------------------------------------------------------ 86 | // Signed 87 | // ------------------------------------------------------------------------ 88 | 89 | int8_t read_s1(); 90 | 91 | // ........................................................................ 92 | // Big-endian 93 | // ........................................................................ 94 | 95 | int16_t read_s2be(); 96 | int32_t read_s4be(); 97 | int64_t read_s8be(); 98 | 99 | // ........................................................................ 100 | // Little-endian 101 | // ........................................................................ 102 | 103 | int16_t read_s2le(); 104 | int32_t read_s4le(); 105 | int64_t read_s8le(); 106 | 107 | // ------------------------------------------------------------------------ 108 | // Unsigned 109 | // ------------------------------------------------------------------------ 110 | 111 | uint8_t read_u1(); 112 | 113 | // ........................................................................ 114 | // Big-endian 115 | // ........................................................................ 116 | 117 | uint16_t read_u2be(); 118 | uint32_t read_u4be(); 119 | uint64_t read_u8be(); 120 | 121 | // ........................................................................ 122 | // Little-endian 123 | // ........................................................................ 124 | 125 | uint16_t read_u2le(); 126 | uint32_t read_u4le(); 127 | uint64_t read_u8le(); 128 | 129 | //@} 130 | 131 | /** @name Floating point numbers */ 132 | //@{ 133 | 134 | // ........................................................................ 135 | // Big-endian 136 | // ........................................................................ 137 | 138 | float read_f4be(); 139 | double read_f8be(); 140 | 141 | // ........................................................................ 142 | // Little-endian 143 | // ........................................................................ 144 | 145 | float read_f4le(); 146 | double read_f8le(); 147 | 148 | //@} 149 | 150 | /** @name Unaligned bit values */ 151 | //@{ 152 | 153 | void align_to_byte(); 154 | uint64_t read_bits_int_be(int n); 155 | uint64_t read_bits_int(int n); 156 | uint64_t read_bits_int_le(int n); 157 | 158 | //@} 159 | 160 | /** @name Byte arrays */ 161 | //@{ 162 | 163 | std::string read_bytes(std::streamsize len); 164 | std::string read_bytes_full(); 165 | std::string read_bytes_term(char term, bool include, bool consume, bool eos_error); 166 | std::string ensure_fixed_contents(std::string expected); 167 | 168 | static std::string bytes_strip_right(std::string src, char pad_byte); 169 | static std::string bytes_terminate(std::string src, char term, bool include); 170 | static std::string bytes_to_str(const std::string src, const char *src_enc); 171 | 172 | //@} 173 | 174 | /** @name Byte array processing */ 175 | //@{ 176 | 177 | /** 178 | * Performs a XOR processing with given data, XORing every byte of input with a single 179 | * given value. 180 | * @param data data to process 181 | * @param key value to XOR with 182 | * @return processed data 183 | */ 184 | static std::string process_xor_one(std::string data, uint8_t key); 185 | 186 | /** 187 | * Performs a XOR processing with given data, XORing every byte of input with a key 188 | * array, repeating key array many times, if necessary (i.e. if data array is longer 189 | * than key array). 190 | * @param data data to process 191 | * @param key array of bytes to XOR with 192 | * @return processed data 193 | */ 194 | static std::string process_xor_many(std::string data, std::string key); 195 | 196 | /** 197 | * Performs a circular left rotation shift for a given buffer by a given amount of bits, 198 | * using groups of 1 bytes each time. Right circular rotation should be performed 199 | * using this procedure with corrected amount. 200 | * @param data source data to process 201 | * @param amount number of bits to shift by 202 | * @return copy of source array with requested shift applied 203 | */ 204 | static std::string process_rotate_left(std::string data, int amount); 205 | 206 | #ifdef KS_ZLIB 207 | /** 208 | * Performs an unpacking ("inflation") of zlib-compressed data with usual zlib headers. 209 | * @param data data to unpack 210 | * @return unpacked data 211 | * @throws IOException 212 | */ 213 | static std::string process_zlib(std::string data); 214 | #endif 215 | 216 | //@} 217 | 218 | /** 219 | * Performs modulo operation between two integers: dividend `a` 220 | * and divisor `b`. Divisor `b` is expected to be positive. The 221 | * result is always 0 <= x <= b - 1. 222 | */ 223 | static int mod(int a, int b); 224 | 225 | /** 226 | * Converts given integer `val` to a decimal string representation. 227 | * Should be used in place of std::to_string() (which is available only 228 | * since C++11) in older C++ implementations. 229 | */ 230 | template 231 | // check for C++11 support - https://stackoverflow.com/a/40512515 232 | #if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1900) 233 | // https://stackoverflow.com/a/27913885 234 | typename std::enable_if< 235 | std::is_integral::value && 236 | // check if we don't have something too large like GCC's `__int128_t` 237 | std::numeric_limits::max() >= 0 && 238 | std::numeric_limits::max() <= std::numeric_limits::max(), 239 | std::string 240 | >::type 241 | #else 242 | std::string 243 | #endif 244 | static to_string(I val) { 245 | // in theory, `digits10 + 3` would be enough (minus sign + leading digit 246 | // + null terminator), but let's add a little more to be safe 247 | char buf[std::numeric_limits::digits10 + 5]; 248 | if (val < 0) { 249 | buf[0] = '-'; 250 | 251 | // NB: `val` is negative and we need to get its absolute value (i.e. minus `val`). However, since 252 | // `int64_t` uses two's complement representation, its range is `[-2**63, 2**63 - 1] = 253 | // [-0x8000_0000_0000_0000, 0x7fff_ffff_ffff_ffff]` (both ends inclusive) and thus the naive 254 | // `-val` operation will overflow for `val = std::numeric_limits::min() = 255 | // -0x8000_0000_0000_0000` (because the result of `-val` is mathematically 256 | // `-(-0x8000_0000_0000_0000) = 0x8000_0000_0000_0000`, but the `int64_t` type can represent at 257 | // most `0x7fff_ffff_ffff_ffff`). And signed integer overflow is undefined behavior in C++. 258 | // 259 | // To avoid undefined behavior for `val = -0x8000_0000_0000_0000 = -2**63`, we do the following 260 | // steps for all negative `val`s: 261 | // 262 | // 1. Convert the signed (and negative) `val` to an unsigned `uint64_t` type. This is a 263 | // well-defined operation in C++: the resulting `uint64_t` value will be `val mod 2**64` (`mod` 264 | // is modulo). The maximum `val` we can have here is `-1` (because `val < 0`), a theoretical 265 | // minimum we are able to support would be `-2**64 + 1 = -0xffff_ffff_ffff_ffff` (even though 266 | // in practice the widest standard type is `int64_t` with the minimum of `-2**63`): 267 | // 268 | // * `static_cast(-1) = -1 mod 2**64 = 2**64 + (-1) = 0xffff_ffff_ffff_ffff = 2**64 - 1` 269 | // * `static_cast(-2**64 + 1) = (-2**64 + 1) mod 2**64 = 2**64 + (-2**64 + 1) = 1` 270 | // 271 | // 2. Subtract `static_cast(val)` from `2**64 - 1 = 0xffff_ffff_ffff_ffff`. Since 272 | // `static_cast(val)` is in range `[1, 2**64 - 1]` (see step 1), the result of this 273 | // subtraction will be mathematically in range `[0, (2**64 - 1) - 1] = [0, 2**64 - 2]`. So the 274 | // mathematical result cannot be negative, hence this unsigned integer subtraction can never 275 | // wrap around (which wouldn't be a good thing to rely upon because it confuses programmers and 276 | // code analysis tools). 277 | // 278 | // 3. Since we did mathematically `(2**64 - 1) - (2**64 + val) = -val - 1` so far (and we wanted 279 | // to do `-val`), we add `1` to correct that. From step 2 we know that the result of `-val - 1` 280 | // is in range `[0, 2**64 - 2]`, so adding `1` will not wrap (at most we could get `2**64 - 1 = 281 | // 0xffff_ffff_ffff_ffff`, which is still in the valid range of `uint64_t`). 282 | 283 | unsigned_to_decimal((std::numeric_limits::max() - static_cast(val)) + 1, &buf[1]); 284 | } else { 285 | unsigned_to_decimal(val, buf); 286 | } 287 | return std::string(buf); 288 | } 289 | 290 | /** 291 | * Converts string `str` to an integer value. Throws an exception if the 292 | * string is not a valid integer. 293 | * 294 | * This one is supposed to mirror `std::stoll()` (which is available only 295 | * since C++11) in older C++ implementations. 296 | * 297 | * Major difference between standard `std::stoll()` and `string_to_int()` 298 | * is that this one does not perform any partial conversions and always 299 | * throws `std::invalid_argument` if the string is not a valid integer. 300 | * 301 | * @param str String to convert 302 | * @param base Base of the integer (default: 10) 303 | * @throws std::invalid_argument if the string is not a valid integer 304 | * @throws std::out_of_range if the integer is out of range 305 | * @return Integer value of the string 306 | */ 307 | static int64_t string_to_int(const std::string& str, int base = 10); 308 | 309 | /** 310 | * Reverses given string `val`, so that the first character becomes the 311 | * last and the last one becomes the first. This should be used to avoid 312 | * the need of local variables at the caller. 313 | */ 314 | static std::string reverse(std::string val); 315 | 316 | /** 317 | * Finds the minimal byte in a byte array, treating bytes as 318 | * unsigned values. 319 | * @param val byte array to scan 320 | * @return minimal byte in byte array as integer 321 | */ 322 | static uint8_t byte_array_min(const std::string val); 323 | 324 | /** 325 | * Finds the maximal byte in a byte array, treating bytes as 326 | * unsigned values. 327 | * @param val byte array to scan 328 | * @return maximal byte in byte array as integer 329 | */ 330 | static uint8_t byte_array_max(const std::string val); 331 | 332 | private: 333 | std::istream* m_io; 334 | std::istringstream m_io_str; 335 | int m_bits_left; 336 | uint64_t m_bits; 337 | 338 | void init(); 339 | void exceptions_enable() const; 340 | 341 | static void unsigned_to_decimal(uint64_t number, char *buffer); 342 | 343 | #ifdef KS_STR_ENCODING_WIN32API 344 | enum { 345 | KAITAI_CP_UNSUPPORTED = -1, 346 | KAITAI_CP_UTF16LE = -2, 347 | KAITAI_CP_UTF16BE = -3, 348 | }; 349 | 350 | /** 351 | * Converts string name of the encoding into a Windows codepage number. We extend standard Windows codepage list 352 | * with a few special meanings (see KAITAI_CP_* enum), reserving negative values of integer for that. 353 | * @param src_enc string name of the encoding; this should match canonical name of the encoding as per discussion 354 | * in https://github.com/kaitai-io/kaitai_struct/issues/116 355 | * @return Windows codepage number or member of KAITAI_CP_* enum. 356 | * @ref https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers 357 | */ 358 | static int encoding_to_win_codepage(const char *src_enc); 359 | 360 | /** 361 | * Converts bytes packed in std::string into a UTF-8 string, based on given source encoding indicated by `codepage`. 362 | * @param src bytes to be converted 363 | * @param codepage Windows codepage number or member of KAITAI_CP_* enum. 364 | * @return UTF-8 string 365 | */ 366 | static std::string bytes_to_str(const std::string src, int codepage); 367 | #endif 368 | 369 | static const int ZLIB_BUF_SIZE = 128 * 1024; 370 | }; 371 | 372 | } 373 | 374 | #endif 375 | -------------------------------------------------------------------------------- /ksy/bnk.ksy: -------------------------------------------------------------------------------- 1 | meta: 2 | id: bnk 3 | file-extension: bnk 4 | endian: le 5 | imports: 6 | - /home/abheekd/dev/wwise-audio-tools/ksy/vlq # https://formats.kaitai.io/vlq_base128_le/ 7 | 8 | seq: 9 | - id: data 10 | type: section 11 | repeat: eos 12 | 13 | # BNK 14 | types: 15 | section: 16 | seq: 17 | - id: type 18 | type: str 19 | size: 4 20 | encoding: utf-8 21 | - id: length 22 | type: u4 23 | - id: section_data 24 | type: 25 | switch-on: type 26 | cases: 27 | '"BKHD"': bkhd_data(length) 28 | '"DIDX"': didx_data(length) 29 | '"HIRC"': hirc_data(length) 30 | '"STID"': stid_data 31 | '"DATA"': data_data(length) 32 | _: rand(length) 33 | 34 | rand: 35 | params: 36 | - id: length 37 | type: u4 38 | seq: 39 | - id: data 40 | size: length 41 | 42 | # BKHD BEGIN 43 | bkhd_data: 44 | params: 45 | - id: length 46 | type: u4 47 | seq: 48 | - id: version 49 | type: u4 50 | - id: id 51 | type: u4 52 | #- id: blank1 53 | # contents: [00, 00, 00, 00] 54 | #- id: blank2 55 | # contents: [00, 00, 00, 00] 56 | #- id: random 57 | # size: length - 16 58 | - id: random 59 | size: length - 8 60 | # BKHD END 61 | 62 | # DIDX BEGIN 63 | didx_data: 64 | params: 65 | - id: length 66 | type: u4 67 | seq: 68 | - id: objs 69 | type: didx_obj 70 | repeat: expr 71 | repeat-expr: num_files 72 | instances: 73 | num_files: 74 | value: length / 12 75 | didx_obj: 76 | seq: 77 | - id: id 78 | type: u4 79 | - id: offset 80 | type: u4 81 | - id: length 82 | type: u4 83 | # DIDX END 84 | 85 | # DATA BEGIN 86 | data_data: 87 | params: 88 | - id: length 89 | type: u4 90 | seq: 91 | - id: data_obj_section 92 | type: data_obj_section(length) 93 | size: length 94 | instances: 95 | didx_data: 96 | value: _parent._parent.data[1].section_data.as 97 | data_obj_section: 98 | params: 99 | - id: length 100 | type: u4 101 | seq: 102 | - id: data 103 | type: data_obj(_parent.didx_data.objs[_index].offset, _parent.didx_data.objs[_index].length) 104 | repeat: expr 105 | repeat-expr: _parent.didx_data.num_files 106 | data_obj: 107 | params: 108 | - id: offset 109 | type: u4 110 | - id: length 111 | type: u4 112 | instances: 113 | file: 114 | pos: offset 115 | size: length 116 | # DATA END 117 | 118 | # STID BEGIN 119 | stid_data: 120 | seq: 121 | - id: unk1 122 | contents: [01, 00, 00, 00] 123 | - id: num_soundbanks 124 | type: u4 125 | - id: objs 126 | type: stid_obj 127 | repeat: expr 128 | repeat-expr: num_soundbanks 129 | stid_obj: 130 | seq: 131 | - id: id 132 | type: u4 133 | - id: name_len 134 | type: u1 135 | - id: name 136 | type: str 137 | size: name_len 138 | encoding: utf-8 139 | # STID END 140 | 141 | # HIRC BEGIN 142 | hirc_data: 143 | params: 144 | - id: length 145 | type: u4 146 | seq: 147 | - id: num_objects 148 | type: u4 149 | - id: objs 150 | type: hirc_obj 151 | repeat: expr 152 | repeat-expr: num_objects 153 | hirc_obj: 154 | seq: 155 | - id: type 156 | type: s1 157 | enum: object_type 158 | - id: length 159 | type: u4 160 | - id: id 161 | type: u4 162 | #- id: additional_bytes 163 | # size: length - (1 * 4) 164 | - id: object_data 165 | type: 166 | switch-on: type 167 | cases: 168 | object_type::settings: settings 169 | object_type::sound_effect_or_voice: sound_effect_or_voice 170 | #object_type::music_segment: music_segment 171 | object_type::event_action: event_action 172 | object_type::event: event 173 | _: random_bytes(length - 4) 174 | 175 | settings: 176 | seq: 177 | - id: settings_count 178 | type: s1 179 | - id: setting_type 180 | size: 1 181 | repeat: expr 182 | repeat-expr: settings_count 183 | - id: setting_val 184 | type: f4 185 | repeat: expr 186 | repeat-expr: settings_count 187 | sound_effect_or_voice: 188 | seq: 189 | - id: unknown 190 | size: 4 191 | - id: included_or_streamed 192 | type: u4 193 | - id: audio_file_id 194 | type: u4 195 | - id: source_id 196 | type: u4 197 | - id: wem_offset 198 | type: u4 199 | if: included_or_streamed == 0 200 | - id: wem_length 201 | type: u4 202 | if: included_or_streamed == 0 203 | - id: sound_object_type 204 | type: s1 205 | - id: sound_structure 206 | size: '_parent.as.length - (4 * 5) - (1 * 1) - (8 * (included_or_streamed == 0 ? 1 : 0))' 207 | #type: sound_structure 208 | instances: 209 | wem_data: 210 | pos: wem_offset 211 | size: wem_length 212 | if: included_or_streamed == 0 213 | event_action: 214 | seq: 215 | - id: scope 216 | type: s1 217 | enum: action_scope 218 | - id: type 219 | type: s1 220 | enum: action_type 221 | - id: game_object_id 222 | type: u4 223 | - id: blank1 224 | contents: [00] 225 | - id: parameter_count 226 | type: s1 227 | - id: parameter_type 228 | size: 1 229 | repeat: expr 230 | repeat-expr: parameter_count 231 | - id: parameter_value 232 | type: 233 | switch-on: parameter_type[_index] 234 | cases: 235 | '[0x0e]': u4 236 | '[0x0f]': u4 237 | '[0x10]': f4 238 | repeat: expr 239 | repeat-expr: parameter_count 240 | - id: blank2 241 | contents: [00] 242 | - id: state_group_id 243 | type: u4 244 | if: type == action_type::set_state 245 | - id: state_id 246 | type: u4 247 | if: type == action_type::set_state 248 | - id: switch_group_id 249 | type: u4 250 | if: type == action_type::set_switch 251 | - id: switch_id 252 | type: u4 253 | if: type == action_type::set_switch 254 | - id: extra 255 | type: 'random_bytes(_parent.length - 4 - 1 - 1 - 4 - 1 - 1 - (1 * parameter_count) - (4 * parameter_count) - 1 - (8 * (type == action_type::set_state ? 1 : 0)) - (8 * (type == action_type::set_switch ? 1 : 0)))' 256 | event: 257 | seq: 258 | - id: event_action_count_new 259 | type: vlq 260 | if: version >= 123 261 | - id: event_action_count_old 262 | type: u4 263 | if: version < 123 264 | - id: event_actions 265 | type: u4 266 | repeat: expr 267 | repeat-expr: event_action_count_value 268 | instances: 269 | version: 270 | value: _root.data[0].section_data.as.version 271 | event_action_count_value: 272 | value: 'version >= 123 ? event_action_count_new.as.value : event_action_count_old.as' 273 | 274 | audio_bus: 275 | seq: 276 | - id: parent_audio_bus_id 277 | type: u4 278 | - id: additional_parameter_count 279 | type: s1 280 | - id: parameter_type 281 | type: s1 282 | enum: audio_bus_parameter_type 283 | repeat: expr 284 | repeat-expr: additional_parameter_count 285 | - id: parameter_value 286 | type: f4 287 | repeat: expr 288 | repeat-expr: additional_parameter_count 289 | - id: action_when_priority_equal 290 | type: s1 291 | - id: action_when_limit_reached 292 | type: s1 293 | - id: sound_instances_limit 294 | type: u2 295 | - id: override_parent_playback_limit 296 | type: b1 297 | - id: unknown 298 | size: 4 299 | #contents: [0x3f, 0x00, 0x00, 0x00] 300 | - id: auto_ducking_recover_time 301 | type: u4 302 | - id: auto_ducking_max_volume 303 | type: f4 304 | - id: auto_ducking_num_busses 305 | type: u4 306 | # incomplete! 307 | music_segment: 308 | seq: 309 | - id: sound_structure 310 | type: sound_structure 311 | - id: child_obj_count 312 | type: u4 313 | - id: child_obj 314 | type: u4 315 | repeat: expr 316 | repeat-expr: child_obj_count 317 | - id: unk 318 | size: 100 319 | 320 | sound_structure: 321 | seq: 322 | - id: override_parent_settings_for_effects 323 | type: b1 324 | - id: effects_count 325 | type: s1 326 | - id: bypassed_effects 327 | size: 1 328 | if: effects_count > 0 329 | - id: effect 330 | type: ss_effect 331 | repeat: expr 332 | repeat-expr: effects_count 333 | if: effects_count > 0 334 | - id: output_bus_id 335 | type: u4 336 | - id: parent_id 337 | type: u4 338 | - id: override_parent_settings_playback_priority 339 | type: b1 340 | - id: offset_priority_enabled 341 | type: b1 342 | - id: additional_parameter_count 343 | type: s1 344 | - id: parameter_type 345 | type: s1 346 | repeat: expr 347 | repeat-expr: additional_parameter_count 348 | - id: parameter_value 349 | type: 350 | switch-on: parameter_type[_index] 351 | cases: 352 | 7: u4 353 | _: f4 354 | repeat: expr 355 | repeat-expr: additional_parameter_count 356 | - id: blank1 357 | #contents: [00] 358 | size: 1 359 | - id: positioning_section_included 360 | type: b1 361 | - id: positioning_dimension 362 | type: s1 363 | if: positioning_section_included 364 | - id: pos_inc_2d 365 | type: ss_pos_inc_2d 366 | if: positioning_section_included and positioning_dimension == 0 367 | - id: pos_inc_3d 368 | type: ss_pos_inc_3d 369 | if: positioning_section_included and positioning_dimension == 0 370 | - id: override_parent_game_aux_settings 371 | type: b1 372 | - id: use_game_aux_sends 373 | type: b1 374 | - id: user_aux_sends_exist 375 | type: b1 376 | - id: aux_busses 377 | type: u4 378 | repeat: expr 379 | repeat-expr: 4 380 | if: user_aux_sends_exist 381 | - id: unk_limit_param_playback 382 | type: b1 383 | - id: when_priority_is_equal 384 | type: s1 385 | if: unk_limit_param_playback 386 | - id: when_limit_is_reached 387 | type: s1 388 | if: unk_limit_param_playback 389 | - id: sound_instance_limit 390 | type: u2 391 | if: unk_limit_param_playback 392 | - id: sound_instance_limiting_behavior 393 | type: s1 394 | - id: virt_voice_behavior 395 | type: s1 396 | - id: parent_playback_settings_override 397 | type: b1 398 | - id: parent_virt_voice_settings_override 399 | type: b1 400 | - id: state_group_count 401 | type: u4 402 | - id: state_group 403 | type: ss_state_group 404 | repeat: expr 405 | repeat-expr: state_group_count 406 | - id: rtpc_count 407 | type: u2 408 | 409 | 410 | ss_effect: 411 | seq: 412 | - id: index 413 | type: s1 414 | - id: id 415 | type: u4 416 | - id: blank 417 | #contents: [00, 00] 418 | size: 2 419 | ss_pos_inc_2d: 420 | seq: 421 | - id: panner_enabled 422 | type: b1 423 | ss_pos_inc_3d: 424 | seq: 425 | - id: source_type 426 | type: u4 427 | - id: attenuation_id 428 | type: u4 429 | - id: spatialization_enabled 430 | type: b1 431 | - id: play_type 432 | type: u4 433 | enum: play_type 434 | if: source_type == 2 435 | - id: looping_enabled 436 | type: b1 437 | if: source_type == 2 438 | - id: trainsition_time 439 | type: u4 440 | if: source_type == 2 441 | - id: follow_listener_orientation 442 | type: b1 443 | if: source_type == 2 444 | - id: update_at_each_frame 445 | type: b1 446 | if: source_type == 3 447 | ss_state_group: 448 | seq: 449 | - id: id 450 | type: u4 451 | - id: change_occurs_at 452 | type: s1 453 | - id: settings_vary_from_default 454 | type: u2 455 | - id: state_obj 456 | type: ss_state_obj 457 | repeat: expr 458 | repeat-expr: settings_vary_from_default 459 | ss_state_obj: 460 | seq: 461 | - id: id 462 | type: u4 463 | - id: settings_obj_id 464 | type: u4 465 | ss_rtpc: 466 | seq: 467 | - id: game_param_x_axis_id 468 | type: u4 469 | - id: y_axis_type 470 | type: u4 # TODO: add enum 471 | - id: unk_id_1 472 | type: u4 473 | - id: unk_1 474 | size: 1 475 | - id: num_points 476 | type: s1 477 | - id: unk_2 478 | size: 1 479 | - id: point 480 | type: ss_rtpc_point 481 | repeat: expr 482 | repeat-expr: num_points 483 | ss_rtpc_point: 484 | seq: 485 | - id: x_coord 486 | type: f4 487 | - id: y_coord 488 | type: f4 489 | - id: curve_shape 490 | type: u4 491 | 492 | random_bytes: 493 | params: 494 | - id: size 495 | type: u4 496 | seq: 497 | - id: data 498 | size: size 499 | # HIRC END 500 | 501 | enums: 502 | # HIRC ENUM BEGIN 503 | object_type: 504 | 1: settings 505 | 2: sound_effect_or_voice 506 | 3: event_action 507 | 4: event 508 | 5: random_or_sequence_container 509 | 6: switch_container 510 | 7: actor_mixer 511 | 8: audio_bus 512 | 9: blend_container 513 | 10: music_segment 514 | 11: music_track 515 | 12: music_switch_container 516 | 13: music_playlist_container 517 | 14: attenuation 518 | 15: dialogue_event 519 | 16: motion_bus 520 | 17: motion_fx 521 | 18: effect 522 | 19: unknown 523 | 20: auxiliary_bus 524 | action_scope: 525 | 1: game_object_switch_or_trigger 526 | 2: global 527 | 3: game_object 528 | 4: game_object_state 529 | 5: all 530 | 9: all_except 531 | action_type: 532 | 1: stop 533 | 2: pause 534 | 3: resume 535 | 4: play 536 | 5: trigger 537 | 6: mute 538 | 7: unmute 539 | 8: set_voice_pitch 540 | 9: reset_voice_pitch 541 | 10: set_voice_volume 542 | 11: reset_voice_volume 543 | 12: set_bus_volume 544 | 13: reset_bus_volume 545 | 14: set_voice_low_pass_filter 546 | 15: reset_voice_low_pass_filter 547 | 16: enable_state 548 | 17: disable_state 549 | 18: set_state 550 | 19: set_game_parameter 551 | 20: reset_game_parameter 552 | 21: set_switch 553 | 22: enable_bypass_or_disable_bypass 554 | 23: reset_bypass_effect 555 | 24: break 556 | 25: seek 557 | audio_bus_parameter_type: 558 | 0: voice_volume 559 | 2: voice_pitch 560 | 3: voice_low_pass_filter 561 | 4: bus_volume 562 | play_type: 563 | 0: sequence_step 564 | 1: random_step 565 | 2: sequence_continuous 566 | 3: random_continuous 567 | 4: sequence_step_new_path 568 | 5: random_step_new_path 569 | # HIRC ENUM END 570 | -------------------------------------------------------------------------------- /include/util/rang.hpp: -------------------------------------------------------------------------------- 1 | #ifndef RANG_DOT_HPP 2 | #define RANG_DOT_HPP 3 | 4 | #if defined(__unix__) || defined(__unix) || defined(__linux__) 5 | #define OS_LINUX 6 | #elif defined(WIN32) || defined(_WIN32) || defined(_WIN64) 7 | #define OS_WIN 8 | #elif defined(__APPLE__) || defined(__MACH__) 9 | #define OS_MAC 10 | #else 11 | #error Unknown Platform 12 | #endif 13 | 14 | #if defined(OS_LINUX) || defined(OS_MAC) 15 | #include 16 | 17 | #elif defined(OS_WIN) 18 | 19 | #if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0600) 20 | #error \ 21 | "Please include rang.hpp before any windows system headers or set _WIN32_WINNT at least to _WIN32_WINNT_VISTA" 22 | #elif !defined(_WIN32_WINNT) 23 | #define _WIN32_WINNT _WIN32_WINNT_VISTA 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | // Only defined in windows 10 onwards, redefining in lower windows since it 31 | // doesn't gets used in lower versions 32 | // https://docs.microsoft.com/en-us/windows/console/getconsolemode 33 | #ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING 34 | #define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 35 | #endif 36 | 37 | #endif 38 | 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | 45 | namespace rang { 46 | 47 | /* For better compability with most of terminals do not use any style settings 48 | * except of reset, bold and reversed. 49 | * Note that on Windows terminals bold style is same as fgB color. 50 | */ 51 | enum class style { 52 | reset = 0, 53 | bold = 1, 54 | dim = 2, 55 | italic = 3, 56 | underline = 4, 57 | blink = 5, 58 | rblink = 6, 59 | reversed = 7, 60 | conceal = 8, 61 | crossed = 9 62 | }; 63 | 64 | enum class fg { 65 | black = 30, 66 | red = 31, 67 | green = 32, 68 | yellow = 33, 69 | blue = 34, 70 | magenta = 35, 71 | cyan = 36, 72 | gray = 37, 73 | reset = 39 74 | }; 75 | 76 | enum class bg { 77 | black = 40, 78 | red = 41, 79 | green = 42, 80 | yellow = 43, 81 | blue = 44, 82 | magenta = 45, 83 | cyan = 46, 84 | gray = 47, 85 | reset = 49 86 | }; 87 | 88 | enum class fgB { 89 | black = 90, 90 | red = 91, 91 | green = 92, 92 | yellow = 93, 93 | blue = 94, 94 | magenta = 95, 95 | cyan = 96, 96 | gray = 97 97 | }; 98 | 99 | enum class bgB { 100 | black = 100, 101 | red = 101, 102 | green = 102, 103 | yellow = 103, 104 | blue = 104, 105 | magenta = 105, 106 | cyan = 106, 107 | gray = 107 108 | }; 109 | 110 | enum class control { // Behaviour of rang function calls 111 | Off = 0, // toggle off rang style/color calls 112 | Auto = 1, // (Default) autodect terminal and colorize if needed 113 | Force = 2 // force ansi color output to non terminal streams 114 | }; 115 | // Use rang::setControlMode to set rang control mode 116 | 117 | enum class winTerm { // Windows Terminal Mode 118 | Auto = 0, // (Default) automatically detects wheter Ansi or Native API 119 | Ansi = 1, // Force use Ansi API 120 | Native = 2 // Force use Native API 121 | }; 122 | // Use rang::setWinTermMode to explicitly set terminal API for Windows 123 | // Calling rang::setWinTermMode have no effect on other OS 124 | 125 | namespace rang_implementation { 126 | 127 | inline std::atomic &controlMode() noexcept 128 | { 129 | static std::atomic value(control::Auto); 130 | return value; 131 | } 132 | 133 | inline std::atomic &winTermMode() noexcept 134 | { 135 | static std::atomic termMode(winTerm::Auto); 136 | return termMode; 137 | } 138 | 139 | inline bool supportsColor() noexcept 140 | { 141 | #if defined(OS_LINUX) || defined(OS_MAC) 142 | 143 | static const bool result = [] { 144 | const char *Terms[] 145 | = { "ansi", "color", "console", "cygwin", "gnome", 146 | "konsole", "kterm", "linux", "msys", "putty", 147 | "rxvt", "screen", "vt100", "xterm" }; 148 | 149 | const char *env_p = std::getenv("TERM"); 150 | if (env_p == nullptr) { 151 | return false; 152 | } 153 | return std::any_of(std::begin(Terms), std::end(Terms), 154 | [&](const char *term) { 155 | return std::strstr(env_p, term) != nullptr; 156 | }); 157 | }(); 158 | 159 | #elif defined(OS_WIN) 160 | // All windows versions support colors through native console methods 161 | static constexpr bool result = true; 162 | #endif 163 | return result; 164 | } 165 | 166 | #ifdef OS_WIN 167 | 168 | 169 | inline bool isMsysPty(int fd) noexcept 170 | { 171 | // Dynamic load for binary compability with old Windows 172 | const auto ptrGetFileInformationByHandleEx 173 | = reinterpret_cast( 174 | GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), 175 | "GetFileInformationByHandleEx")); 176 | if (!ptrGetFileInformationByHandleEx) { 177 | return false; 178 | } 179 | 180 | HANDLE h = reinterpret_cast(_get_osfhandle(fd)); 181 | if (h == INVALID_HANDLE_VALUE) { 182 | return false; 183 | } 184 | 185 | // Check that it's a pipe: 186 | if (GetFileType(h) != FILE_TYPE_PIPE) { 187 | return false; 188 | } 189 | 190 | // POD type is binary compatible with FILE_NAME_INFO from WinBase.h 191 | // It have the same alignment and used to avoid UB in caller code 192 | struct MY_FILE_NAME_INFO { 193 | DWORD FileNameLength; 194 | WCHAR FileName[MAX_PATH]; 195 | }; 196 | 197 | auto pNameInfo = std::unique_ptr( 198 | new (std::nothrow) MY_FILE_NAME_INFO()); 199 | if (!pNameInfo) { 200 | return false; 201 | } 202 | 203 | // Check pipe name is template of 204 | // {"cygwin-","msys-"}XXXXXXXXXXXXXXX-ptyX-XX 205 | if (!ptrGetFileInformationByHandleEx(h, FileNameInfo, pNameInfo.get(), 206 | sizeof(MY_FILE_NAME_INFO))) { 207 | return false; 208 | } 209 | std::wstring name(pNameInfo->FileName, pNameInfo->FileNameLength / sizeof(WCHAR)); 210 | if ((name.find(L"msys-") == std::wstring::npos 211 | && name.find(L"cygwin-") == std::wstring::npos) 212 | || name.find(L"-pty") == std::wstring::npos) { 213 | return false; 214 | } 215 | 216 | return true; 217 | } 218 | 219 | #endif 220 | 221 | inline bool isTerminal(const std::streambuf *osbuf) noexcept 222 | { 223 | using std::cerr; 224 | using std::clog; 225 | using std::cout; 226 | #if defined(OS_LINUX) || defined(OS_MAC) 227 | if (osbuf == cout.rdbuf()) { 228 | static const bool cout_term = isatty(fileno(stdout)) != 0; 229 | return cout_term; 230 | } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) { 231 | static const bool cerr_term = isatty(fileno(stderr)) != 0; 232 | return cerr_term; 233 | } 234 | #elif defined(OS_WIN) 235 | if (osbuf == cout.rdbuf()) { 236 | static const bool cout_term 237 | = (_isatty(_fileno(stdout)) || isMsysPty(_fileno(stdout))); 238 | return cout_term; 239 | } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) { 240 | static const bool cerr_term 241 | = (_isatty(_fileno(stderr)) || isMsysPty(_fileno(stderr))); 242 | return cerr_term; 243 | } 244 | #endif 245 | return false; 246 | } 247 | 248 | template 249 | using enableStd = typename std::enable_if< 250 | std::is_same::value || std::is_same::value 251 | || std::is_same::value || std::is_same::value 252 | || std::is_same::value, 253 | std::ostream &>::type; 254 | 255 | 256 | #ifdef OS_WIN 257 | 258 | struct SGR { // Select Graphic Rendition parameters for Windows console 259 | BYTE fgColor; // foreground color (0-15) lower 3 rgb bits + intense bit 260 | BYTE bgColor; // background color (0-15) lower 3 rgb bits + intense bit 261 | BYTE bold; // emulated as FOREGROUND_INTENSITY bit 262 | BYTE underline; // emulated as BACKGROUND_INTENSITY bit 263 | BOOLEAN inverse; // swap foreground/bold & background/underline 264 | BOOLEAN conceal; // set foreground/bold to background/underline 265 | }; 266 | 267 | enum class AttrColor : BYTE { // Color attributes for console screen buffer 268 | black = 0, 269 | red = 4, 270 | green = 2, 271 | yellow = 6, 272 | blue = 1, 273 | magenta = 5, 274 | cyan = 3, 275 | gray = 7 276 | }; 277 | 278 | inline HANDLE getConsoleHandle(const std::streambuf *osbuf) noexcept 279 | { 280 | if (osbuf == std::cout.rdbuf()) { 281 | static const HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); 282 | return hStdout; 283 | } else if (osbuf == std::cerr.rdbuf() || osbuf == std::clog.rdbuf()) { 284 | static const HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE); 285 | return hStderr; 286 | } 287 | return INVALID_HANDLE_VALUE; 288 | } 289 | 290 | inline bool setWinTermAnsiColors(const std::streambuf *osbuf) noexcept 291 | { 292 | HANDLE h = getConsoleHandle(osbuf); 293 | if (h == INVALID_HANDLE_VALUE) { 294 | return false; 295 | } 296 | DWORD dwMode = 0; 297 | if (!GetConsoleMode(h, &dwMode)) { 298 | return false; 299 | } 300 | dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; 301 | if (!SetConsoleMode(h, dwMode)) { 302 | return false; 303 | } 304 | return true; 305 | } 306 | 307 | inline bool supportsAnsi(const std::streambuf *osbuf) noexcept 308 | { 309 | using std::cerr; 310 | using std::clog; 311 | using std::cout; 312 | if (osbuf == cout.rdbuf()) { 313 | static const bool cout_ansi 314 | = (isMsysPty(_fileno(stdout)) || setWinTermAnsiColors(osbuf)); 315 | return cout_ansi; 316 | } else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) { 317 | static const bool cerr_ansi 318 | = (isMsysPty(_fileno(stderr)) || setWinTermAnsiColors(osbuf)); 319 | return cerr_ansi; 320 | } 321 | return false; 322 | } 323 | 324 | inline const SGR &defaultState() noexcept 325 | { 326 | static const SGR defaultSgr = []() -> SGR { 327 | CONSOLE_SCREEN_BUFFER_INFO info; 328 | WORD attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; 329 | if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), 330 | &info) 331 | || GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), 332 | &info)) { 333 | attrib = info.wAttributes; 334 | } 335 | SGR sgr = { 0, 0, 0, 0, FALSE, FALSE }; 336 | sgr.fgColor = attrib & 0x0F; 337 | sgr.bgColor = (attrib & 0xF0) >> 4; 338 | return sgr; 339 | }(); 340 | return defaultSgr; 341 | } 342 | 343 | inline BYTE ansi2attr(BYTE rgb) noexcept 344 | { 345 | static const AttrColor rev[8] 346 | = { AttrColor::black, AttrColor::red, AttrColor::green, 347 | AttrColor::yellow, AttrColor::blue, AttrColor::magenta, 348 | AttrColor::cyan, AttrColor::gray }; 349 | return static_cast(rev[rgb]); 350 | } 351 | 352 | inline void setWinSGR(rang::bg col, SGR &state) noexcept 353 | { 354 | if (col != rang::bg::reset) { 355 | state.bgColor = ansi2attr(static_cast(col) - 40); 356 | } else { 357 | state.bgColor = defaultState().bgColor; 358 | } 359 | } 360 | 361 | inline void setWinSGR(rang::fg col, SGR &state) noexcept 362 | { 363 | if (col != rang::fg::reset) { 364 | state.fgColor = ansi2attr(static_cast(col) - 30); 365 | } else { 366 | state.fgColor = defaultState().fgColor; 367 | } 368 | } 369 | 370 | inline void setWinSGR(rang::bgB col, SGR &state) noexcept 371 | { 372 | state.bgColor = (BACKGROUND_INTENSITY >> 4) 373 | | ansi2attr(static_cast(col) - 100); 374 | } 375 | 376 | inline void setWinSGR(rang::fgB col, SGR &state) noexcept 377 | { 378 | state.fgColor 379 | = FOREGROUND_INTENSITY | ansi2attr(static_cast(col) - 90); 380 | } 381 | 382 | inline void setWinSGR(rang::style style, SGR &state) noexcept 383 | { 384 | switch (style) { 385 | case rang::style::reset: state = defaultState(); break; 386 | case rang::style::bold: state.bold = FOREGROUND_INTENSITY; break; 387 | case rang::style::underline: 388 | case rang::style::blink: 389 | state.underline = BACKGROUND_INTENSITY; 390 | break; 391 | case rang::style::reversed: state.inverse = TRUE; break; 392 | case rang::style::conceal: state.conceal = TRUE; break; 393 | default: break; 394 | } 395 | } 396 | 397 | inline SGR ¤t_state() noexcept 398 | { 399 | static SGR state = defaultState(); 400 | return state; 401 | } 402 | 403 | inline WORD SGR2Attr(const SGR &state) noexcept 404 | { 405 | WORD attrib = 0; 406 | if (state.conceal) { 407 | if (state.inverse) { 408 | attrib = (state.fgColor << 4) | state.fgColor; 409 | if (state.bold) 410 | attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; 411 | } else { 412 | attrib = (state.bgColor << 4) | state.bgColor; 413 | if (state.underline) 414 | attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; 415 | } 416 | } else if (state.inverse) { 417 | attrib = (state.fgColor << 4) | state.bgColor; 418 | if (state.bold) attrib |= BACKGROUND_INTENSITY; 419 | if (state.underline) attrib |= FOREGROUND_INTENSITY; 420 | } else { 421 | attrib = state.fgColor | (state.bgColor << 4) | state.bold 422 | | state.underline; 423 | } 424 | return attrib; 425 | } 426 | 427 | template 428 | inline void setWinColorAnsi(std::ostream &os, T const value) 429 | { 430 | os << "\033[" << static_cast(value) << "m"; 431 | } 432 | 433 | template 434 | inline void setWinColorNative(std::ostream &os, T const value) 435 | { 436 | const HANDLE h = getConsoleHandle(os.rdbuf()); 437 | if (h != INVALID_HANDLE_VALUE) { 438 | setWinSGR(value, current_state()); 439 | // Out all buffered text to console with previous settings: 440 | os.flush(); 441 | SetConsoleTextAttribute(h, SGR2Attr(current_state())); 442 | } 443 | } 444 | 445 | template 446 | inline enableStd setColor(std::ostream &os, T const value) 447 | { 448 | if (winTermMode() == winTerm::Auto) { 449 | if (supportsAnsi(os.rdbuf())) { 450 | setWinColorAnsi(os, value); 451 | } else { 452 | setWinColorNative(os, value); 453 | } 454 | } else if (winTermMode() == winTerm::Ansi) { 455 | setWinColorAnsi(os, value); 456 | } else { 457 | setWinColorNative(os, value); 458 | } 459 | return os; 460 | } 461 | #else 462 | template 463 | inline enableStd setColor(std::ostream &os, T const value) 464 | { 465 | return os << "\033[" << static_cast(value) << "m"; 466 | } 467 | #endif 468 | } // namespace rang_implementation 469 | 470 | template 471 | inline rang_implementation::enableStd operator<<(std::ostream &os, 472 | const T value) 473 | { 474 | const control option = rang_implementation::controlMode(); 475 | switch (option) { 476 | case control::Auto: 477 | return rang_implementation::supportsColor() 478 | && rang_implementation::isTerminal(os.rdbuf()) 479 | ? rang_implementation::setColor(os, value) 480 | : os; 481 | case control::Force: return rang_implementation::setColor(os, value); 482 | default: return os; 483 | } 484 | } 485 | 486 | inline void setWinTermMode(const rang::winTerm value) noexcept 487 | { 488 | rang_implementation::winTermMode() = value; 489 | } 490 | 491 | inline void setControlMode(const control value) noexcept 492 | { 493 | rang_implementation::controlMode() = value; 494 | } 495 | 496 | } // namespace rang 497 | 498 | #undef OS_LINUX 499 | #undef OS_WIN 500 | #undef OS_MAC 501 | 502 | #endif /* ifndef RANG_DOT_HPP */ 503 | --------------------------------------------------------------------------------