├── .clang-format ├── .github └── workflows │ └── main.yml ├── .gitignore ├── CMakeLists.txt ├── License ├── ReadMe.md ├── example ├── bigtest.nbt ├── example.cpp ├── hello_world.nbt └── include │ ├── strict_fstream.hpp │ └── zstr.hpp ├── nbt.hpp └── tests ├── data ├── arrays.nbt ├── compound.nbt ├── list.nbt ├── numerics.nbt ├── printed_list.txt └── string.nbt ├── test_arrays.cpp ├── test_compound.cpp ├── test_list.cpp ├── test_misc.cpp ├── test_numerics.cpp └── test_string.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: DontAlign 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveMacros: false 8 | AlignConsecutiveAssignments: false 9 | AlignConsecutiveBitFields: false 10 | AlignConsecutiveDeclarations: false 11 | AlignEscapedNewlines: Right 12 | AlignOperands: DontAlign 13 | AlignTrailingComments: true 14 | AllowAllArgumentsOnNextLine: false 15 | AllowAllConstructorInitializersOnNextLine: false 16 | AllowAllParametersOfDeclarationOnNextLine: false 17 | AllowShortEnumsOnASingleLine: true 18 | AllowShortBlocksOnASingleLine: Empty 19 | AllowShortCaseLabelsOnASingleLine: false 20 | AllowShortFunctionsOnASingleLine: Empty 21 | AllowShortLambdasOnASingleLine: All 22 | AllowShortIfStatementsOnASingleLine: Never 23 | AllowShortLoopsOnASingleLine: false 24 | AlwaysBreakAfterDefinitionReturnType: None 25 | AlwaysBreakAfterReturnType: None 26 | AlwaysBreakBeforeMultilineStrings: true 27 | AlwaysBreakTemplateDeclarations: No 28 | AttributeMacros: 29 | - __capability 30 | BinPackArguments: true 31 | BinPackParameters: true 32 | BraceWrapping: 33 | AfterCaseLabel: false 34 | AfterClass: false 35 | AfterControlStatement: Never 36 | AfterEnum: false 37 | AfterFunction: false 38 | AfterNamespace: false 39 | AfterObjCDeclaration: false 40 | AfterStruct: false 41 | AfterUnion: false 42 | AfterExternBlock: false 43 | BeforeCatch: false 44 | BeforeElse: false 45 | BeforeLambdaBody: false 46 | BeforeWhile: false 47 | IndentBraces: false 48 | SplitEmptyFunction: true 49 | SplitEmptyRecord: true 50 | SplitEmptyNamespace: true 51 | BreakBeforeBinaryOperators: None 52 | BreakBeforeConceptDeclarations: true 53 | BreakBeforeBraces: Attach 54 | BreakBeforeInheritanceComma: false 55 | BreakInheritanceList: BeforeColon 56 | BreakBeforeTernaryOperators: true 57 | BreakConstructorInitializersBeforeComma: false 58 | BreakConstructorInitializers: BeforeColon 59 | BreakAfterJavaFieldAnnotations: false 60 | BreakStringLiterals: true 61 | ColumnLimit: 80 62 | CommentPragmas: '^ IWYU pragma:' 63 | CompactNamespaces: false 64 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 65 | ConstructorInitializerIndentWidth: 4 66 | ContinuationIndentWidth: 4 67 | Cpp11BracedListStyle: true 68 | DeriveLineEnding: true 69 | DerivePointerAlignment: false 70 | DisableFormat: false 71 | EmptyLineAfterAccessModifier: Never 72 | EmptyLineBeforeAccessModifier: LogicalBlock 73 | ExperimentalAutoDetectBinPacking: false 74 | FixNamespaceComments: true 75 | ForEachMacros: 76 | - foreach 77 | - Q_FOREACH 78 | - BOOST_FOREACH 79 | IfMacros: 80 | - KJ_IF_MAYBE 81 | IncludeBlocks: Preserve 82 | IncludeCategories: 83 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 84 | Priority: 2 85 | SortPriority: 0 86 | CaseSensitive: false 87 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 88 | Priority: 3 89 | SortPriority: 0 90 | CaseSensitive: false 91 | - Regex: '.*' 92 | Priority: 1 93 | SortPriority: 0 94 | CaseSensitive: false 95 | IncludeIsMainRegex: '(Test)?$' 96 | IncludeIsMainSourceRegex: '' 97 | IndentAccessModifiers: false 98 | IndentCaseLabels: true 99 | IndentCaseBlocks: false 100 | IndentGotoLabels: true 101 | IndentPPDirectives: None 102 | IndentExternBlock: AfterExternBlock 103 | IndentRequires: false 104 | IndentWidth: 2 105 | IndentWrappedFunctionNames: false 106 | InsertTrailingCommas: None 107 | JavaScriptQuotes: Leave 108 | JavaScriptWrapImports: true 109 | KeepEmptyLinesAtTheStartOfBlocks: true 110 | LambdaBodyIndentation: Signature 111 | MacroBlockBegin: '' 112 | MacroBlockEnd: '' 113 | MaxEmptyLinesToKeep: 2 114 | NamespaceIndentation: None 115 | ObjCBinPackProtocolList: Auto 116 | ObjCBlockIndentWidth: 2 117 | ObjCBreakBeforeNestedBlockParam: true 118 | ObjCSpaceAfterProperty: false 119 | ObjCSpaceBeforeProtocolList: true 120 | PenaltyBreakAssignment: 2 121 | PenaltyBreakBeforeFirstCallParameter: 19 122 | PenaltyBreakComment: 300 123 | PenaltyBreakFirstLessLess: 120 124 | PenaltyBreakString: 1000 125 | PenaltyBreakTemplateDeclaration: 10 126 | PenaltyExcessCharacter: 1000000 127 | PenaltyReturnTypeOnItsOwnLine: 60 128 | PenaltyIndentedWhitespace: 0 129 | PointerAlignment: Left 130 | PPIndentWidth: -1 131 | ReferenceAlignment: Pointer 132 | ReflowComments: true 133 | ShortNamespaceLines: 1 134 | SortIncludes: CaseInsensitive 135 | SortJavaStaticImport: Before 136 | SortUsingDeclarations: true 137 | SpaceAfterCStyleCast: true 138 | SpaceAfterLogicalNot: false 139 | SpaceAfterTemplateKeyword: true 140 | SpaceBeforeAssignmentOperators: true 141 | SpaceBeforeCaseColon: false 142 | SpaceBeforeCpp11BracedList: true 143 | SpaceBeforeCtorInitializerColon: true 144 | SpaceBeforeInheritanceColon: true 145 | SpaceBeforeParens: Never 146 | SpaceAroundPointerQualifiers: Default 147 | SpaceBeforeRangeBasedForLoopColon: true 148 | SpaceInEmptyBlock: false 149 | SpaceInEmptyParentheses: false 150 | SpacesBeforeTrailingComments: 1 151 | SpacesInAngles: Never 152 | SpacesInConditionalStatement: false 153 | SpacesInContainerLiterals: false 154 | SpacesInCStyleCastParentheses: false 155 | SpacesInLineCommentPrefix: 156 | Minimum: 1 157 | Maximum: -1 158 | SpacesInParentheses: false 159 | SpacesInSquareBrackets: false 160 | SpaceBeforeSquareBrackets: false 161 | BitFieldColonSpacing: Both 162 | Standard: Latest 163 | StatementAttributeLikeMacros: 164 | - Q_EMIT 165 | StatementMacros: 166 | - Q_UNUSED 167 | - QT_REQUIRE_VERSION 168 | TabWidth: 8 169 | UseCRLF: false 170 | UseTab: Never 171 | WhitespaceSensitiveMacros: 172 | - STRINGIZE 173 | - PP_STRINGIZE 174 | - BOOST_PP_STRINGIZE 175 | - NS_SWIFT_NAME 176 | - CF_SWIFT_NAME 177 | ... 178 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build+tests 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | linux: 6 | runs-on: ubuntu-latest 7 | container: archlinux:base-devel 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | 12 | - name: Install Build Dependencies 13 | run: | 14 | pacman --noconfirm -Syu 15 | pacman --noconfirm -S cmake ninja boost git curl zip unzip tar 16 | 17 | - name: Configure 18 | run: cmake . -G Ninja -D RUN_COVERAGE:BOOL=ON 19 | 20 | - name: Build 21 | run: cmake --build . --config Debug 22 | 23 | - name: Test 24 | run: ctest -j6 -C Debug --output-on-failure 25 | 26 | - name: Upload Coverage 27 | uses: codecov/codecov-action@v3 28 | with: 29 | gcov: true 30 | gcov_args: -r 31 | 32 | # windows: 33 | # runs-on: windows-latest 34 | # steps: 35 | # - name: Checkout 36 | # uses: actions/checkout@v2 37 | 38 | # - name: Configure 39 | # run: cmake . 40 | 41 | # - name: Build 42 | # run: cmake --build . --config Release 43 | 44 | # - name: Test 45 | # run: ctest -j6 -C Release -T test --output-on-failure 46 | 47 | # osx: 48 | # runs-on: macos-latest 49 | # steps: 50 | # - name: Checkout 51 | # uses: actions/checkout@v2 52 | 53 | # - name: Configure 54 | # env: 55 | # CC: gcc-11 56 | # CXX: g++-11 57 | # run: cmake . 58 | 59 | # - name: Build 60 | # run: cmake --build . --config Release 61 | 62 | # - name: Test 63 | # run: ctest -j6 -C Release -T test --output-on-failure 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | build 3 | main 4 | out.nbt 5 | nbt.cpp 6 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17) 2 | project(cpp-nbt CXX) 3 | 4 | set(RUN_COVERAGE OFF CACHE BOOL "Compile with coverage options") 5 | 6 | if(MSVC) 7 | set(WARNINGS /W4;/WX) 8 | else() 9 | set(WARNINGS -Wall;-Wextra;-pedantic;-Werror;-Wno-unused-value) 10 | endif() 11 | 12 | include_directories(${CMAKE_SOURCE_DIR}) 13 | 14 | include(CTest) 15 | 16 | function(setup_test name) 17 | set(target ${name}_bin) 18 | add_executable(${target} tests/test_${name}.cpp) 19 | target_compile_features(${target} PRIVATE cxx_std_23) 20 | if(RUN_COVERAGE) 21 | target_compile_options(${target} PRIVATE -fprofile-arcs;-ftest-coverage;-fno-inline) 22 | target_link_options(${target} PRIVATE --coverage) 23 | endif() 24 | add_test( 25 | NAME test_${name} 26 | COMMAND ${CMAKE_BINARY_DIR}/${target} 27 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/tests/data 28 | ) 29 | endfunction(setup_test) 30 | 31 | setup_test(numerics) 32 | setup_test(string) 33 | setup_test(arrays) 34 | setup_test(list) 35 | setup_test(compound) 36 | setup_test(misc) 37 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020 N. Vito Gamberini 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | # cpp-nbt 2 | 3 | [![license](https://img.shields.io/badge/license-zlib-lightgrey.svg)](https://en.wikipedia.org/wiki/Zlib_License) 4 | 5 | 6 | This is a C++23 header-only library for reading/writing 7 | [Minecraft NBT](https://wiki.vg/NBT) data: 8 | * Single header file 9 | * Requires C++23 (GCC 13, Clang 14, MSVC 19.latest) 10 | * Reads from `istream`s, writes to `ostream`s 11 | * Supports pretty printing 12 | 13 | This is mostly for me to play with bleeding edge C++ stuff. Don't expect 14 | this to compile on anything but trunk. 15 | 16 | ## Quickstart 17 | 18 | `std::map` doesn't work with incomplete types portably, so you need to provide 19 | your own (`std::map` may work depending on your platform and stdlib). By 20 | default cpp-nbt uses `boost::container::map`. You can do this by defining 21 | `NBT_MAP_TYPE` to whatever type you want to use, so long it has a vaguely 22 | `std::map`-ish API. 23 | 24 | Include nbt.hpp, munch some data. 25 | 26 | ```cpp 27 | #define NBT_MAP_TYPE myFavoriteMap 28 | #include "nbt.hpp" 29 | 30 | nbt::NBT root {std::ifstream {"hello_world.nbt"}}; 31 | std::cout << root; 32 | ``` 33 | 34 | ## Tags 35 | 36 | All PC-edition NBT tags are supported, Bedrock is not currently supported. 37 | 38 | Most tags map directly to primitive types, the remainder map to STL containers. 39 | 40 | | Tag Type | Description | 41 | | --- | --- | 42 | | `TagEnd` | `std::nullptr_t` | 43 | | `TagByte` | `std::int8_t` | 44 | | `TagShort` | `std::int16_t` | 45 | | `TagInt` | `std::int32_t` | 46 | | `TagLong` | `std::int64_t` | 47 | | `TagFloat` | `float` | 48 | | `TagDouble` | `double` | 49 | | `TagByteArray` | `std::vector` | 50 | | `TagIntArray` | `std::vector` | 51 | | `TagLongArray` | `std::vector` | 52 | | `TagString` | `std::string` | 53 | | `TagList` | `std::variant, std::vector, and all other tags>` | 54 | | `Tag` | `std::variant` | 55 | | `TagCompound` | `std::map`| 56 | | `NBTData` | `struct { TagString name; TagCompound tags; }` | 57 | | `NBT` | `struct {std::optional data; }` | 58 | 59 | 60 | ### Constructing and Destructing Tags 61 | 62 | Java edition NBT root nodes are always either a TagEnd, or a named TagCompound. 63 | These two root nodes are encapsulated by the `NBT` type, which has an 64 | `encode()` and `decode()` methods to serialize and deserialize NBT. When 65 | the TagCompound is present the `data` field will be present, otherwise it 66 | will be absent. If absent, the NBT container will serialize to a single `TagEnd`. 67 | 68 | Example Usage: 69 | ```cpp 70 | nbt::NBT root; 71 | root.decode(std::ifstream {"hello_world.nbt"}); 72 | // Optionally, use the constructor 73 | // nbt::NBT root {ifs}; 74 | 75 | root.encode(std::ofstream {"out.nbt"}); 76 | ``` 77 | 78 | ### Manipulating Tags 79 | 80 | Tags must exist inside a container type, and manipulating NBT containers 81 | involves standard usage of the STL. 82 | 83 | ```cpp 84 | nbt::NBT root {"LyricalNBT", { 85 | {"Hello", "World"}, 86 | {"Lyrics", nbt::TagList { 87 | "There's", "a", "song", "that", "we're", "singing", 88 | }}, 89 | }}; 90 | 91 | root["LuckyNumbers"] = nbt::TagByteArray {1, 3, 7, 9, 13, 15}; 92 | std::get(root["LuckyNumbers"]).push_back(21); 93 | 94 | std::cout << root["Hello"] << std::endl; 95 | std::cout << root << std::endl; 96 | ``` 97 | 98 | 99 | ## Issues 100 | 101 | Please open an issue or better yet, a pull request, for any bugs or other 102 | problems. 103 | -------------------------------------------------------------------------------- /example/bigtest.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpockBotMC/cpp-nbt/ecfff0fb7654044cfa2398e6d46222fb07a6448d/example/bigtest.nbt -------------------------------------------------------------------------------- /example/example.cpp: -------------------------------------------------------------------------------- 1 | #include "nbt.hpp" 2 | #include "zstr.hpp" 3 | #include 4 | 5 | int main(void) { 6 | std::ifstream ifs {"hello_world.nbt"}; 7 | nbt::NBT tags {ifs}; 8 | std::cout << tags << std::endl; 9 | ifs.close(); 10 | 11 | zstr::ifstream zfs {"bigtest.nbt"}; 12 | tags.decode(zfs); 13 | std::cout << tags << std::endl; 14 | 15 | std::ofstream ofs {"out.nbt"}; 16 | tags.name = "Even More Test"; 17 | tags["intArrayTest"] = nbt::TagIntArray {0, 1, 2, 3, 4}; 18 | tags["longArrayTest"] = nbt::TagLongArray {5, 6, 7, 8}; 19 | tags["funBlockGame"] = nbt::TagCompound { 20 | {"a", nbt::TagDouble {0.1}}, 21 | {"b", nbt::TagFloat {0.2}}, 22 | {"c", nbt::TagString {"Minecraft"}}, 23 | }; 24 | tags.encode(ofs); 25 | 26 | ifs.open("out.nbt"); 27 | tags.decode(ifs); 28 | std::cout << tags << std::endl; 29 | } 30 | -------------------------------------------------------------------------------- /example/hello_world.nbt: -------------------------------------------------------------------------------- 1 | 2 | hello worldname Bananrama -------------------------------------------------------------------------------- /example/include/strict_fstream.hpp: -------------------------------------------------------------------------------- 1 | #ifndef __STRICT_FSTREAM_HPP 2 | #define __STRICT_FSTREAM_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * This namespace defines wrappers for std::ifstream, std::ofstream, and 11 | * std::fstream objects. The wrappers perform the following steps: 12 | * - check the open modes make sense 13 | * - check that the call to open() is successful 14 | * - (for input streams) check that the opened file is peek-able 15 | * - turn on the badbit in the exception mask 16 | */ 17 | namespace strict_fstream 18 | { 19 | 20 | /// Overload of error-reporting function, to enable use with VS. 21 | /// Ref: http://stackoverflow.com/a/901316/717706 22 | static std::string strerror() 23 | { 24 | std::string buff(80, '\0'); 25 | #ifdef _WIN32 26 | if (strerror_s(&buff[0], buff.size(), errno) != 0) 27 | { 28 | buff = "Unknown error"; 29 | } 30 | #elif (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE 31 | // XSI-compliant strerror_r() 32 | if (strerror_r(errno, &buff[0], buff.size()) != 0) 33 | { 34 | buff = "Unknown error"; 35 | } 36 | #else 37 | // GNU-specific strerror_r() 38 | auto p = strerror_r(errno, &buff[0], buff.size()); 39 | std::string tmp(p, std::strlen(p)); 40 | std::swap(buff, tmp); 41 | #endif 42 | buff.resize(buff.find('\0')); 43 | return buff; 44 | } 45 | 46 | /// Exception class thrown by failed operations. 47 | class Exception 48 | : public std::exception 49 | { 50 | public: 51 | Exception(const std::string& msg) : _msg(msg) {} 52 | const char * what() const noexcept { return _msg.c_str(); } 53 | private: 54 | std::string _msg; 55 | }; // class Exception 56 | 57 | namespace detail 58 | { 59 | 60 | struct static_method_holder 61 | { 62 | static std::string mode_to_string(std::ios_base::openmode mode) 63 | { 64 | static const int n_modes = 6; 65 | static const std::ios_base::openmode mode_val_v[n_modes] = 66 | { 67 | std::ios_base::in, 68 | std::ios_base::out, 69 | std::ios_base::app, 70 | std::ios_base::ate, 71 | std::ios_base::trunc, 72 | std::ios_base::binary 73 | }; 74 | 75 | static const char * mode_name_v[n_modes] = 76 | { 77 | "in", 78 | "out", 79 | "app", 80 | "ate", 81 | "trunc", 82 | "binary" 83 | }; 84 | std::string res; 85 | for (int i = 0; i < n_modes; ++i) 86 | { 87 | if (mode & mode_val_v[i]) 88 | { 89 | res += (! res.empty()? "|" : ""); 90 | res += mode_name_v[i]; 91 | } 92 | } 93 | if (res.empty()) res = "none"; 94 | return res; 95 | } 96 | static void check_mode(const std::string& filename, std::ios_base::openmode mode) 97 | { 98 | if ((mode & std::ios_base::trunc) && ! (mode & std::ios_base::out)) 99 | { 100 | throw Exception(std::string("strict_fstream: open('") + filename + "'): mode error: trunc and not out"); 101 | } 102 | else if ((mode & std::ios_base::app) && ! (mode & std::ios_base::out)) 103 | { 104 | throw Exception(std::string("strict_fstream: open('") + filename + "'): mode error: app and not out"); 105 | } 106 | else if ((mode & std::ios_base::trunc) && (mode & std::ios_base::app)) 107 | { 108 | throw Exception(std::string("strict_fstream: open('") + filename + "'): mode error: trunc and app"); 109 | } 110 | } 111 | static void check_open(std::ios * s_p, const std::string& filename, std::ios_base::openmode mode) 112 | { 113 | if (s_p->fail()) 114 | { 115 | perror("Fail bit set: "); 116 | throw Exception(std::string("strict_fstream: open('") 117 | + filename + "'," + mode_to_string(mode) + "): open failed: " 118 | + strerror()); 119 | } 120 | } 121 | static void check_peek(std::istream * is_p, const std::string& filename, std::ios_base::openmode mode) 122 | { 123 | bool peek_failed = true; 124 | try 125 | { 126 | is_p->peek(); 127 | peek_failed = is_p->fail(); 128 | } 129 | catch (std::ios_base::failure& e) {} 130 | if (peek_failed) 131 | { 132 | throw Exception(std::string("strict_fstream: open('") 133 | + filename + "'," + mode_to_string(mode) + "): peek failed: " 134 | + strerror()); 135 | } 136 | is_p->clear(); 137 | } 138 | }; // struct static_method_holder 139 | 140 | } // namespace detail 141 | 142 | class ifstream 143 | : public std::ifstream 144 | { 145 | public: 146 | ifstream() = default; 147 | ifstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 148 | { 149 | open(filename, mode); 150 | } 151 | void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 152 | { 153 | mode |= std::ios_base::in; 154 | exceptions(std::ios_base::badbit); 155 | detail::static_method_holder::check_mode(filename, mode); 156 | std::ifstream::open(filename, mode); 157 | detail::static_method_holder::check_open(this, filename, mode); 158 | detail::static_method_holder::check_peek(this, filename, mode); 159 | } 160 | }; // class ifstream 161 | 162 | class ofstream 163 | : public std::ofstream 164 | { 165 | public: 166 | ofstream() = default; 167 | ofstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out) 168 | { 169 | open(filename, mode); 170 | } 171 | void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out) 172 | { 173 | mode |= std::ios_base::out; 174 | exceptions(std::ios_base::badbit); 175 | detail::static_method_holder::check_mode(filename, mode); 176 | std::ofstream::open(filename, mode); 177 | detail::static_method_holder::check_open(this, filename, mode); 178 | } 179 | }; // class ofstream 180 | 181 | class fstream 182 | : public std::fstream 183 | { 184 | public: 185 | fstream() = default; 186 | fstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 187 | { 188 | open(filename, mode); 189 | } 190 | void open(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 191 | { 192 | if (! (mode & std::ios_base::out)) mode |= std::ios_base::in; 193 | exceptions(std::ios_base::badbit); 194 | detail::static_method_holder::check_mode(filename, mode); 195 | std::fstream::open(filename, mode); 196 | detail::static_method_holder::check_open(this, filename, mode); 197 | detail::static_method_holder::check_peek(this, filename, mode); 198 | } 199 | }; // class fstream 200 | 201 | } // namespace strict_fstream 202 | 203 | #endif 204 | -------------------------------------------------------------------------------- /example/include/zstr.hpp: -------------------------------------------------------------------------------- 1 | //--------------------------------------------------------- 2 | // Copyright 2015 Ontario Institute for Cancer Research 3 | // Written by Matei David (matei@cs.toronto.edu) 4 | //--------------------------------------------------------- 5 | 6 | // Reference: 7 | // http://stackoverflow.com/questions/14086417/how-to-write-custom-input-stream-in-c 8 | 9 | #ifndef __ZSTR_HPP 10 | #define __ZSTR_HPP 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "strict_fstream.hpp" 17 | 18 | namespace zstr 19 | { 20 | 21 | /// Exception class thrown by failed zlib operations. 22 | class Exception 23 | : public std::exception 24 | { 25 | public: 26 | Exception(z_stream * zstrm_p, int ret) 27 | : _msg("zlib: ") 28 | { 29 | switch (ret) 30 | { 31 | case Z_STREAM_ERROR: 32 | _msg += "Z_STREAM_ERROR: "; 33 | break; 34 | case Z_DATA_ERROR: 35 | _msg += "Z_DATA_ERROR: "; 36 | break; 37 | case Z_MEM_ERROR: 38 | _msg += "Z_MEM_ERROR: "; 39 | break; 40 | case Z_VERSION_ERROR: 41 | _msg += "Z_VERSION_ERROR: "; 42 | break; 43 | case Z_BUF_ERROR: 44 | _msg += "Z_BUF_ERROR: "; 45 | break; 46 | default: 47 | std::ostringstream oss; 48 | oss << ret; 49 | _msg += "[" + oss.str() + "]: "; 50 | break; 51 | } 52 | _msg += zstrm_p->msg; 53 | } 54 | Exception(const std::string msg) : _msg(msg) {} 55 | const char * what() const noexcept { return _msg.c_str(); } 56 | private: 57 | std::string _msg; 58 | }; // class Exception 59 | 60 | namespace detail 61 | { 62 | 63 | class z_stream_wrapper 64 | : public z_stream 65 | { 66 | public: 67 | z_stream_wrapper(bool _is_input = true, int _level = Z_DEFAULT_COMPRESSION) 68 | : is_input(_is_input) 69 | { 70 | this->zalloc = Z_NULL; 71 | this->zfree = Z_NULL; 72 | this->opaque = Z_NULL; 73 | int ret; 74 | if (is_input) 75 | { 76 | this->avail_in = 0; 77 | this->next_in = Z_NULL; 78 | ret = inflateInit2(this, 15+32); 79 | } 80 | else 81 | { 82 | ret = deflateInit2(this, _level, Z_DEFLATED, 15+16, 8, Z_DEFAULT_STRATEGY); 83 | } 84 | if (ret != Z_OK) throw Exception(this, ret); 85 | } 86 | ~z_stream_wrapper() 87 | { 88 | if (is_input) 89 | { 90 | inflateEnd(this); 91 | } 92 | else 93 | { 94 | deflateEnd(this); 95 | } 96 | } 97 | private: 98 | bool is_input; 99 | }; // class z_stream_wrapper 100 | 101 | } // namespace detail 102 | 103 | class istreambuf 104 | : public std::streambuf 105 | { 106 | public: 107 | istreambuf(std::streambuf * _sbuf_p, 108 | std::size_t _buff_size = default_buff_size, bool _auto_detect = true) 109 | : sbuf_p(_sbuf_p), 110 | zstrm_p(nullptr), 111 | buff_size(_buff_size), 112 | auto_detect(_auto_detect), 113 | auto_detect_run(false), 114 | is_text(false) 115 | { 116 | assert(sbuf_p); 117 | in_buff = new char [buff_size]; 118 | in_buff_start = in_buff; 119 | in_buff_end = in_buff; 120 | out_buff = new char [buff_size]; 121 | setg(out_buff, out_buff, out_buff); 122 | } 123 | 124 | istreambuf(const istreambuf &) = delete; 125 | istreambuf(istreambuf &&) = default; 126 | istreambuf & operator = (const istreambuf &) = delete; 127 | istreambuf & operator = (istreambuf &&) = default; 128 | 129 | virtual ~istreambuf() 130 | { 131 | delete [] in_buff; 132 | delete [] out_buff; 133 | if (zstrm_p) delete zstrm_p; 134 | } 135 | 136 | virtual std::streambuf::int_type underflow() 137 | { 138 | if (this->gptr() == this->egptr()) 139 | { 140 | // pointers for free region in output buffer 141 | char * out_buff_free_start = out_buff; 142 | do 143 | { 144 | // read more input if none available 145 | if (in_buff_start == in_buff_end) 146 | { 147 | // empty input buffer: refill from the start 148 | in_buff_start = in_buff; 149 | std::streamsize sz = sbuf_p->sgetn(in_buff, buff_size); 150 | in_buff_end = in_buff + sz; 151 | if (in_buff_end == in_buff_start) break; // end of input 152 | } 153 | // auto detect if the stream contains text or deflate data 154 | if (auto_detect && ! auto_detect_run) 155 | { 156 | auto_detect_run = true; 157 | unsigned char b0 = *reinterpret_cast< unsigned char * >(in_buff_start); 158 | unsigned char b1 = *reinterpret_cast< unsigned char * >(in_buff_start + 1); 159 | // Ref: 160 | // http://en.wikipedia.org/wiki/Gzip 161 | // http://stackoverflow.com/questions/9050260/what-does-a-zlib-header-look-like 162 | is_text = ! (in_buff_start + 2 <= in_buff_end 163 | && ((b0 == 0x1F && b1 == 0x8B) // gzip header 164 | || (b0 == 0x78 && (b1 == 0x01 // zlib header 165 | || b1 == 0x9C 166 | || b1 == 0xDA)))); 167 | } 168 | if (is_text) 169 | { 170 | // simply swap in_buff and out_buff, and adjust pointers 171 | assert(in_buff_start == in_buff); 172 | std::swap(in_buff, out_buff); 173 | out_buff_free_start = in_buff_end; 174 | in_buff_start = in_buff; 175 | in_buff_end = in_buff; 176 | } 177 | else 178 | { 179 | // run inflate() on input 180 | if (! zstrm_p) zstrm_p = new detail::z_stream_wrapper(true); 181 | zstrm_p->next_in = reinterpret_cast< decltype(zstrm_p->next_in) >(in_buff_start); 182 | zstrm_p->avail_in = in_buff_end - in_buff_start; 183 | zstrm_p->next_out = reinterpret_cast< decltype(zstrm_p->next_out) >(out_buff_free_start); 184 | zstrm_p->avail_out = (out_buff + buff_size) - out_buff_free_start; 185 | int ret = inflate(zstrm_p, Z_NO_FLUSH); 186 | // process return code 187 | if (ret != Z_OK && ret != Z_STREAM_END) throw Exception(zstrm_p, ret); 188 | // update in&out pointers following inflate() 189 | in_buff_start = reinterpret_cast< decltype(in_buff_start) >(zstrm_p->next_in); 190 | in_buff_end = in_buff_start + zstrm_p->avail_in; 191 | out_buff_free_start = reinterpret_cast< decltype(out_buff_free_start) >(zstrm_p->next_out); 192 | assert(out_buff_free_start + zstrm_p->avail_out == out_buff + buff_size); 193 | // if stream ended, deallocate inflator 194 | if (ret == Z_STREAM_END) 195 | { 196 | delete zstrm_p; 197 | zstrm_p = nullptr; 198 | } 199 | } 200 | } while (out_buff_free_start == out_buff); 201 | // 2 exit conditions: 202 | // - end of input: there might or might not be output available 203 | // - out_buff_free_start != out_buff: output available 204 | this->setg(out_buff, out_buff, out_buff_free_start); 205 | } 206 | return this->gptr() == this->egptr() 207 | ? traits_type::eof() 208 | : traits_type::to_int_type(*this->gptr()); 209 | } 210 | private: 211 | std::streambuf * sbuf_p; 212 | char * in_buff; 213 | char * in_buff_start; 214 | char * in_buff_end; 215 | char * out_buff; 216 | detail::z_stream_wrapper * zstrm_p; 217 | std::size_t buff_size; 218 | bool auto_detect; 219 | bool auto_detect_run; 220 | bool is_text; 221 | 222 | static const std::size_t default_buff_size = (std::size_t)1 << 20; 223 | }; // class istreambuf 224 | 225 | class ostreambuf 226 | : public std::streambuf 227 | { 228 | public: 229 | ostreambuf(std::streambuf * _sbuf_p, 230 | std::size_t _buff_size = default_buff_size, int _level = Z_DEFAULT_COMPRESSION) 231 | : sbuf_p(_sbuf_p), 232 | zstrm_p(new detail::z_stream_wrapper(false, _level)), 233 | buff_size(_buff_size) 234 | { 235 | assert(sbuf_p); 236 | in_buff = new char [buff_size]; 237 | out_buff = new char [buff_size]; 238 | setp(in_buff, in_buff + buff_size); 239 | } 240 | 241 | ostreambuf(const ostreambuf &) = delete; 242 | ostreambuf(ostreambuf &&) = default; 243 | ostreambuf & operator = (const ostreambuf &) = delete; 244 | ostreambuf & operator = (ostreambuf &&) = default; 245 | 246 | int deflate_loop(int flush) 247 | { 248 | while (true) 249 | { 250 | zstrm_p->next_out = reinterpret_cast< decltype(zstrm_p->next_out) >(out_buff); 251 | zstrm_p->avail_out = buff_size; 252 | int ret = deflate(zstrm_p, flush); 253 | if (ret != Z_OK && ret != Z_STREAM_END && ret != Z_BUF_ERROR) throw Exception(zstrm_p, ret); 254 | std::streamsize sz = sbuf_p->sputn(out_buff, reinterpret_cast< decltype(out_buff) >(zstrm_p->next_out) - out_buff); 255 | if (sz != reinterpret_cast< decltype(out_buff) >(zstrm_p->next_out) - out_buff) 256 | { 257 | // there was an error in the sink stream 258 | return -1; 259 | } 260 | if (ret == Z_STREAM_END || ret == Z_BUF_ERROR || sz == 0) 261 | { 262 | break; 263 | } 264 | } 265 | return 0; 266 | } 267 | 268 | virtual ~ostreambuf() 269 | { 270 | // flush the zlib stream 271 | // 272 | // NOTE: Errors here (sync() return value not 0) are ignored, because we 273 | // cannot throw in a destructor. This mirrors the behaviour of 274 | // std::basic_filebuf::~basic_filebuf(). To see an exception on error, 275 | // close the ofstream with an explicit call to close(), and do not rely 276 | // on the implicit call in the destructor. 277 | // 278 | sync(); 279 | delete [] in_buff; 280 | delete [] out_buff; 281 | delete zstrm_p; 282 | } 283 | virtual std::streambuf::int_type overflow(std::streambuf::int_type c = traits_type::eof()) 284 | { 285 | zstrm_p->next_in = reinterpret_cast< decltype(zstrm_p->next_in) >(pbase()); 286 | zstrm_p->avail_in = pptr() - pbase(); 287 | while (zstrm_p->avail_in > 0) 288 | { 289 | int r = deflate_loop(Z_NO_FLUSH); 290 | if (r != 0) 291 | { 292 | setp(nullptr, nullptr); 293 | return traits_type::eof(); 294 | } 295 | } 296 | setp(in_buff, in_buff + buff_size); 297 | return traits_type::eq_int_type(c, traits_type::eof()) ? traits_type::eof() : sputc(c); 298 | } 299 | virtual int sync() 300 | { 301 | // first, call overflow to clear in_buff 302 | overflow(); 303 | if (! pptr()) return -1; 304 | // then, call deflate asking to finish the zlib stream 305 | zstrm_p->next_in = nullptr; 306 | zstrm_p->avail_in = 0; 307 | if (deflate_loop(Z_FINISH) != 0) return -1; 308 | deflateReset(zstrm_p); 309 | return 0; 310 | } 311 | private: 312 | std::streambuf * sbuf_p; 313 | char * in_buff; 314 | char * out_buff; 315 | detail::z_stream_wrapper * zstrm_p; 316 | std::size_t buff_size; 317 | 318 | static const std::size_t default_buff_size = (std::size_t)1 << 20; 319 | }; // class ostreambuf 320 | 321 | class istream 322 | : public std::istream 323 | { 324 | public: 325 | istream(std::istream & is) 326 | : std::istream(new istreambuf(is.rdbuf())) 327 | { 328 | exceptions(std::ios_base::badbit); 329 | } 330 | explicit istream(std::streambuf * sbuf_p) 331 | : std::istream(new istreambuf(sbuf_p)) 332 | { 333 | exceptions(std::ios_base::badbit); 334 | } 335 | virtual ~istream() 336 | { 337 | delete rdbuf(); 338 | } 339 | }; // class istream 340 | 341 | class ostream 342 | : public std::ostream 343 | { 344 | public: 345 | ostream(std::ostream & os) 346 | : std::ostream(new ostreambuf(os.rdbuf())) 347 | { 348 | exceptions(std::ios_base::badbit); 349 | } 350 | explicit ostream(std::streambuf * sbuf_p) 351 | : std::ostream(new ostreambuf(sbuf_p)) 352 | { 353 | exceptions(std::ios_base::badbit); 354 | } 355 | virtual ~ostream() 356 | { 357 | delete rdbuf(); 358 | } 359 | }; // class ostream 360 | 361 | namespace detail 362 | { 363 | 364 | template < typename FStream_Type > 365 | struct strict_fstream_holder 366 | { 367 | strict_fstream_holder(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 368 | : _fs(filename, mode) 369 | {} 370 | FStream_Type _fs; 371 | }; // class strict_fstream_holder 372 | 373 | } // namespace detail 374 | 375 | class ifstream 376 | : private detail::strict_fstream_holder< strict_fstream::ifstream >, 377 | public std::istream 378 | { 379 | public: 380 | explicit ifstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::in) 381 | : detail::strict_fstream_holder< strict_fstream::ifstream >(filename, mode), 382 | std::istream(new istreambuf(_fs.rdbuf())) 383 | { 384 | exceptions(std::ios_base::badbit); 385 | } 386 | virtual ~ifstream() 387 | { 388 | if (rdbuf()) delete rdbuf(); 389 | } 390 | }; // class ifstream 391 | 392 | class ofstream 393 | : private detail::strict_fstream_holder< strict_fstream::ofstream >, 394 | public std::ostream 395 | { 396 | public: 397 | explicit ofstream(const std::string& filename, std::ios_base::openmode mode = std::ios_base::out) 398 | : detail::strict_fstream_holder< strict_fstream::ofstream >(filename, mode | std::ios_base::binary), 399 | std::ostream(new ostreambuf(_fs.rdbuf())) 400 | { 401 | exceptions(std::ios_base::badbit); 402 | } 403 | virtual ~ofstream() 404 | { 405 | if (rdbuf()) delete rdbuf(); 406 | } 407 | }; // class ofstream 408 | 409 | } // namespace zstr 410 | 411 | #endif 412 | -------------------------------------------------------------------------------- /nbt.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | Draw a crazy picture, 3 | Write a nutty poem, 4 | Sing a mumble-grumble song, 5 | Whistle through your comb. 6 | Do a loony-goony dance 7 | 'Cross the kitchen floor, 8 | Put something silly in the world 9 | That ain't been there before. 10 | - Shel Silverstein 11 | 12 | cpp-nbt 13 | The C++20 NBT serialization header no one asked for 14 | https://github.com/SpockBotMC/cpp-nbt 15 | Licensed under zlib, see repo License file for details 16 | */ 17 | 18 | #ifndef NBT_HPP 19 | #define NBT_HPP 20 | 21 | #ifndef NBT_MAP_TYPE 22 | #include 23 | #define NBT_MAP_TYPE boost::container::map 24 | #endif 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | 36 | namespace nbt { 37 | 38 | static constexpr std::string_view indent_step {" "}; 39 | 40 | enum TagType { 41 | TAG_END, 42 | TAG_BYTE, 43 | TAG_SHORT, 44 | TAG_INT, 45 | TAG_LONG, 46 | TAG_FLOAT, 47 | TAG_DOUBLE, 48 | TAG_BYTE_ARRAY, 49 | TAG_STRING, 50 | TAG_LIST, 51 | TAG_COMPOUND, 52 | TAG_INT_ARRAY, 53 | TAG_LONG_ARRAY 54 | }; 55 | 56 | 57 | typedef std::nullptr_t TagEnd; 58 | 59 | 60 | typedef std::int8_t TagByte; 61 | typedef std::int16_t TagShort; 62 | typedef std::int32_t TagInt; 63 | typedef std::int64_t TagLong; 64 | 65 | 66 | typedef float TagFloat; 67 | typedef double TagDouble; 68 | 69 | 70 | typedef std::vector TagByteArray; 71 | typedef std::vector TagIntArray; 72 | typedef std::vector TagLongArray; 73 | 74 | typedef std::string TagString; 75 | 76 | struct TagList; 77 | 78 | struct Tag; 79 | typedef NBT_MAP_TYPE TagCompound; 80 | 81 | namespace detail { 82 | 83 | template consteval std::string_view tag_name() { 84 | if constexpr(std::same_as) 85 | return "TagEnd"; 86 | else if constexpr(std::same_as) 87 | return "TagByte"; 88 | else if constexpr(std::same_as) 89 | return "TagShort"; 90 | else if constexpr(std::same_as) 91 | return "TagInt"; 92 | else if constexpr(std::same_as) 93 | return "TagLong"; 94 | else if constexpr(std::same_as) 95 | return "TagFloat"; 96 | else if constexpr(std::same_as) 97 | return "TagDouble"; 98 | else if constexpr(std::same_as) 99 | return "TagByteArray"; 100 | else if constexpr(std::same_as) 101 | return "TagString"; 102 | else if constexpr(std::same_as) 103 | return "TagList"; 104 | else if constexpr(std::same_as) 105 | return "TagCompound"; 106 | else if constexpr(std::same_as) 107 | return "TagIntArray"; 108 | else if constexpr(std::same_as) 109 | return "TagLongArray"; 110 | else 111 | return "UnknownTag"; 112 | } 113 | 114 | auto nbeswap(std::integral auto val) noexcept { 115 | if constexpr(std::endian::native == std::endian::big) 116 | return val; 117 | else 118 | return std::byteswap(val); 119 | } 120 | 121 | void encode(std::ostream& os, std::integral auto val) { 122 | auto out {nbeswap(val)}; 123 | os.write(reinterpret_cast(&out), sizeof(out)); 124 | } 125 | 126 | template T decode(std::istream& is) { 127 | T val; 128 | is.read(reinterpret_cast(&val), sizeof(val)); 129 | return nbeswap(val); 130 | } 131 | 132 | void encode(std::ostream& os, std::floating_point auto val) { 133 | using I = std::conditional_t; 134 | encode(os, std::bit_cast(val)); 135 | } 136 | 137 | template T decode(std::istream& is) { 138 | using I = std::conditional_t; 139 | return std::bit_cast(decode(is)); 140 | } 141 | 142 | template 143 | concept isTagEnd = std::same_as || std::same_as; 144 | 145 | void encode(std::ostream& os, isTagEnd auto = nullptr) { 146 | os.put(TAG_END); 147 | } // namespace detail 148 | 149 | template T decode(std::istream& is) { 150 | is.ignore(); 151 | return {}; 152 | } 153 | 154 | void print(std::ostream& os, isTagEnd auto = nullptr) { 155 | os << "."; 156 | } 157 | 158 | template 159 | concept isTagString = std::same_as; 160 | 161 | void encode(std::ostream& os, const isTagString auto& str) { 162 | encode(os, str.size()); 163 | os.write(str.data(), str.size()); 164 | } 165 | 166 | template T decode(std::istream& is) { 167 | auto len {decode(is)}; 168 | TagString str(len, '\0'); 169 | is.read(str.data(), len); 170 | return str; 171 | } 172 | 173 | template 174 | concept isPrint = std::integral || std::floating_point || isTagString; 175 | 176 | void print(std::ostream& os, const isPrint auto& val) { 177 | if constexpr(std::same_as, TagByte>) 178 | os << static_cast(val); 179 | else 180 | os << val; 181 | } 182 | 183 | template 184 | concept TagArray = std::same_as || 185 | std::same_as || std::same_as; 186 | 187 | void encode(std::ostream& os, const TagArray auto& vec) { 188 | encode(os, vec.size()); 189 | for(auto el : vec) 190 | encode(os, el); 191 | } 192 | 193 | template T decode(std::istream& is) { 194 | T vec(decode(is)); 195 | for(auto& el : vec) 196 | el = decode(is); 197 | return vec; 198 | } 199 | 200 | void print_vec(std::ostream& os, const auto& vec) { 201 | os << "{"; 202 | if(size_t size {vec.size()}; size) { 203 | os << +vec[0]; 204 | size_t end {size < 7 ? size : 3}; 205 | for(size_t i {1}; i < end; i++) 206 | os << ", " << +vec[i]; 207 | if(size >= 7) 208 | os << ", and " << size - 3 << " more"; 209 | } 210 | os << "}"; 211 | } 212 | 213 | void print(std::ostream& os, const TagArray auto& vec) { 214 | print_vec(os, vec); 215 | } 216 | 217 | template 218 | concept isTagList = std::same_as; 219 | 220 | template void encode(std::ostream& os, 221 | const T& val) requires isTagList || std::same_as { 222 | val.encode(os); 223 | } 224 | 225 | void print(std::ostream& os, const isTagList auto& val, 226 | const std::string& indent = "") { 227 | val.print(os, indent); 228 | } 229 | 230 | template T decode(std::istream& is); 231 | 232 | template 233 | concept isTagCompound = std::same_as; 234 | 235 | void encode(std::ostream& os, const isTagCompound auto& map); 236 | 237 | template T decode(std::istream& is); 238 | 239 | void print(std::ostream& os, const isTagCompound auto& map, 240 | const std::string& indent = ""); 241 | 242 | } // namespace detail 243 | 244 | struct TagList 245 | : public std::variant, std::vector, 246 | std::vector, std::vector, std::vector, 247 | std::vector, std::vector, 248 | std::vector, std::vector, 249 | std::vector, std::vector, 250 | std::vector, std::vector> { 251 | using variant::variant; 252 | 253 | TagList(std::istream& is) { 254 | decode(is); 255 | } 256 | 257 | void encode(std::ostream& os) const { 258 | detail::encode(os, index()); 259 | auto enc = [&](const auto& vec) { 260 | detail::encode(os, vec.size()); 261 | for(const auto& val : vec) 262 | detail::encode(os, val); 263 | }; 264 | std::visit(enc, *static_cast(this)); 265 | } 266 | 267 | void decode(std::istream& is) { 268 | switch(detail::decode(is)) { // clang-format off 269 | case TAG_END: *this = dec_vec(is); break; 270 | case TAG_BYTE: *this = dec_vec(is); break; 271 | case TAG_SHORT: *this = dec_vec(is); break; 272 | case TAG_INT: *this = dec_vec(is); break; 273 | case TAG_LONG: *this = dec_vec(is); break; 274 | case TAG_FLOAT: *this = dec_vec(is); break; 275 | case TAG_DOUBLE: *this = dec_vec(is); break; 276 | case TAG_BYTE_ARRAY: *this = dec_vec(is); break; 277 | case TAG_STRING: *this = dec_vec(is); break; 278 | case TAG_LIST: *this = dec_vec(is); break; 279 | case TAG_COMPOUND: *this = dec_vec(is); break; 280 | case TAG_INT_ARRAY: *this = dec_vec(is); break; 281 | case TAG_LONG_ARRAY: *this = dec_vec(is); break; 282 | default: throw std::runtime_error {"invalid tag type"}; 283 | } // clang-format on 284 | } 285 | 286 | void print(std::ostream& os, const std::string& indent) const { 287 | os << "(const T& vec) { 291 | using type = typename T::value_type; 292 | os << detail::tag_name() << "> {"; 293 | if constexpr(detail::isTagList || detail::isTagCompound) { 294 | for(const auto& tag : vec) { 295 | os << "\n" << next_indent; 296 | detail::print(os, tag, next_indent); 297 | } 298 | } else if constexpr(detail::TagArray) { 299 | for(const auto& tag : vec) { 300 | os << "\n" << next_indent; 301 | detail::print_vec(os, tag); 302 | } 303 | } else if constexpr(detail::isTagEnd) { 304 | os << "}"; 305 | } else if constexpr(detail::isTagString) { 306 | for(const auto& tag : vec) { 307 | os << "\n" << next_indent; 308 | detail::print(os, tag); 309 | } 310 | } else { 311 | os << "\n" << next_indent; 312 | detail::print_vec(os, vec); 313 | } 314 | os << "\n" << indent << "}"; 315 | }; 316 | std::visit(pr, *static_cast(this)); 317 | } 318 | 319 | private: 320 | template std::vector dec_vec(std::istream& is) { 321 | std::vector vec(detail::decode(is)); 322 | for(auto& el : vec) 323 | el = detail::decode(is); 324 | return vec; 325 | } 326 | }; 327 | 328 | struct Tag : public std::variant { 331 | using variant::variant; 332 | 333 | Tag(std::istream& is, size_t type) { 334 | decode(is, type); 335 | } 336 | 337 | void encode(std::ostream& os) const { 338 | auto enc = [&](const auto& val) { detail::encode(os, val); }; 339 | std::visit(enc, *static_cast(this)); 340 | } 341 | 342 | void decode(std::istream& is, size_t type) { 343 | switch(type) { // clang-format off 344 | case TAG_END: *this = detail::decode(is); break; 345 | case TAG_BYTE: *this = detail::decode(is); break; 346 | case TAG_SHORT: *this = detail::decode(is); break; 347 | case TAG_INT: *this = detail::decode(is); break; 348 | case TAG_LONG: *this = detail::decode(is); break; 349 | case TAG_FLOAT: *this = detail::decode(is); break; 350 | case TAG_DOUBLE: *this = detail::decode(is); break; 351 | case TAG_BYTE_ARRAY: *this = detail::decode(is); break; 352 | case TAG_STRING: *this = detail::decode(is); break; 353 | case TAG_LIST: *this = detail::decode(is); break; 354 | case TAG_COMPOUND: *this = detail::decode(is); break; 355 | case TAG_INT_ARRAY: *this = detail::decode(is); break; 356 | case TAG_LONG_ARRAY: *this = detail::decode(is); break; 357 | default: throw std::runtime_error {"invalid tag type"}; 358 | } //clang-format on 359 | } 360 | 361 | void print(std::ostream& os) { 362 | auto pr = [&](const auto& val){detail::print(os, val);}; 363 | std::visit(pr, *static_cast(this)); 364 | } 365 | 366 | private: 367 | friend std::ostream& operator << (std::ostream& os, Tag val) { 368 | val.print(os); 369 | return os; 370 | } 371 | }; 372 | 373 | namespace detail { 374 | 375 | template T decode(std::istream& is) { 376 | return TagList {is}; 377 | } 378 | 379 | void encode(std::ostream& os, const isTagCompound auto& map) { 380 | for(const auto& [key, tag] : map) { 381 | detail::encode(os, tag.index()); 382 | detail::encode(os, key); 383 | detail::encode(os, tag); 384 | } 385 | detail::encode(os, TAG_END); 386 | } 387 | 388 | template T decode(std::istream& is) { 389 | TagCompound map; 390 | auto type {decode(is)}; 391 | for(; type != TAG_END; type = decode(is)) { 392 | auto key {decode(is)}; 393 | map[key] = Tag {is, static_cast(type)}; 394 | } 395 | return map; 396 | } 397 | 398 | void print(std::ostream& os, const isTagCompound auto& map, 399 | const std::string& indent) { 400 | std::string next_indent {indent + std::string{indent_step}}; 401 | os << " {"; 402 | auto pr = [&](const T& tag) { 403 | if constexpr(detail::isTagList || detail::isTagCompound) { 404 | print(os, tag, next_indent); 405 | } else { 406 | os << "<" << detail::tag_name() << "> "; 407 | print(os, tag); 408 | } 409 | }; 410 | for(const auto& [name, tag] : map) { 411 | os << "\n" << next_indent << name << ": "; 412 | std::visit(pr, tag); 413 | } 414 | if(map.empty()) 415 | os << "}"; 416 | else 417 | os << "\n" << indent << "}"; 418 | } 419 | 420 | } // namespace detail 421 | 422 | struct NBTData { 423 | TagString name; 424 | TagCompound tags; 425 | }; 426 | 427 | struct NBT { 428 | std::optional data; 429 | 430 | NBT() = default; 431 | 432 | NBT(const TagString& name, const TagCompound& tags = {}) 433 | : data {{name, tags}} {} 434 | NBT(const TagCompound& tags, const TagString& name = {}) 435 | : data {{name, tags}} {} 436 | 437 | NBT(std::istream &is) { 438 | decode(is); 439 | } 440 | NBT(std::istream &&is) { 441 | decode(is); 442 | } 443 | 444 | void decode(std::istream& is) { 445 | auto type {detail::decode(is)}; 446 | if(type == TAG_COMPOUND) { 447 | auto name {detail::decode(is)}; 448 | data = NBTData {std::move(name), detail::decode(is)}; 449 | } else if(type != TAG_END) { 450 | throw std::runtime_error {"invalid tag type"}; 451 | } 452 | } 453 | 454 | void encode(std::ostream& os) const { 455 | if(data) { 456 | detail::encode(os, TAG_COMPOUND); 457 | detail::encode(os, data->name); 458 | detail::encode(os, data->tags); 459 | } else { 460 | detail::encode(os); 461 | } 462 | } 463 | 464 | void print(std::ostream& os) const { 465 | if(data) { 466 | os << "\"" << data->name << "\"\n"; 467 | detail::print(os, data->tags); 468 | } else { 469 | os << "Empty NBT"; 470 | } 471 | } 472 | 473 | private: 474 | friend std::ostream& operator << (std::ostream& os, NBT val) { 475 | val.print(os); 476 | return os; 477 | } 478 | }; 479 | 480 | } // namespace nbt 481 | 482 | #endif // NBT_HPP 483 | -------------------------------------------------------------------------------- /tests/data/arrays.nbt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Array Test 4 | byte array int array 5 | long array -------------------------------------------------------------------------------- /tests/data/compound.nbt: -------------------------------------------------------------------------------- 1 | 2 | Compound Test 3 | compound -------------------------------------------------------------------------------- /tests/data/list.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpockBotMC/cpp-nbt/ecfff0fb7654044cfa2398e6d46222fb07a6448d/tests/data/list.nbt -------------------------------------------------------------------------------- /tests/data/numerics.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SpockBotMC/cpp-nbt/ecfff0fb7654044cfa2398e6d46222fb07a6448d/tests/data/numerics.nbt -------------------------------------------------------------------------------- /tests/data/printed_list.txt: -------------------------------------------------------------------------------- 1 | "List Test" 2 | { 3 | list: { 4 | {} 5 | } 6 | { 7 | {1, 2, 3} 8 | } 9 | { 10 | {256, 512, 768} 11 | } 12 | { 13 | {65536, 131072, 196608} 14 | } 15 | { 16 | {4294967296, 8589934592, 12884901888} 17 | } 18 | { 19 | {0.1, 0.2, 0.3} 20 | } 21 | { 22 | {0.1, 0.2, 0.3} 23 | } 24 | { 25 | {1} 26 | {2} 27 | {3} 28 | } 29 | { 30 | {65536} 31 | {131072} 32 | {196608} 33 | } 34 | { 35 | {4294967296} 36 | {8589934592} 37 | {12884901888} 38 | } 39 | { 40 | String #1 41 | String #2 42 | String #3 43 | } 44 | { 45 | { 46 | name: Compound #1 47 | } 48 | { 49 | name: Compound #2 50 | } 51 | { 52 | name: Compound #3 53 | } 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /tests/data/string.nbt: -------------------------------------------------------------------------------- 1 | 2 | String TeststringThis is a string 🙂 -------------------------------------------------------------------------------- /tests/test_arrays.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #undef NDEBUG 5 | #include 6 | 7 | #include "nbt.hpp" 8 | 9 | int main() { 10 | 11 | nbt::TagByteArray test_byte_array {0, 1, 2, 3}; 12 | nbt::TagIntArray test_int_array {0, 1 << 16, 2 << 16, 3 << 16}; 13 | nbt::TagLongArray test_long_array {0, 1LL << 32, 2LL << 32, 3LL << 32}; 14 | 15 | nbt::NBT root {"Array Test", 16 | { 17 | {"byte array", test_byte_array}, 18 | {"int array", test_int_array}, 19 | {"long array", test_long_array}, 20 | }}; 21 | 22 | 23 | std::stringstream good_buffer; 24 | good_buffer << std::ifstream {"arrays.nbt"}.rdbuf(); 25 | 26 | std::stringstream test_buffer; 27 | root.encode(test_buffer); 28 | 29 | assert(("binary_arrays", good_buffer.str() == test_buffer.str())); 30 | 31 | 32 | nbt::NBT file {good_buffer}; 33 | 34 | assert(("test_byte_arry", 35 | root.data->tags.at("byte array") == file.data->tags.at("byte array"))); 36 | 37 | assert(("test_int_array", 38 | root.data->tags.at("int array") == file.data->tags.at("int array"))); 39 | 40 | assert(("test_long_array", 41 | root.data->tags.at("long array") == file.data->tags.at("long array"))); 42 | 43 | 44 | std::stringstream print_buffer; 45 | print_buffer << root; 46 | 47 | const std::string printed {print_buffer.str()}; 48 | 49 | assert(("printed_byte_array", 50 | printed.find("byte array: {0, 1, 2, 3}") != printed.npos)); 51 | 52 | assert(("printed_int_array", 53 | printed.find("int array: {0, 65536, 131072, 196608}") != 54 | printed.npos)); 55 | 56 | assert(("printed_long_array", 57 | printed.find("long array: {0, 4294967296, 8589934592, " 58 | "12884901888}") != printed.npos)); 59 | } 60 | -------------------------------------------------------------------------------- /tests/test_compound.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #undef NDEBUG 5 | #include 6 | 7 | #include "nbt.hpp" 8 | 9 | int main() { 10 | 11 | nbt::TagCompound test_compound {}; 12 | 13 | nbt::NBT root {"Compound Test", {{"compound", test_compound}}}; 14 | 15 | 16 | std::stringstream good_buffer; 17 | good_buffer << std::ifstream {"compound.nbt"}.rdbuf(); 18 | 19 | std::stringstream test_buffer; 20 | root.encode(test_buffer); 21 | 22 | assert(("binary_compound", good_buffer.str() == test_buffer.str())); 23 | 24 | root.decode(test_buffer); 25 | 26 | std::stringstream print_buffer; 27 | print_buffer << root; 28 | 29 | const std::string expected { 30 | "\"Compound Test\"\n" 31 | " {\n" 32 | " compound: {}\n" 33 | "}"}; 34 | 35 | assert(("printed_compound", expected == print_buffer.str())); 36 | } 37 | -------------------------------------------------------------------------------- /tests/test_list.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #undef NDEBUG 5 | #include 6 | 7 | #include "nbt.hpp" 8 | 9 | int main() { 10 | 11 | std::vector test_list { 12 | std::vector {{}}, 13 | std::vector { 14 | 1, 15 | 2, 16 | 3, 17 | }, 18 | std::vector { 19 | 1 << 8, 20 | 2 << 8, 21 | 3 << 8, 22 | }, 23 | std::vector { 24 | 1 << 16, 25 | 2 << 16, 26 | 3 << 16, 27 | }, 28 | std::vector { 29 | 1LL << 32, 30 | 2LL << 32, 31 | 3LL << 32, 32 | }, 33 | std::vector { 34 | 0.1f, 35 | 0.2f, 36 | 0.3f, 37 | }, 38 | std::vector { 39 | 0.1f, 40 | 0.2f, 41 | 0.3f, 42 | }, 43 | std::vector { 44 | {1}, 45 | {2}, 46 | {3}, 47 | }, 48 | std::vector { 49 | {1 << 16}, 50 | {2 << 16}, 51 | {3 << 16}, 52 | }, 53 | std::vector { 54 | {1LL << 32}, 55 | {2LL << 32}, 56 | {3LL << 32}, 57 | }, 58 | std::vector { 59 | "String #1", 60 | "String #2", 61 | "String #3", 62 | }, 63 | std::vector { 64 | {{"name", "Compound #1"}}, 65 | {{"name", "Compound #2"}}, 66 | {{"name", "Compound #3"}}, 67 | }, 68 | }; 69 | 70 | nbt::NBT nbt {"List Test", {{"list", test_list}}}; 71 | 72 | std::stringstream good_buffer; 73 | good_buffer << std::ifstream {"list.nbt"}.rdbuf(); 74 | 75 | std::stringstream test_buffer; 76 | nbt.encode(test_buffer); 77 | 78 | assert(("binary_list", good_buffer.str() == test_buffer.str())); 79 | 80 | nbt::NBT file {good_buffer}; 81 | 82 | assert(nbt.data->tags.at("list") == file.data->tags.at("list")); 83 | 84 | 85 | std::stringstream print_buffer; 86 | print_buffer << nbt; 87 | std::stringstream good_print; 88 | 89 | good_print << std::ifstream {"printed_list.txt"}.rdbuf(); 90 | 91 | assert(("printed_list", print_buffer.str() == good_print.str())); 92 | } 93 | -------------------------------------------------------------------------------- /tests/test_misc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #undef NDEBUG 6 | #include 7 | 8 | #include "nbt.hpp" 9 | 10 | int main() { 11 | 12 | // TagCompound store/get 13 | nbt::TagCompound tag_compound { 14 | {"Hello", "World"}, 15 | }; 16 | 17 | std::ostringstream {} << tag_compound; 18 | 19 | // NBT default constructor 20 | std::ostringstream os {}; 21 | nbt::NBT {}.encode(os); 22 | 23 | // NBT name constructor and named empty encode 24 | nbt::NBT {"NBT"}.encode(os); 25 | 26 | // NBT r-value reference constructor and invalid tag exception 27 | try { 28 | nbt::NBT {std::istringstream {"not a tag"}}; 29 | } catch(std::exception&) { 30 | } 31 | 32 | // NBT TagCompound constructor 33 | nbt::NBT {tag_compound}; 34 | 35 | nbt::TagCompound invalid; 36 | struct make_invalid_int { 37 | nullptr_t val; 38 | operator int() { 39 | throw std::runtime_error {"now you're invalid"}; 40 | } 41 | }; 42 | 43 | try { 44 | invalid["invalid_field"] = make_invalid_int {}; 45 | } catch(std::exception&) { 46 | } 47 | 48 | try { 49 | std::stringstream os {}; 50 | nbt::detail::encode(os, invalid); 51 | } catch(std::exception&) { 52 | } 53 | 54 | try { 55 | std::ostringstream {} << invalid; 56 | } catch(std::exception&) { 57 | } 58 | 59 | std::stringstream invalid_buf {}; 60 | 61 | nbt::detail::encode(invalid_buf, nbt::TAG_COMPOUND); 62 | nbt::detail::encode(invalid_buf, "invalid_nbt"); 63 | nbt::detail::encode(invalid_buf, -1); 64 | nbt::detail::encode(invalid_buf, nbt::TAG_END); 65 | 66 | try { 67 | nbt::NBT {invalid_buf}; 68 | } catch(std::exception&) { 69 | } 70 | 71 | std::stringstream multi_TagEnd_list {}; 72 | nbt::detail::encode(multi_TagEnd_list, nbt::TAG_END); 73 | nbt::detail::encode(multi_TagEnd_list, 10); 74 | nbt::TagList end_list {nbt::detail::decode(multi_TagEnd_list)}; 75 | 76 | std::stringstream invalid_list_buf {}; 77 | 78 | nbt::detail::encode(invalid_list_buf, -1); 79 | nbt::detail::encode(invalid_list_buf, 10); 80 | 81 | try { 82 | nbt::detail::decode(invalid_list_buf); 83 | } catch(std::exception&) { 84 | } 85 | }; 86 | -------------------------------------------------------------------------------- /tests/test_numerics.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #undef NDEBUG 5 | #include 6 | 7 | #include "nbt.hpp" 8 | 9 | int main() { 10 | 11 | nbt::TagByte test_byte {1}; 12 | nbt::TagShort test_short {1 << 8}; 13 | nbt::TagInt test_int {1 << 16}; 14 | nbt::TagLong test_long {1LL << 32}; 15 | 16 | nbt::TagFloat test_float {0.1f}; 17 | nbt::TagDouble test_double {0.2}; 18 | 19 | nbt::NBT nbt { 20 | "Numeric Test", 21 | { 22 | {"byte", test_byte}, 23 | {"short", test_short}, 24 | {"int", test_int}, 25 | {"long", test_long}, 26 | {"float", test_float}, 27 | {"double", test_double}, 28 | }, 29 | }; 30 | 31 | 32 | std::stringstream good_buffer; 33 | good_buffer << std::ifstream {"numerics.nbt"}.rdbuf(); 34 | 35 | std::stringstream test_buffer; 36 | nbt.encode(test_buffer); 37 | 38 | assert(("binary_numerics", good_buffer.str() == test_buffer.str())); 39 | 40 | 41 | nbt::NBT file {good_buffer}; 42 | 43 | assert( 44 | ("test_byte", nbt.data->tags.at("byte") == file.data->tags.at("byte"))); 45 | 46 | assert(("test_short", 47 | nbt.data->tags.at("short") == file.data->tags.at("short"))); 48 | 49 | assert(("test_int", nbt.data->tags.at("int") == file.data->tags.at("int"))); 50 | 51 | assert( 52 | ("test_long", nbt.data->tags.at("long") == file.data->tags.at("long"))); 53 | 54 | assert(("test_float", 55 | nbt.data->tags.at("float") == file.data->tags.at("float"))); 56 | 57 | assert(("test_double", 58 | nbt.data->tags.at("double") == file.data->tags.at("double"))); 59 | 60 | 61 | std::stringstream print_buffer; 62 | print_buffer << nbt; 63 | const std::string printed {print_buffer.str()}; 64 | 65 | assert(("printed_byte", printed.find("byte: 1") != printed.npos)); 66 | 67 | assert( 68 | ("printed_short", printed.find("short: 256") != printed.npos)); 69 | 70 | assert(("printed_int", printed.find("int: 65536") != printed.npos)); 71 | 72 | assert(("printed_long", 73 | printed.find("long: 4294967296") != printed.npos)); 74 | 75 | assert(("printed_long", 76 | printed.find("long: 4294967296") != printed.npos)); 77 | 78 | assert( 79 | ("printed_float", printed.find("float: 0.1") != printed.npos)); 80 | 81 | assert(("printed_double", 82 | printed.find("double: 0.2") != printed.npos)); 83 | } 84 | -------------------------------------------------------------------------------- /tests/test_string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #undef NDEBUG 5 | #include 6 | 7 | #include "nbt.hpp" 8 | 9 | int main() { 10 | 11 | nbt::TagString test_string {"This is a string 🙂"}; 12 | 13 | nbt::NBT nbt { 14 | "String Test", 15 | { 16 | {"string", test_string}, 17 | }, 18 | }; 19 | 20 | std::stringstream good_buffer; 21 | good_buffer << std::ifstream {"string.nbt", std::ios::binary}.rdbuf(); 22 | 23 | std::stringstream test_buffer; 24 | nbt.encode(test_buffer); 25 | 26 | assert(("binary_string", good_buffer.str() == test_buffer.str())); 27 | 28 | 29 | nbt::NBT file {good_buffer}; 30 | 31 | assert(("test_string", 32 | nbt.data->tags.at("string") == file.data->tags.at("string"))); 33 | 34 | std::stringstream print_buffer; 35 | 36 | 37 | print_buffer << nbt; 38 | 39 | const std::string expected { 40 | "\"String Test\"\n" 41 | " {\n" 42 | " string: This is a string 🙂\n" 43 | "}"}; 44 | 45 | assert(("printed_string", expected == print_buffer.str())); 46 | } 47 | --------------------------------------------------------------------------------