├── .devcontainer ├── .bashrc ├── Dockerfile ├── devcontainer.json └── meson │ └── cross │ ├── arm-linux-gnueabihf │ ├── x86_64 │ ├── x86_64-alpine-linux-musl │ ├── x86_64-linux-gnu │ └── x86_64-w64-mingw32 ├── .gitignore ├── .gitlab-ci.yml ├── .vscode ├── c_cpp_properties.json ├── settings.json └── tasks.json ├── LICENSE ├── README.md ├── gcovr.cfg ├── include ├── meson.build └── nbt.hpp ├── meson.build ├── meson_options.txt ├── src ├── meson.build ├── nbt.cpp ├── nbt_internal.hpp └── nbtc.cpp ├── subprojects └── .ignore └── test ├── bedrock_level.dat ├── bigtest.nbt ├── issue2.cpp ├── issue3.cpp ├── meson.build ├── nbt.cpp └── test.hpp /.devcontainer/.bashrc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | PS1='${debian_chroot:+($debian_chroot)}\[\033[01;91m\]\u@\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ ' 4 | umask 022 5 | export LS_OPTIONS='--color=auto' 6 | eval "`dircolors`" 7 | alias ls='ls $LS_OPTIONS' 8 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:buster 2 | 3 | RUN apt update && apt install -y build-essential crossbuild-essential-amd64 crossbuild-essential-armhf mingw-w64 \ 4 | python3 python3-pip python3-setuptools python3-wheel ninja-build \ 5 | wget cmake xxd valgrind gcovr lcov git cppcheck gdb \ 6 | && pip3 install meson 7 | 8 | COPY meson /usr/local/share/meson 9 | COPY .bashrc /root/.bashrc 10 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "C++", 3 | "dockerFile": "Dockerfile", 4 | 5 | "runArgs": [ 6 | "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" 7 | ], 8 | 9 | "settings": { 10 | "terminal.integrated.shell.linux": "/bin/bash" 11 | }, 12 | 13 | "extensions": [ 14 | "ms-vscode.cpptools", 15 | "yzhang.markdown-all-in-one", 16 | "asabil.meson" 17 | ] 18 | } -------------------------------------------------------------------------------- /.devcontainer/meson/cross/arm-linux-gnueabihf: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = 'arm-linux-gnueabihf-gcc' 3 | cpp = 'arm-linux-gnueabihf-g++' 4 | ar = 'arm-linux-gnueabihf-ar' 5 | strip = 'arm-linux-gnueabihf-strip' 6 | 7 | [host_machine] 8 | system = 'linux' 9 | cpu_family = 'arm' 10 | cpu = 'armv7' 11 | endian = 'little' 12 | -------------------------------------------------------------------------------- /.devcontainer/meson/cross/x86_64: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = 'x86_64-linux-gnu-gcc' 3 | cpp = 'x86_64-linux-gnu-g++' 4 | ar = 'x86_64-linux-gnu-ar' 5 | strip = 'x86_64-linux-gnu-strip' 6 | pkgconfig = 'x86_64-linux-gnu-pkg-config' 7 | 8 | [host_machine] 9 | system = 'linux' 10 | cpu_family = 'x86_64' 11 | cpu = 'x86_64' 12 | endian = 'little' 13 | -------------------------------------------------------------------------------- /.devcontainer/meson/cross/x86_64-alpine-linux-musl: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = '/usr/bin/x86_64-alpine-linux-musl-gcc' 3 | cpp = '/usr/bin/x86_64-alpine-linux-musl-g++' 4 | ar = '/usr/bin/x86_64-alpine-linux-musl-gcc-ar' 5 | strip = '/usr/bin/x86_64-alpine-linux-musl-strip' 6 | 7 | [host_machine] 8 | system = 'linux' 9 | cpu_family = 'x86_64' 10 | cpu = 'x86_64' 11 | endian = 'little' 12 | -------------------------------------------------------------------------------- /.devcontainer/meson/cross/x86_64-linux-gnu: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = 'x86_64-linux-gnu-gcc' 3 | cpp = 'x86_64-linux-gnu-g++' 4 | ar = 'x86_64-linux-gnu-ar' 5 | strip = 'x86_64-linux-gnu-strip' 6 | pkgconfig = 'x86_64-linux-gnu-pkg-config' 7 | 8 | [host_machine] 9 | system = 'linux' 10 | cpu_family = 'x86_64' 11 | cpu = 'x86_64' 12 | endian = 'little' 13 | -------------------------------------------------------------------------------- /.devcontainer/meson/cross/x86_64-w64-mingw32: -------------------------------------------------------------------------------- 1 | [binaries] 2 | c = 'x86_64-w64-mingw32-gcc' 3 | cpp = 'x86_64-w64-mingw32-g++' 4 | ar = 'x86_64-w64-mingw32-ar' 5 | strip = 'x86_64-w64-mingw32-strip' 6 | pkgconfig = 'x86_64-w64-mingw32-pkg-config' 7 | windres = 'x86_64-w64-mingw32-windres' 8 | 9 | [host_machine] 10 | system = 'windows' 11 | cpu_family = 'x86_64' 12 | cpu = 'x86_64' 13 | endian = 'little' 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /cross 3 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: reg.handtruth.com/roots/cpp-meson/build:debian 2 | 3 | stages: 4 | - setup 5 | - build 6 | - test 7 | - analysis 8 | - package 9 | 10 | variables: 11 | GIT_SUBMODULE_STRATEGY: normal 12 | 13 | Restore:x86_64-linux-gnu: &restore 14 | stage: setup 15 | variables: 16 | TARGET: x86_64-linux-gnu 17 | script: 18 | - meson -Db_coverage=true build/$TARGET --cross-file=$TARGET 19 | tags: ["default"] 20 | artifacts: 21 | paths: 22 | - build 23 | expire_in: 2 hrs 24 | 25 | Restore:arm-linux-gnueabihf: 26 | <<: *restore 27 | variables: 28 | TARGET: arm-linux-gnueabihf 29 | 30 | Restore:x86_64-w64-mingw32: 31 | <<: *restore 32 | variables: 33 | TARGET: x86_64-w64-mingw32 34 | 35 | Restore:x86_64-alpine-linux-musl: 36 | <<: *restore 37 | image: reg.handtruth.com/roots/cpp-meson/build:alpine 38 | variables: 39 | TARGET: x86_64-alpine-linux-musl 40 | 41 | Build:x86_64-linux-gnu: &build 42 | stage: build 43 | variables: 44 | TARGET: x86_64-linux-gnu 45 | script: 46 | - cd build/$TARGET 47 | - ninja 48 | tags: ["default"] 49 | artifacts: 50 | paths: 51 | - build 52 | expire_in: 2 hrs 53 | dependencies: 54 | - Restore:x86_64-linux-gnu 55 | 56 | Build:arm-linux-gnueabihf: 57 | <<: *build 58 | variables: 59 | TARGET: arm-linux-gnueabihf 60 | dependencies: 61 | - Restore:arm-linux-gnueabihf 62 | 63 | Build:x86_64-alpine-linux-musl: 64 | <<: *build 65 | image: reg.handtruth.com/roots/cpp-meson/build:alpine 66 | variables: 67 | TARGET: x86_64-alpine-linux-musl 68 | dependencies: 69 | - Restore:x86_64-alpine-linux-musl 70 | 71 | Build:x86_64-w64-mingw32: 72 | <<: *build 73 | variables: 74 | TARGET: x86_64-w64-mingw32 75 | dependencies: 76 | - Restore:x86_64-w64-mingw32 77 | 78 | Test:x86_64-linux-gnu: &test 79 | stage: test 80 | variables: 81 | TARGET: x86_64-linux-gnu 82 | MESON_TESTTHREADS: 1 83 | script: 84 | - cd build/$TARGET 85 | - meson test --print-errorlogs --suite regular --wrap='valgrind --error-exitcode=1 --leak-check=full --tool=memcheck --track-origins=yes' 86 | - cat meson-logs/testlog-valgrind.txt 87 | tags: ["default"] 88 | artifacts: 89 | paths: 90 | - build 91 | expire_in: 2 hrs 92 | dependencies: 93 | - Restore:x86_64-linux-gnu 94 | - Build:x86_64-linux-gnu 95 | 96 | Test:arm-linux-gnueabihf: 97 | <<: *test 98 | image: reg.handtruth.com/roots/cpp-meson/build:debian-armhf 99 | variables: 100 | TARGET: arm-linux-gnueabihf 101 | tags: ["armhf"] 102 | dependencies: 103 | - Restore:arm-linux-gnueabihf 104 | - Build:arm-linux-gnueabihf 105 | when: manual 106 | 107 | Test:x86_64-alpine-linux-musl: 108 | <<: *test 109 | image: reg.handtruth.com/roots/cpp-meson/build:alpine 110 | variables: 111 | TARGET: x86_64-alpine-linux-musl 112 | dependencies: 113 | - Restore:x86_64-alpine-linux-musl 114 | - Build:x86_64-alpine-linux-musl 115 | 116 | Coverage: &coverage 117 | stage: analysis 118 | variables: 119 | TARGET: x86_64-linux-gnu 120 | script: 121 | - cd build/$TARGET 122 | - ninja coverage-text 123 | - cat meson-logs/coverage.txt 124 | coverage: '/^TOTAL\s+\S+\s+\S+\s+(\d+\.?\d+)%/' 125 | tags: ["default"] 126 | artifacts: 127 | paths: 128 | - build 129 | expire_in: 2 hrs 130 | dependencies: 131 | - Restore:x86_64-linux-gnu 132 | - Build:x86_64-linux-gnu 133 | - Test:x86_64-linux-gnu 134 | 135 | Check: 136 | stage: analysis 137 | variables: 138 | TARGET: x86_64-linux-gnu 139 | script: 140 | - cd build/$TARGET 141 | - ninja cppcheck 142 | coverage: '/^TOTAL\s+\S+\s+\S+\s+(\d+\.?\d+)%/' 143 | tags: ["default"] 144 | artifacts: 145 | paths: 146 | - build/x86_64-linux-gnu/cppcheck.log 147 | expire_in: 1 week 148 | dependencies: 149 | - Restore:x86_64-linux-gnu 150 | 151 | Package:x86_64-linux-gnu: &package 152 | stage: package 153 | variables: 154 | TARGET: x86_64-linux-gnu 155 | script: 156 | - meson -Dprefix=`pwd`/dist/$TARGET -Dbuildtype=release -Doptimization=3 --cross-file=$TARGET package/$TARGET 157 | - cd package/$TARGET 158 | - ninja 159 | - ninja install 160 | tags: ["default"] 161 | artifacts: 162 | paths: 163 | - dist 164 | expire_in: 1 mos 165 | dependencies: [] 166 | 167 | Package:arm-linux-gnueabihf: 168 | <<: *package 169 | variables: 170 | TARGET: arm-linux-gnueabihf 171 | 172 | Package:x86_64-w64-mingw32: 173 | <<: *package 174 | variables: 175 | TARGET: x86_64-w64-mingw32 176 | 177 | Package:x86_64-alpine-linux-musl: 178 | <<: *package 179 | image: reg.handtruth.com/roots/cpp-meson/build:alpine 180 | variables: 181 | TARGET: x86_64-alpine-linux-musl 182 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "compilerPath": "/usr/bin/gcc", 10 | "cStandard": "c11", 11 | "cppStandard": "c++17", 12 | "intelliSenseMode": "gcc-x64", 13 | "compileCommands": "${workspaceFolder}/build/compile_commands.json" 14 | } 15 | ], 16 | "version": 4 17 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "array": "cpp", 4 | "atomic": "cpp", 5 | "*.tcc": "cpp", 6 | "cctype": "cpp", 7 | "clocale": "cpp", 8 | "cmath": "cpp", 9 | "cstdarg": "cpp", 10 | "cstddef": "cpp", 11 | "cstdint": "cpp", 12 | "cstdio": "cpp", 13 | "cstdlib": "cpp", 14 | "cwchar": "cpp", 15 | "cwctype": "cpp", 16 | "deque": "cpp", 17 | "unordered_map": "cpp", 18 | "vector": "cpp", 19 | "exception": "cpp", 20 | "algorithm": "cpp", 21 | "functional": "cpp", 22 | "iterator": "cpp", 23 | "memory": "cpp", 24 | "memory_resource": "cpp", 25 | "numeric": "cpp", 26 | "optional": "cpp", 27 | "random": "cpp", 28 | "string": "cpp", 29 | "string_view": "cpp", 30 | "system_error": "cpp", 31 | "tuple": "cpp", 32 | "type_traits": "cpp", 33 | "utility": "cpp", 34 | "fstream": "cpp", 35 | "initializer_list": "cpp", 36 | "iosfwd": "cpp", 37 | "iostream": "cpp", 38 | "istream": "cpp", 39 | "limits": "cpp", 40 | "new": "cpp", 41 | "ostream": "cpp", 42 | "sstream": "cpp", 43 | "stdexcept": "cpp", 44 | "streambuf": "cpp", 45 | "typeinfo": "cpp", 46 | "config.cpp.in": "cpp", 47 | "cinttypes": "cpp", 48 | "map": "cpp" 49 | } 50 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // Документацию по формату tasks.json см. 3 | // по адресу https://go.microsoft.com/fwlink/?LinkId=733558 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "meson", 8 | "mode": "build", 9 | "problemMatcher": [ 10 | "$gcc" 11 | ], 12 | "group": { 13 | "kind": "build", 14 | "isDefault": true 15 | } 16 | }, 17 | { 18 | "type": "meson", 19 | "mode": "test", 20 | "problemMatcher": [ 21 | "$gcc" 22 | ], 23 | "group": { 24 | "kind": "test", 25 | "isDefault": true 26 | } 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ktlo 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | NBT library for C++17 2 | ======================================= 3 | 4 | You can read and save files in NBT format with this library. It supports different formats: 5 | NBT Java Edition, NBT Bedrock Edition (both network and file formats) and Mojangson. 6 | 7 | Usage 8 | --------------------------------------- 9 | 10 | ```c++ 11 | #include 12 | #include 13 | 14 | using namespace nbt; 15 | 16 | int main() { 17 | std::ifstream input("java.nbt"); 18 | tags::compound_tag root; 19 | input >> contexts::java >> root; 20 | std::ofstream output("mojangson.txt"); 21 | output << contexts::mojangson << root; 22 | } 23 | ``` 24 | 25 | By default all lists have their own specialization for each tag. If you want to get something simple 26 | like `std::vector` as list values you can use `nbt::tags::tag_compound::make_heavy` 27 | and `nbt::tags::list_tag::as_tags`. After that all lists in NBT subtree would be 28 | `nbt::tags::tag_list_tag` objects. 29 | 30 | NBTC 31 | --------------------------------------- 32 | 33 | There are also a simple converter between NBT formats: `nbtc`. 34 | 35 | ./nbtc FROM TO 36 | 37 | ### Parameters: 38 | - FROM read stdin as FROM NBT format 39 | - TO write NBT tag to stdout as TO format 40 | 41 | ### NBT formats: 42 | - *java* Minecraft Java Edition NBT 43 | - *bedrock* or *bedrock-disk* Minecraft Bedrock Edition storage NBT format 44 | - *bedrock-net* Minecraft Bedrock Edition network NBT format 45 | - *mojangson* text NBT representation 46 | - *kbt* HandTruth NBT extension 47 | -------------------------------------------------------------------------------- /gcovr.cfg: -------------------------------------------------------------------------------- 1 | exclude = '*/test/*' 2 | -------------------------------------------------------------------------------- /include/meson.build: -------------------------------------------------------------------------------- 1 | includes = include_directories('.') 2 | 3 | install_headers([ 4 | 'nbt.hpp' 5 | ]) 6 | -------------------------------------------------------------------------------- /include/nbt.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NBT_HEAD_BNTYGTFREQXCPVMFFG 2 | #define NBT_HEAD_BNTYGTFREQXCPVMFFG 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #if defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN || \ 15 | defined(__BIG_ENDIAN__) || \ 16 | defined(__ARMEB__) || \ 17 | defined(__THUMBEB__) || \ 18 | defined(__AARCH64EB__) || \ 19 | defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) 20 | # define NBT_BIG_ENDIAN 21 | #elif defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN || \ 22 | defined(__LITTLE_ENDIAN__) || \ 23 | defined(__ARMEL__) || \ 24 | defined(__THUMBEL__) || \ 25 | defined(__AARCH64EL__) || \ 26 | defined(_MIPSEL) || defined(__MIPSEL) || defined(__MIPSEL__) || \ 27 | defined(__MINGW32__) 28 | #else 29 | //# error "unknown architecture" 30 | #endif 31 | 32 | namespace nbt { 33 | 34 | enum class tag_id : std::uint8_t { 35 | tag_end, tag_byte, tag_short, tag_int, tag_long, tag_float, 36 | tag_double, tag_bytearray, tag_string, tag_list, tag_compound, 37 | tag_intarray, tag_longarray 38 | }; 39 | 40 | } // namespace nbt 41 | 42 | namespace std { 43 | 44 | string to_string(nbt::tag_id tid); 45 | 46 | } // namespace std 47 | 48 | namespace nbt { 49 | 50 | inline int cheof(std::istream & input) { 51 | int value = input.get(); 52 | if (value == EOF) 53 | throw std::runtime_error("EOF"); 54 | return value; 55 | } 56 | 57 | std::ostream & operator<<(std::ostream & output, tag_id tid); 58 | 59 | template 60 | number_t reverse(number_t number) { 61 | constexpr std::size_t n = sizeof(number_t); 62 | union { 63 | number_t number; 64 | std::uint8_t data[n]; 65 | } tmp { number }; 66 | for (std::size_t i = 0; i < n/2; ++i) { 67 | std::uint8_t z = tmp.data[i]; 68 | tmp.data[i] = tmp.data[n - 1 - i]; 69 | tmp.data[n - 1 - i] = z; 70 | } 71 | return tmp.number; 72 | } 73 | 74 | template 75 | number_t net_order(number_t number) { 76 | #ifndef NBT_BIG_ENDIAN 77 | return reverse(number); 78 | #else 79 | return number; 80 | #endif 81 | } 82 | 83 | template 84 | number_t disk_order(number_t number) { 85 | #ifdef NBT_BIG_ENDIAN 86 | return reverse(number); 87 | #else 88 | return number; 89 | #endif 90 | } 91 | 92 | struct context { 93 | enum class orders { 94 | big_endian, little_endian 95 | } order; 96 | enum class formats { 97 | bin, zigzag, mojangson, zint 98 | } format; 99 | static const context & get(std::ios_base & ios); 100 | void set(std::ios_base & ios) const; 101 | }; 102 | 103 | inline std::istream & operator>>(std::istream & input, const context & ctxt) { 104 | ctxt.set(input); 105 | return input; 106 | } 107 | 108 | inline std::ostream & operator<<(std::ostream & output, const context & ctxt) { 109 | ctxt.set(output); 110 | return output; 111 | } 112 | 113 | namespace contexts { 114 | 115 | inline const context java { 116 | context::orders::big_endian, 117 | context::formats::bin 118 | }, bedrock_net { 119 | context::orders::little_endian, 120 | context::formats::zigzag 121 | }, bedrock_disk { 122 | context::orders::little_endian, 123 | context::formats::bin 124 | }, kbt { 125 | context::orders::big_endian, 126 | context::formats::zint 127 | }, mojangson { 128 | context::orders::big_endian, 129 | context::formats::mojangson 130 | }; 131 | 132 | } // namespace contexts 133 | 134 | template 135 | number_t correct_order(number_t number, const context::orders order) { 136 | switch (order) { 137 | case context::orders::little_endian: 138 | return disk_order(number); 139 | case context::orders::big_endian: 140 | default: 141 | return net_order(number); 142 | } 143 | } 144 | 145 | std::int32_t load_varint(std::istream & input); 146 | 147 | void dump_varint(std::ostream & output, std::int32_t value); 148 | 149 | std::int64_t load_varlong(std::istream & input); 150 | 151 | void dump_varlong(std::ostream & output, std::int64_t value); 152 | 153 | template 154 | number_t load_flat(std::istream & input, const context::orders order) { 155 | constexpr std::size_t n = sizeof(number_t); 156 | union { 157 | number_t number; 158 | char data[n]; 159 | } tmp; 160 | input.read(tmp.data, n); 161 | return correct_order(tmp.number, order); 162 | } 163 | 164 | template 165 | number_t load_text(std::istream & input) { 166 | return static_cast(load_text>(input)); 167 | } 168 | 169 | template <> 170 | std::int8_t load_text(std::istream & input); 171 | 172 | template <> 173 | std::int16_t load_text(std::istream & input); 174 | 175 | template <> 176 | std::int32_t load_text(std::istream & input); 177 | 178 | template <> 179 | std::int64_t load_text(std::istream & input); 180 | 181 | template <> 182 | float load_text(std::istream & input); 183 | 184 | template <> 185 | double load_text(std::istream & input); 186 | 187 | template 188 | number_t load(std::istream & input, const context & ctxt) { 189 | if (ctxt.format == context::formats::mojangson) 190 | return load_text(input); 191 | else 192 | return load_flat(input, ctxt.order); 193 | } 194 | 195 | template <> 196 | std::int32_t load(std::istream & input, const context & ctxt); 197 | 198 | template <> 199 | inline std::uint32_t load(std::istream & input, const context & ctxt) { 200 | return static_cast(load(input, ctxt)); 201 | } 202 | 203 | template <> 204 | std::int64_t load(std::istream & input, const context & ctxt); 205 | 206 | template <> 207 | inline std::uint64_t load(std::istream & input, const context & ctxt) { 208 | return static_cast(load(input, ctxt)); 209 | } 210 | 211 | void skip_space(std::istream & input); 212 | 213 | std::size_t load_size(std::istream & input, const context & ctxt); 214 | void dump_size(std::ostream & output, const context & ctxt, std::size_t size); 215 | 216 | template 217 | void scan_sequence_text(std::istream & input, F element_action) { 218 | for (;;) { 219 | skip_space(input); 220 | char c = cheof(input); 221 | if (c == ']') { 222 | break; 223 | } 224 | input.putback(c); 225 | element_action(); 226 | skip_space(input); 227 | int next = cheof(input); 228 | switch (next) { 229 | case ',': continue; 230 | case ']': return; 231 | default: throw std::runtime_error(std::string("unexpected character: ") + char(next)); 232 | } 233 | } 234 | } 235 | 236 | template 237 | std::vector load_array_text(std::istream & input); 238 | 239 | template 240 | std::vector load_array_bin(std::istream & input, const context & ctxt) { 241 | auto size = load_size(input, ctxt); 242 | std::vector result; 243 | result.reserve(size); 244 | for (std::size_t i = 0; i < size; i++) 245 | result.emplace_back(load(input, ctxt)); 246 | return result; 247 | } 248 | 249 | template 250 | std::vector load_array(std::istream & input, const context & ctxt) { 251 | if (ctxt.format == context::formats::mojangson) 252 | return load_array_text(input); 253 | else 254 | return load_array_bin(input, ctxt); 255 | } 256 | 257 | template 258 | void dump_flat(std::ostream & output, number_t number, const context::orders order) { 259 | constexpr std::size_t n = sizeof(number_t); 260 | union { 261 | number_t number; 262 | char data[n]; 263 | } tmp { correct_order(number, order) }; 264 | output.write(tmp.data, n); 265 | } 266 | 267 | template 268 | void dump_text(std::ostream & output, number_t number) { 269 | dump_text(output, std::make_signed_t(number)); 270 | } 271 | 272 | template <> 273 | void dump_text(std::ostream & output, std::int8_t number); 274 | 275 | template <> 276 | void dump_text(std::ostream & output, std::int16_t number); 277 | 278 | template <> 279 | void dump_text(std::ostream & output, std::int32_t number); 280 | 281 | template <> 282 | void dump_text(std::ostream & output, std::int64_t number); 283 | 284 | template <> 285 | void dump_text(std::ostream & output, float number); 286 | 287 | template <> 288 | void dump_text(std::ostream & output, double number); 289 | 290 | template 291 | void dump(std::ostream & output, number_t number, const context & ctxt) { 292 | if (ctxt.format == context::formats::mojangson) 293 | dump_text(output, number); 294 | else 295 | dump_flat(output, number, ctxt.order); 296 | } 297 | 298 | template <> 299 | void dump(std::ostream & output, std::int32_t number, const context & ctxt); 300 | 301 | template <> 302 | inline void dump(std::ostream & output, std::uint32_t number, const context & ctxt) { 303 | dump(output, static_cast(number), ctxt); 304 | } 305 | 306 | template <> 307 | void dump(std::ostream & output, std::int64_t number, const context & ctxt); 308 | 309 | template <> 310 | inline void dump(std::ostream & output, std::uint64_t number, const context & ctxt) { 311 | dump(output, static_cast(number), ctxt); 312 | } 313 | 314 | template 315 | void dump_array_text(std::ostream & output, const std::vector & array); 316 | 317 | template 318 | void dump_array_bin(std::ostream & output, const std::vector & array, const context & ctxt) { 319 | dump_size(output, ctxt, array.size()); 320 | for (const auto & element : array) 321 | dump(output, element, ctxt); 322 | } 323 | 324 | template 325 | void dump_array(std::ostream & output, const std::vector & array, const context & ctxt) { 326 | if (ctxt.format == context::formats::mojangson) 327 | dump_array_text(output, array); 328 | else 329 | dump_array_bin(output, array, ctxt); 330 | } 331 | 332 | template 333 | void dump_list(std::ostream & output, tag_id aid, const std::vector & list, F action) { 334 | const context & ctxt = context::get(output); 335 | if (ctxt.format == context::formats::mojangson) { 336 | output << '['; 337 | } else { 338 | output.put(static_cast(aid)); 339 | auto size = static_cast(list.size()); 340 | dump_size(output, ctxt, size); 341 | } 342 | auto iter = list.cbegin(); 343 | auto end = list.cend(); 344 | if (iter != end) { 345 | action(*iter, ctxt); 346 | for (++iter; iter != end; ++iter) { 347 | if (ctxt.format == context::formats::mojangson) 348 | output << ','; 349 | action(*iter, ctxt); 350 | } 351 | } 352 | if (ctxt.format == context::formats::mojangson) 353 | output << ']'; 354 | } 355 | 356 | template 357 | std::unique_ptr load_list(std::istream & input, F action) { 358 | const context & ctxt = context::get(input); 359 | auto ptr = std::make_unique(); 360 | typename tag_type::value_type & result = ptr->value; 361 | if (ctxt.format != context::formats::mojangson) { 362 | std::size_t size = load_size(input, ctxt); 363 | result.reserve(size); 364 | for (std::size_t i = 0; i < size; i++) 365 | result.emplace_back(action(ctxt)); 366 | } else { 367 | scan_sequence_text(input, [&] { 368 | result.emplace_back(action(ctxt)); 369 | }); 370 | } 371 | result.shrink_to_fit(); 372 | return ptr; 373 | } 374 | 375 | tag_id deduce_tag(std::istream & input); 376 | 377 | namespace tags { 378 | 379 | struct tag { 380 | virtual tag_id id() const noexcept = 0; 381 | virtual void write(std::ostream & output) const = 0; 382 | virtual std::unique_ptr copy() const = 0; 383 | virtual ~tag() {} 384 | }; 385 | 386 | template 387 | struct find_by; 388 | 389 | template 390 | struct find_of; 391 | 392 | std::unique_ptr read(tag_id tid, std::istream & input); 393 | 394 | template 395 | std::unique_ptr cast(std::unique_ptr && ptr) { 396 | static_assert(std::is_base_of::value); 397 | std::unique_ptr result(dynamic_cast(ptr.release())); 398 | return result; 399 | } 400 | 401 | template 402 | std::unique_ptr cast(std::unique_ptr & ptr) { 403 | static_assert(std::is_base_of::value); 404 | std::unique_ptr result(dynamic_cast(ptr.release())); 405 | return result; 406 | } 407 | 408 | # define TAG_FIND(T) \ 409 | template <> \ 410 | struct find_by final { \ 411 | typedef T type; \ 412 | }; \ 413 | template <> \ 414 | struct find_of final { \ 415 | typedef T type; \ 416 | }; 417 | 418 | template 419 | using tag_by = typename find_by::type; 420 | 421 | template 422 | using tag_of = typename find_of::type; 423 | 424 | struct end_tag final : public tag { 425 | typedef std::nullptr_t value_type; 426 | static constexpr tag_id tid = tag_id::tag_end; 427 | virtual tag_id id() const noexcept override { 428 | return tid; 429 | } 430 | virtual void write(std::ostream & output) const override; 431 | virtual std::unique_ptr copy() const override; 432 | }; 433 | inline end_tag end; 434 | TAG_FIND(end_tag) 435 | 436 | template 437 | struct numeric_tag : public tag { 438 | typedef number_t value_type; 439 | value_type value; 440 | static constexpr tag_id tid = TID; 441 | virtual tag_id id() const noexcept override { 442 | return tid; 443 | } 444 | numeric_tag() = default; 445 | constexpr numeric_tag(value_type number) : value(number) {} 446 | static std::unique_ptr read(std::istream & input) { 447 | return std::make_unique(load(input, context::get(input))); 448 | } 449 | virtual void write(std::ostream & output) const override { 450 | dump(output, value, context::get(output)); 451 | } 452 | virtual std::unique_ptr copy() const override { 453 | return std::make_unique(value); 454 | } 455 | }; 456 | 457 | # define NUMERIC_TAG(name, tid, number_t) \ 458 | using name = numeric_tag; \ 459 | TAG_FIND(name) 460 | 461 | NUMERIC_TAG(byte_tag, tag_id::tag_byte, std::int8_t) 462 | NUMERIC_TAG(short_tag, tag_id::tag_short, std::int16_t) 463 | NUMERIC_TAG(int_tag, tag_id::tag_int, std::int32_t) 464 | NUMERIC_TAG(long_tag, tag_id::tag_long, std::int64_t) 465 | NUMERIC_TAG(float_tag, tag_id::tag_float, float) 466 | NUMERIC_TAG(double_tag, tag_id::tag_double, double) 467 | 468 | # undef NUMERIC_TAG 469 | 470 | template 471 | struct array_tag final : public tag { 472 | typedef number_t element_type; 473 | typedef std::vector value_type; 474 | static constexpr char prefix = prefix_c; 475 | value_type value; 476 | static constexpr tag_id tid = TID; 477 | virtual tag_id id() const noexcept override { 478 | return tid; 479 | } 480 | array_tag() = default; 481 | array_tag(const value_type & array) : value(array) {} 482 | array_tag(value_type && array) : value(std::move(array)) {} 483 | static std::unique_ptr read(std::istream & input) { 484 | const context & ctxt = context::get(input); 485 | auto result = std::make_unique(load_array(input, ctxt)); 486 | return result; 487 | } 488 | virtual void write(std::ostream & output) const override { 489 | const context & ctxt = context::get(output); 490 | dump_array(output, value, ctxt); 491 | } 492 | virtual std::unique_ptr copy() const override { 493 | return std::make_unique(value); 494 | } 495 | }; 496 | 497 | # define ARRAY_TAG(name, tid, number_t, prefix) \ 498 | using name = array_tag; \ 499 | TAG_FIND(name) 500 | 501 | ARRAY_TAG(bytearray_tag, tag_id::tag_bytearray, std::int8_t, 'B') 502 | ARRAY_TAG(intarray_tag, tag_id::tag_intarray, std::int32_t, 'I') 503 | ARRAY_TAG(longarray_tag, tag_id::tag_longarray, std::int64_t, 'L') 504 | 505 | # undef ARRAY_TAG 506 | 507 | struct string_tag final : public tag { 508 | typedef std::string value_type; 509 | value_type value; 510 | static constexpr tag_id tid = tag_id::tag_string; 511 | virtual tag_id id() const noexcept override { 512 | return tag_id::tag_string; 513 | } 514 | string_tag() = default; 515 | string_tag(const value_type & string); 516 | string_tag(value_type && string); 517 | static std::unique_ptr read(std::istream & input); 518 | virtual void write(std::ostream & output) const override; 519 | virtual std::unique_ptr copy() const override; 520 | }; 521 | 522 | TAG_FIND(string_tag) 523 | 524 | struct tag_list_tag; 525 | 526 | struct list_tag : public tag { 527 | static constexpr tag_id tid = tag_id::tag_list; 528 | virtual tag_id id() const noexcept override { 529 | return tag_id::tag_list; 530 | } 531 | virtual tag_id element_id() const noexcept = 0; 532 | virtual size_t size() const noexcept = 0; 533 | virtual bool heavy() const noexcept = 0; 534 | virtual std::unique_ptr operator[](size_t i) const = 0; 535 | static std::unique_ptr read(std::istream & input); 536 | virtual tag_list_tag as_tags() = 0; 537 | }; 538 | 539 | template <> 540 | struct find_by final { 541 | typedef list_tag type; 542 | }; 543 | 544 | struct tag_list_tag final : public list_tag { 545 | tag_id eid; 546 | typedef std::unique_ptr element_type; 547 | typedef std::vector value_type; 548 | value_type value; 549 | virtual tag_id element_id() const noexcept override { 550 | return value.empty() ? eid : (eid != tag_id::tag_end ? eid : value[0]->id()); 551 | } 552 | virtual size_t size() const noexcept override { 553 | return value.size(); 554 | } 555 | virtual bool heavy() const noexcept override { 556 | return true; 557 | } 558 | virtual std::unique_ptr operator[](size_t i) const override; 559 | tag_list_tag(); 560 | explicit tag_list_tag(tag_id tid); 561 | tag_list_tag(const tag_list_tag & other); 562 | tag_list_tag(const value_type & list, tag_id tid = tag_id::tag_end); 563 | tag_list_tag(value_type && list, tag_id tid = tag_id::tag_end); 564 | virtual void write(std::ostream & output) const override; 565 | virtual std::unique_ptr copy() const override; 566 | virtual tag_list_tag as_tags() override; 567 | }; 568 | 569 | template 570 | struct find_list_by; 571 | 572 | template 573 | struct find_list_of; 574 | 575 | template 576 | using list_by = typename find_list_by::type; 577 | 578 | template 579 | using list_of = typename find_list_of::type; 580 | 581 | # define FIND_LIST_TAG(name) \ 582 | template <> \ 583 | struct find_list_by { \ 584 | typedef name type; \ 585 | }; \ 586 | template <> \ 587 | struct find_list_of { \ 588 | typedef name type; \ 589 | }; 590 | 591 | struct end_list_tag final : public list_tag { 592 | typedef end_tag tag_type; 593 | static_assert(std::is_base_of::value); 594 | typedef typename end_tag::value_type element_type; 595 | typedef std::vector value_type; 596 | static constexpr tag_id eid = tag_type::tid; 597 | virtual tag_id element_id() const noexcept override { 598 | return eid; 599 | } 600 | virtual size_t size() const noexcept override { 601 | return 0u; 602 | } 603 | virtual bool heavy() const noexcept override { 604 | return false; 605 | } 606 | virtual std::unique_ptr operator[](size_t i) const override; 607 | end_list_tag() = default; 608 | static std::unique_ptr read_content(std::istream & input); 609 | virtual void write(std::ostream & output) const override; 610 | virtual std::unique_ptr copy() const override; 611 | virtual tag_list_tag as_tags() override; 612 | }; 613 | inline end_list_tag end_list; 614 | 615 | FIND_LIST_TAG(end_list_tag) 616 | 617 | template 618 | struct number_list_tag final : public list_tag { 619 | typedef T tag_type; 620 | static_assert(std::is_base_of::value); 621 | typedef typename T::value_type element_type; 622 | typedef std::vector value_type; 623 | static constexpr tag_id eid = tag_type::tid; 624 | virtual tag_id element_id() const noexcept override { 625 | return eid; 626 | } 627 | value_type value; 628 | virtual size_t size() const noexcept override { 629 | return value.size(); 630 | } 631 | virtual bool heavy() const noexcept override { 632 | return false; 633 | } 634 | virtual std::unique_ptr operator[](size_t i) const override { 635 | return std::make_unique(value.at(i)); 636 | } 637 | number_list_tag() = default; 638 | number_list_tag(const value_type & list) : value(list) {} 639 | number_list_tag(value_type && list) : value(std::move(list)) {} 640 | static std::unique_ptr read_content(std::istream & input) { 641 | return load_list(input, [&input](const context & ctxt) -> element_type { 642 | return load(input, ctxt); 643 | }); 644 | } 645 | virtual void write(std::ostream & output) const override { 646 | dump_list(output, eid, value, [&output](const element_type & number, const context & ctxt) { 647 | dump(output, number, ctxt); 648 | }); 649 | } 650 | virtual std::unique_ptr copy() const override { 651 | return std::make_unique(*this); 652 | } 653 | virtual tag_list_tag as_tags() override { 654 | tag_list_tag result(eid); 655 | result.value.reserve(value.size()); 656 | for (auto each : value) 657 | result.value.push_back(std::make_unique(each)); 658 | value.clear(); 659 | value.shrink_to_fit(); 660 | return result; 661 | } 662 | }; 663 | 664 | # define NUMBER_LIST_TAG(name, tag_type) \ 665 | using name = number_list_tag; \ 666 | FIND_LIST_TAG(name) 667 | 668 | NUMBER_LIST_TAG(byte_list_tag, byte_tag) 669 | NUMBER_LIST_TAG(short_list_tag, short_tag) 670 | NUMBER_LIST_TAG(int_list_tag, int_tag) 671 | NUMBER_LIST_TAG(long_list_tag, long_tag) 672 | NUMBER_LIST_TAG(float_list_tag, float_tag) 673 | NUMBER_LIST_TAG(double_list_tag, double_tag) 674 | 675 | # undef NUMBER_LIST_TAG 676 | 677 | template 678 | struct array_list_tag final : public list_tag { 679 | typedef T tag_type; 680 | static_assert(std::is_base_of::value); 681 | typedef typename T::value_type element_type; 682 | typedef std::vector value_type; 683 | static constexpr tag_id eid = tag_type::tid; 684 | virtual tag_id element_id() const noexcept override { 685 | return eid; 686 | } 687 | value_type value; 688 | virtual size_t size() const noexcept override { 689 | return value.size(); 690 | } 691 | virtual bool heavy() const noexcept override { 692 | return false; 693 | } 694 | virtual std::unique_ptr operator[](size_t i) const override { 695 | return std::make_unique(value.at(i)); 696 | } 697 | array_list_tag() = default; 698 | array_list_tag(const value_type & list) : value(list) {} 699 | array_list_tag(value_type && list) : value(std::move(list)) {} 700 | static std::unique_ptr read_content(std::istream & input) { 701 | return load_list(input, [&input] (const context & ctxt) -> element_type { 702 | return load_array(input, ctxt); 703 | }); 704 | } 705 | virtual void write(std::ostream & output) const override { 706 | dump_list(output, eid, value, [&output] 707 | (const element_type & element, const context & ctxt) { 708 | dump_array(output, element, ctxt); 709 | }); 710 | } 711 | virtual std::unique_ptr copy() const override { 712 | return std::make_unique(*this); 713 | } 714 | virtual tag_list_tag as_tags() override { 715 | tag_list_tag result(eid); 716 | result.value.reserve(value.size()); 717 | for (auto & each : value) 718 | result.value.push_back(std::make_unique(std::move(each))); 719 | value.clear(); 720 | value.shrink_to_fit(); 721 | return result; 722 | } 723 | }; 724 | # define ARRAY_LIST_TAG(name, tag_type) \ 725 | using name = array_list_tag; \ 726 | FIND_LIST_TAG(name) 727 | 728 | ARRAY_LIST_TAG(bytearray_list_tag, bytearray_tag) 729 | ARRAY_LIST_TAG(intarray_list_tag, intarray_tag) 730 | ARRAY_LIST_TAG(longarray_list_tag, longarray_tag) 731 | 732 | # undef ARRAY_LIST_TAG 733 | 734 | struct string_list_tag final : public list_tag { 735 | typedef string_tag tag_type; 736 | typedef std::string element_type; 737 | typedef std::vector value_type; 738 | static constexpr tag_id eid = tag_type::tid; 739 | virtual tag_id element_id() const noexcept override { 740 | return eid; 741 | } 742 | value_type value; 743 | virtual size_t size() const noexcept override { 744 | return value.size(); 745 | } 746 | virtual bool heavy() const noexcept override { 747 | return false; 748 | } 749 | virtual std::unique_ptr operator[](size_t i) const override; 750 | string_list_tag() = default; 751 | string_list_tag(const value_type & list); 752 | string_list_tag(value_type && list); 753 | static std::unique_ptr read_content(std::istream & input); 754 | virtual void write(std::ostream & output) const override; 755 | virtual std::unique_ptr copy() const override; 756 | virtual tag_list_tag as_tags() override; 757 | }; 758 | 759 | FIND_LIST_TAG(string_list_tag) 760 | 761 | struct list_list_tag final : public list_tag { 762 | typedef list_tag tag_type; 763 | typedef std::unique_ptr element_type; 764 | typedef std::vector value_type; 765 | static constexpr tag_id eid = tag_type::tid; 766 | virtual tag_id element_id() const noexcept override { 767 | return eid; 768 | } 769 | value_type value; 770 | virtual size_t size() const noexcept override { 771 | return value.size(); 772 | } 773 | virtual bool heavy() const noexcept override { 774 | return false; 775 | } 776 | virtual std::unique_ptr operator[](size_t i) const override; 777 | list_list_tag() = default; 778 | list_list_tag(const list_list_tag & other); 779 | list_list_tag(const value_type & list); 780 | list_list_tag(value_type && list); 781 | static std::unique_ptr read_content(std::istream & input); 782 | virtual void write(std::ostream & output) const override; 783 | virtual std::unique_ptr copy() const override; 784 | virtual tag_list_tag as_tags() override; 785 | }; 786 | 787 | FIND_LIST_TAG(list_list_tag) 788 | 789 | struct compound_tag final : public tag { 790 | typedef std::map> value_type; 791 | bool is_root = false; 792 | value_type value; 793 | static constexpr tag_id tid = tag_id::tag_compound; 794 | virtual tag_id id() const noexcept override { 795 | return tag_id::tag_compound; 796 | } 797 | compound_tag() = default; 798 | compound_tag(const compound_tag & other); 799 | explicit compound_tag(bool root); 800 | compound_tag(const value_type & map, bool root = false); 801 | compound_tag(value_type && map, bool root = false); 802 | static std::unique_ptr read(std::istream & input); 803 | virtual void write(std::ostream & output) const override; 804 | virtual std::unique_ptr copy() const override; 805 | compound_tag & operator=(const compound_tag & other); 806 | void make_heavy(); 807 | 808 | template 809 | auto put(std::string && name, T && item) { 810 | return value.insert_or_assign(std::move(name), std::make_unique>(std::move(item))); 811 | } 812 | 813 | template 814 | auto put(const std::string & name, T && item) { 815 | return value.insert_or_assign(name, std::make_unique>(item)); 816 | } 817 | 818 | template 819 | typename tag_of::value_type & get(const std::string & name) { 820 | return dynamic_cast &>(*value.at(name)).value; 821 | } 822 | 823 | template 824 | const typename tag_of::value_type & get(const std::string & name) const { 825 | return dynamic_cast &>(*value.at(name)).value; 826 | } 827 | 828 | template 829 | typename tag_of::value_type & tag(const std::string & name) { 830 | auto iter = value.find(name); 831 | if (iter == value.end()) { 832 | auto ptr = std::make_unique>(); 833 | typename tag_of::value_type & result = ptr->value; 834 | value.emplace(name, std::move(ptr)); 835 | return result; 836 | } else { 837 | return dynamic_cast &>(*iter->second).value; 838 | } 839 | } 840 | 841 | bool erase(const std::string & name); 842 | }; 843 | 844 | TAG_FIND(compound_tag) 845 | 846 | struct compound_list_tag final : public list_tag { 847 | typedef compound_tag tag_type; 848 | typedef tag_type element_type; 849 | typedef std::vector value_type; 850 | static constexpr tag_id eid = tag_type::tid; 851 | virtual tag_id element_id() const noexcept override { 852 | return eid; 853 | } 854 | value_type value; 855 | virtual size_t size() const noexcept override { 856 | return value.size(); 857 | } 858 | virtual bool heavy() const noexcept override { 859 | return false; 860 | } 861 | virtual std::unique_ptr operator[](size_t i) const override; 862 | compound_list_tag() = default; 863 | compound_list_tag(const value_type & list); 864 | compound_list_tag(value_type && list); 865 | static std::unique_ptr read_content(std::istream & input); 866 | virtual void write(std::ostream & output) const override; 867 | virtual std::unique_ptr copy() const override; 868 | virtual tag_list_tag as_tags() override; 869 | }; 870 | 871 | FIND_LIST_TAG(compound_list_tag) 872 | 873 | # undef FIND_LIST_TAG 874 | # undef TAG_FIND 875 | 876 | 877 | template 878 | tag_of wrap(T && value) { 879 | return tag_of(value); 880 | } 881 | 882 | template 883 | std::unique_ptr read(std::istream & input) { 884 | return tag_by::read(input); 885 | } 886 | 887 | } // namespace tags 888 | 889 | std::istream & operator>>(std::istream & input, tags::compound_tag & compound); 890 | std::ostream & operator<<(std::ostream & output, const tags::tag & tag); 891 | 892 | template 893 | std::vector load_array_text(std::istream & input) { 894 | std::vector result; 895 | skip_space(input); 896 | char a = cheof(input); 897 | if (a != '[') 898 | throw std::runtime_error("failed to open array tag"); 899 | a = cheof(input); 900 | if (a != tags::tag_of>::prefix) 901 | throw std::runtime_error("wrong array tag type"); 902 | a = cheof(input); 903 | if (a != ';') 904 | throw std::runtime_error("unexpected symbol in array tag"); 905 | scan_sequence_text(input, [&] { 906 | result.push_back(load_text(input)); 907 | }); 908 | result.shrink_to_fit(); 909 | return result; 910 | } 911 | 912 | template 913 | void dump_array_text(std::ostream & output, const std::vector & array) { 914 | output << '[' << tags::tag_of>::prefix << ';'; 915 | auto iter = array.cbegin(); 916 | auto end = array.cend(); 917 | if (iter == end) 918 | return; 919 | dump_text(output, *iter); 920 | for (++iter; iter != end; ++iter) { 921 | output << ','; 922 | dump_text(output, *iter); 923 | } 924 | output << ']'; 925 | } 926 | 927 | } // namespace nbt 928 | 929 | namespace std { 930 | 931 | string to_string(const nbt::tags::tag & tag); 932 | 933 | } // namespace std 934 | 935 | #endif // NBT_HEAD_BNTYGTFREQXCPVMFFG 936 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('nbt-cpp', 'cpp', 2 | version : run_command('git', 'describe', '--abbrev=0', '--tags').stdout().strip(), 3 | default_options : ['warning_level=3', 4 | 'cpp_std=c++17']) 5 | 6 | group = 'com.handtruth.mc' 7 | maintainer = 'ktlo ' 8 | 9 | modules = [ 10 | ] 11 | 12 | ###################################### 13 | 14 | module_deps = [] 15 | 16 | foreach module : modules 17 | module_deps += dependency(module, fallback : [module, 'dep']) 18 | endforeach 19 | 20 | subdir('include') 21 | subdir('src') 22 | subdir('test') 23 | 24 | dep = declare_dependency(link_with : lib, include_directories : includes) 25 | 26 | cppcheck = custom_target(meson.project_name() + '_cppcheck_internal', 27 | output : meson.project_name() + '_cppcheck.log', 28 | input : sources + test_files, 29 | command : [ 30 | 'cppcheck', 31 | '--enable=all', 32 | '-I', meson.current_source_dir() / 'include', 33 | '-I', meson.current_source_dir() / 'src', 34 | '@INPUT@', 35 | # '--project=compile_commands.json', 36 | '--output-file=@OUTPUT@' 37 | ]) 38 | 39 | run_target('cppcheck', command : ['cat', cppcheck.full_path()], depends : cppcheck) 40 | -------------------------------------------------------------------------------- /meson_options.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handtruth/nbt-cpp/cb43446e86a20c9c0446de793dba680ab8bf0647/meson_options.txt -------------------------------------------------------------------------------- /src/meson.build: -------------------------------------------------------------------------------- 1 | sources = files([ 2 | 'nbt.cpp' 3 | ]) 4 | 5 | src = include_directories('.') 6 | 7 | lib = library(meson.project_name(), sources, include_directories : includes, install: true, dependencies: module_deps) 8 | 9 | nbtc = executable('nbtc', ['nbtc.cpp'], include_directories : includes, link_with : lib, install : true, dependencies : [module_deps]) 10 | -------------------------------------------------------------------------------- /src/nbt.cpp: -------------------------------------------------------------------------------- 1 | #include "nbt_internal.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace nbt { 9 | 10 | void skip_space(std::istream & input) { 11 | for (;;) { 12 | int next = cheof(input); 13 | if (!std::isspace(next)) { 14 | input.putback(next); 15 | return; 16 | } 17 | } 18 | } 19 | 20 | int context_id() { 21 | static const int i = std::ios_base::xalloc(); 22 | return i; 23 | } 24 | 25 | context *& context_storage(std::ios_base & ios) { 26 | int i = context_id(); 27 | return reinterpret_cast(ios.pword(i)); 28 | } 29 | 30 | const context & context::get(std::ios_base & ios) { 31 | auto ctxt = context_storage(ios); 32 | if (ctxt == nullptr) 33 | return contexts::java; 34 | else 35 | return *ctxt; 36 | } 37 | 38 | void ios_callback(std::ios_base::event event, std::ios_base & ios, int) { 39 | if (event == std::ios_base::event::erase_event) 40 | delete context_storage(ios); 41 | } 42 | 43 | void context::set(std::ios_base & ios) const { 44 | context *& ctxt = context_storage(ios); 45 | if (ctxt == nullptr) { 46 | ios.register_callback(ios_callback, context_id()); 47 | ctxt = new context(*this); 48 | } else { 49 | *ctxt = *this; 50 | } 51 | } 52 | 53 | std::int32_t load_varint(std::istream & input) { 54 | return load_varnum(input); 55 | } 56 | 57 | void dump_varint(std::ostream & output, std::int32_t value) { 58 | dump_varnum(output, value); 59 | } 60 | 61 | std::int64_t load_varlong(std::istream & input) { 62 | return load_varnum(input); 63 | } 64 | 65 | void dump_varlong(std::ostream & output, std::int64_t value) { 66 | dump_varnum(output, value); 67 | } 68 | 69 | template 70 | number_t load_text_simple(std::istream & input) { 71 | number_t value; 72 | input >> value; 73 | int next = cheof(input); 74 | if (next != suffix) 75 | input.putback(next); 76 | return value; 77 | } 78 | 79 | template <> 80 | std::int8_t load_text(std::istream & input) { 81 | int value; 82 | input >> value; 83 | int next = cheof(input); 84 | if (next != 'b') 85 | input.putback(next); 86 | return static_cast(value); 87 | } 88 | 89 | template <> 90 | std::int16_t load_text(std::istream & input) { 91 | return load_text_simple(input); 92 | } 93 | 94 | template <> 95 | std::int32_t load_text(std::istream & input) { 96 | std::int32_t value; 97 | input >> value; 98 | return value; 99 | } 100 | 101 | template <> 102 | std::int64_t load_text(std::istream & input) { 103 | return load_text_simple(input); 104 | } 105 | 106 | template <> 107 | float load_text(std::istream & input) { 108 | return load_text_simple(input); 109 | } 110 | 111 | template <> 112 | double load_text(std::istream & input) { 113 | return load_text_simple(input); 114 | } 115 | 116 | template <> 117 | std::int32_t load(std::istream & input, const context & ctxt) { 118 | switch (ctxt.format) { 119 | case context::formats::bin: 120 | return load_flat(input, ctxt.order); 121 | case context::formats::zigzag: 122 | return load_varint(input); 123 | case context::formats::mojangson: 124 | return load_text(input); 125 | case context::formats::zint: 126 | default: 127 | return load_zint(input); 128 | } 129 | } 130 | 131 | template <> 132 | std::int64_t load(std::istream & input, const context & ctxt) { 133 | switch (ctxt.format) { 134 | case context::formats::bin: 135 | return load_flat(input, ctxt.order); 136 | case context::formats::zigzag: 137 | return load_varlong(input); 138 | case context::formats::mojangson: 139 | return load_text(input); 140 | case context::formats::zint: 141 | default: 142 | return load_zint(input); 143 | } 144 | } 145 | 146 | template <> 147 | void dump(std::ostream & output, std::int32_t number, const context & ctxt) { 148 | switch (ctxt.format) { 149 | case context::formats::bin: 150 | return dump_flat(output, number, ctxt.order); 151 | case context::formats::zigzag: 152 | return dump_varint(output, number); 153 | case context::formats::mojangson: 154 | return dump_text(output, number); 155 | case context::formats::zint: 156 | default: 157 | return dump_zint(output, number); 158 | } 159 | } 160 | 161 | template <> 162 | void dump(std::ostream & output, std::int64_t number, const context & ctxt) { 163 | switch (ctxt.format) { 164 | case context::formats::bin: 165 | return dump_flat(output, number, ctxt.order); 166 | case context::formats::zigzag: 167 | return dump_varlong(output, number); 168 | case context::formats::mojangson: 169 | return dump_text(output, number); 170 | case context::formats::zint: 171 | default: 172 | return dump_zint(output, number); 173 | } 174 | } 175 | 176 | template <> 177 | void dump_text(std::ostream & output, std::int8_t number) { 178 | output << int(number) << 'b'; 179 | } 180 | 181 | template <> 182 | void dump_text(std::ostream & output, std::int16_t number) { 183 | output << number << 's'; 184 | } 185 | 186 | template <> 187 | void dump_text(std::ostream & output, std::int32_t number) { 188 | output << number; 189 | } 190 | 191 | template <> 192 | void dump_text(std::ostream & output, std::int64_t number) { 193 | output << number << 'l'; 194 | } 195 | 196 | template <> 197 | void dump_text(std::ostream & output, float number) { 198 | output << number << 'f'; 199 | } 200 | 201 | template <> 202 | void dump_text(std::ostream & output, double number) { 203 | output << number; 204 | } 205 | 206 | std::size_t load_size(std::istream & input, const context & ctxt) { 207 | if (ctxt.format == context::formats::bin) 208 | return load_flat(input, ctxt.order); 209 | else 210 | return static_cast(load_varint(input)); 211 | } 212 | 213 | void dump_size(std::ostream & output, const context & ctxt, std::size_t size) { 214 | auto sz = static_cast(size); 215 | if (ctxt.format == context::formats::bin) 216 | dump_flat(output, sz, ctxt.order); 217 | else 218 | dump_varint(output, sz); 219 | } 220 | 221 | std::ostream & operator<<(std::ostream & output, tag_id tid) { 222 | return output << std::to_string(tid); 223 | } 224 | 225 | inline bool is_valid_char(char c) { 226 | return std::isalnum(c) || c == '-' || c == '_' || c == '+' || c == '.'; 227 | } 228 | 229 | tag_id deduce_tag(std::istream & input) { 230 | skip_space(input); 231 | char a = cheof(input); 232 | if (a == '}' || a == ']') { 233 | input.putback(a); 234 | return tag_id::tag_end; 235 | } 236 | if (a == '[') { 237 | char b = cheof(input); 238 | tag_id id; 239 | switch (b) { 240 | case 'B': 241 | id = tag_id::tag_bytearray; break; 242 | case 'I': 243 | id = tag_id::tag_intarray; break; 244 | case 'L': 245 | id = tag_id::tag_longarray; break; 246 | default: 247 | id = tag_id::tag_list; break; 248 | } 249 | if (id != tag_id::tag_list) { 250 | char c = input.peek(); 251 | if (c != ';') 252 | id = tag_id::tag_list; 253 | } 254 | input.putback(b); 255 | input.putback(a); 256 | return id; 257 | } 258 | if (a == '{') { 259 | input.putback(a); 260 | return tag_id::tag_compound; 261 | } 262 | if (std::isdigit(a) || a == '-' || a == '+') { 263 | std::string buffer(&a, 1); 264 | tag_id deduced; 265 | for (;;) { 266 | char b = cheof(input); 267 | buffer.push_back(b); 268 | if (std::isdigit(b)) { 269 | continue; 270 | } else if (b == 'b') { 271 | deduced = tag_id::tag_byte; 272 | } else if (b == 's') { 273 | deduced = tag_id::tag_short; 274 | } else if (b == 'l') { 275 | deduced = tag_id::tag_long; 276 | } else if (b == 'f') { 277 | deduced = tag_id::tag_float; 278 | } else if (b == 'd') { 279 | deduced = tag_id::tag_double; 280 | } else if (b == 'e') { 281 | char c = cheof(input); 282 | buffer.push_back(c); 283 | if (std::isdigit(c) || c == '-' || c == '+') { 284 | for (;;) { 285 | char d = cheof(input); 286 | buffer.push_back(d); 287 | if (std::isdigit(d)) { 288 | continue; 289 | } else if (d == 'f') { 290 | deduced = tag_id::tag_float; 291 | } else if (d == 'd') { 292 | deduced = tag_id::tag_double; 293 | } else { 294 | deduced = tag_id::tag_double; 295 | } 296 | break; 297 | } 298 | } else { 299 | deduced = tag_id::tag_int; 300 | } 301 | } else if (b == '.') { 302 | for (;;) { 303 | char c = cheof(input); 304 | buffer.push_back(c); 305 | if (std::isdigit(c)) { 306 | continue; 307 | } else if (c == 'e' || c == 'E') { 308 | char d = cheof(input); 309 | buffer.push_back(d); 310 | if (std::isdigit(d) || d == '-' || d == '+') 311 | continue; 312 | } else if (c == 'f') { 313 | deduced = tag_id::tag_float; 314 | } else if (c == 'd') { 315 | deduced = tag_id::tag_double; 316 | } else { 317 | deduced = tag_id::tag_double; 318 | } 319 | break; 320 | } 321 | } else { 322 | deduced = tag_id::tag_int; 323 | } 324 | break; 325 | } 326 | for (auto iter = buffer.crbegin(), end = buffer.crend(); iter != end; ++iter) 327 | input.putback(*iter); 328 | return deduced; 329 | } 330 | if (a == '"' || is_valid_char(a)) { 331 | input.putback(a); 332 | return tag_id::tag_string; 333 | } 334 | input.putback(a); 335 | return tag_id::tag_end; 336 | } 337 | 338 | namespace tags { 339 | 340 | std::string read_string_text(std::istream & input) { 341 | skip_space(input); 342 | char first = cheof(input); 343 | if (first == '"') { 344 | std::string result; 345 | for (;;) { 346 | int c = cheof(input); 347 | switch (c) { 348 | case '"': 349 | return result; 350 | case '\\': { 351 | int s = cheof(input); 352 | switch (s) { 353 | case '"': 354 | result.push_back('"'); break; 355 | case '\\': 356 | result.push_back('\\'); break; 357 | case '/': 358 | result.push_back('/'); break; 359 | case 'b': 360 | result.push_back('\b'); break; 361 | case 'f': 362 | result.push_back('\f'); break; 363 | case 'n': 364 | result.push_back('\n'); break; 365 | case 'r': 366 | result.push_back('\r'); break; 367 | case 't': 368 | result.push_back('\t'); break; 369 | case 'u': 370 | throw std::runtime_error("TODO"); 371 | default: 372 | result.push_back('\\'); 373 | result.push_back(s); 374 | break; 375 | } 376 | break; 377 | } 378 | default: 379 | result.push_back(c); 380 | break; 381 | } 382 | } 383 | } else { 384 | std::string result(&first, 1); 385 | for (;;) { 386 | int c = cheof(input); 387 | if (is_valid_char(c)) { 388 | result.push_back(c); 389 | } else { 390 | input.putback(c); 391 | return result; 392 | } 393 | } 394 | } 395 | } 396 | 397 | std::string read_string_bin(std::istream & input, const context & ctxt) { 398 | std::uint32_t size = (ctxt.format == context::formats::zigzag || ctxt.format == context::formats::zint) ? 399 | load_varint(input) : load_flat(input, ctxt.order); 400 | std::string result; 401 | result.resize(size); 402 | input.read(result.data(), size); 403 | return result; 404 | } 405 | 406 | std::string read_string(std::istream & input, const context & ctxt) { 407 | if (ctxt.format == context::formats::mojangson) 408 | return read_string_text(input); 409 | else 410 | return read_string_bin(input, ctxt); 411 | } 412 | 413 | void write_string_text(std::ostream & output, const std::string & string) { 414 | output << '"'; 415 | for (char c : string) { 416 | switch (c) { 417 | case '"': 418 | output << "\\\""; break; 419 | case '\\': 420 | output << "\\\\"; break; 421 | case '\b': 422 | output << "\\b"; break; 423 | case '\f': 424 | output << "\\f"; break; 425 | case '\n': 426 | output << "\\n"; break; 427 | case '\r': 428 | output << "\\r"; break; 429 | case '\t': 430 | output << "\\t"; break; 431 | default: 432 | output << c; 433 | } 434 | } 435 | output << '"'; 436 | } 437 | 438 | void write_string(std::ostream & output, const std::string & string, const context & ctxt) { 439 | switch (ctxt.format) { 440 | case context::formats::bin: 441 | dump(output, static_cast(string.size()), ctxt); break; 442 | case context::formats::mojangson: 443 | write_string_text(output, string); return; 444 | case context::formats::zigzag: 445 | case context::formats::zint: 446 | default: 447 | dump_varint(output, static_cast(string.size())); break; 448 | } 449 | output << string; 450 | } 451 | 452 | template 453 | std::unique_ptr read_bridge(tag_id id, std::istream & input) { 454 | if (id == tid) 455 | return read(input); 456 | else 457 | return read_bridge(static_cast(tid) - 1)>(id, input); 458 | } 459 | 460 | template <> 461 | std::unique_ptr read_bridge(tag_id, std::istream &) { 462 | throw std::runtime_error("end tag not for reading"); 463 | } 464 | 465 | std::unique_ptr read(tag_id tid, std::istream & input) { 466 | return read_bridge(tid, input); 467 | } 468 | 469 | template 470 | std::unique_ptr read_list_content_bridge(tag_id id, std::istream & input) { 471 | if (id == tid) 472 | return list_by::read_content(input); 473 | else 474 | return read_list_content_bridge(static_cast(tid) - 1)>(id, input); 475 | } 476 | 477 | template <> 478 | std::unique_ptr read_list_content_bridge(tag_id id, std::istream & input) { 479 | assert(id == tag_id::tag_end); 480 | return list_by::read_content(input); 481 | } 482 | 483 | std::unique_ptr read_list_content(tag_id tid, std::istream & input) { 484 | return read_list_content_bridge(tid, input); 485 | } 486 | 487 | void end_tag::write(std::ostream & output) const { 488 | output.put('\0'); 489 | } 490 | 491 | std::unique_ptr end_tag::copy() const { 492 | return std::make_unique(); 493 | } 494 | 495 | string_tag::string_tag(const value_type & string) : value(string) {} 496 | 497 | string_tag::string_tag(value_type && string) : value(std::move(string)) {} 498 | 499 | std::unique_ptr string_tag::read(std::istream & input) { 500 | const context & ctxt = context::get(input); 501 | return std::make_unique(read_string(input, ctxt)); 502 | } 503 | 504 | void string_tag::write(std::ostream & output) const { 505 | const context & ctxt = context::get(output); 506 | write_string(output, value, ctxt); 507 | } 508 | 509 | std::unique_ptr string_tag::copy() const { 510 | return std::make_unique(value); 511 | } 512 | 513 | std::unique_ptr list_tag::read(std::istream & input) { 514 | const context & ctxt = context::get(input); 515 | if (ctxt.format == context::formats::mojangson) { 516 | skip_space(input); 517 | char a = cheof(input); 518 | if (a != '[') 519 | throw std::runtime_error("failed to open list tag"); 520 | tag_id type = deduce_tag(input); 521 | return read_list_content(type, input); 522 | } else { 523 | tag_id type = static_cast(cheof(input)); 524 | return read_list_content(type, input); 525 | } 526 | } 527 | 528 | std::unique_ptr tag_list_tag::operator[](size_t i) const { 529 | return value.at(i)->copy(); 530 | } 531 | 532 | tag_list_tag::tag_list_tag() : eid(tag_id::tag_end) {} 533 | 534 | tag_list_tag::tag_list_tag(tag_id tid) : eid(tid) {} 535 | 536 | tag_list_tag::tag_list_tag(const tag_list_tag & other) : eid(other.eid) { 537 | value.reserve(other.size()); 538 | for (const auto & each : other.value) 539 | value.emplace_back(each->copy()); 540 | } 541 | 542 | tag_list_tag::tag_list_tag(const value_type & list, tag_id tid) : eid(tid) { 543 | value.reserve(list.size()); 544 | for (const auto & each : list) 545 | value.emplace_back(each->copy()); 546 | } 547 | 548 | tag_list_tag::tag_list_tag(value_type && list, tag_id tid) : eid(tid), value(std::move(list)) {} 549 | 550 | void tag_list_tag::write(std::ostream & output) const { 551 | tag_id aid = element_id(); 552 | dump_list(output, aid, value, [&output, aid](const std::unique_ptr & tag, const context &) { 553 | if (aid != tag->id()) 554 | throw std::runtime_error("wrong tag id"); 555 | tag->write(output); 556 | }); 557 | } 558 | 559 | std::unique_ptr tag_list_tag::copy() const { 560 | return std::make_unique(*this); 561 | } 562 | 563 | tag_list_tag tag_list_tag::as_tags() { 564 | return std::move(*this); 565 | } 566 | 567 | std::unique_ptr end_list_tag::operator[](size_t) const { 568 | throw std::out_of_range("end list tag always empty"); 569 | } 570 | 571 | std::unique_ptr end_list_tag::read_content(std::istream & input) { 572 | if (context::get(input).format != context::formats::mojangson) { 573 | char c = cheof(input); 574 | if (c != '\0') 575 | throw std::runtime_error("end list tag should be empty"); 576 | } 577 | return std::make_unique(); 578 | } 579 | 580 | void end_list_tag::write(std::ostream & output) const { 581 | if (context::get(output).format == context::formats::mojangson) { 582 | output << "[]"; 583 | } else { 584 | output.put(static_cast(eid)); 585 | output.put('\0'); 586 | } 587 | } 588 | 589 | std::unique_ptr end_list_tag::copy() const { 590 | return std::make_unique(); 591 | } 592 | 593 | tag_list_tag end_list_tag::as_tags() { 594 | return tag_list_tag(tag_id::tag_end); 595 | } 596 | 597 | std::unique_ptr string_list_tag::operator[](size_t i) const { 598 | return std::make_unique(value.at(i)); 599 | } 600 | 601 | string_list_tag::string_list_tag(const value_type & list) : value(list) {} 602 | 603 | string_list_tag::string_list_tag(value_type && list) : value(std::move(list)) {} 604 | 605 | std::unique_ptr string_list_tag::read_content(std::istream & input) { 606 | return load_list(input, [&input](const context & ctxt) -> std::string { 607 | return read_string(input, ctxt); 608 | }); 609 | } 610 | 611 | void string_list_tag::write(std::ostream & output) const { 612 | tag_id aid = element_id(); 613 | dump_list(output, aid, value, [&output](const std::string & string, const context & ctxt) { 614 | write_string(output, string, ctxt); 615 | }); 616 | } 617 | 618 | std::unique_ptr string_list_tag::copy() const { 619 | return std::make_unique(*this); 620 | } 621 | 622 | tag_list_tag string_list_tag::as_tags() { 623 | tag_list_tag result(eid); 624 | result.value.reserve(value.size()); 625 | for (auto & each : value) 626 | result.value.push_back(std::make_unique(std::move(each))); 627 | value.clear(); 628 | value.shrink_to_fit(); 629 | return result; 630 | } 631 | 632 | std::unique_ptr list_list_tag::operator[](size_t i) const { 633 | return value.at(i)->copy(); 634 | } 635 | 636 | list_list_tag::list_list_tag(const list_list_tag & other) { 637 | value.reserve(other.value.size()); 638 | for (const auto & element : other.value) 639 | value.emplace_back(cast(element->copy())); 640 | } 641 | 642 | list_list_tag::list_list_tag(const value_type & list) { 643 | value.reserve(list.size()); 644 | for (const auto & element : list) 645 | value.emplace_back(cast(element->copy())); 646 | } 647 | 648 | list_list_tag::list_list_tag(value_type && list) : value(std::move(list)) {} 649 | 650 | std::unique_ptr list_list_tag::read_content(std::istream & input) { 651 | return load_list(input, [&input](const context &) -> element_type { 652 | return list_tag::read(input); 653 | }); 654 | } 655 | 656 | void list_list_tag::write(std::ostream & output) const { 657 | dump_list(output, eid, value, [&output](const element_type & list, const context &) { 658 | list->write(output); 659 | }); 660 | } 661 | 662 | std::unique_ptr list_list_tag::copy() const { 663 | return std::make_unique(*this); 664 | } 665 | 666 | tag_list_tag list_list_tag::as_tags() { 667 | tag_list_tag result(eid); 668 | result.value.reserve(value.size()); 669 | for (auto & each : value) 670 | result.value.push_back(std::move(each)); 671 | value.clear(); 672 | value.shrink_to_fit(); 673 | return result; 674 | } 675 | 676 | compound_tag::compound_tag(const compound_tag & other) : is_root(other.is_root) { 677 | for (const auto & pair : other.value) 678 | value.emplace(pair.first, pair.second->copy()); 679 | } 680 | 681 | compound_tag::compound_tag(bool root) : is_root(root) {} 682 | 683 | compound_tag::compound_tag(const value_type & map, bool root) : is_root(root) { 684 | for (const auto & pair : map) 685 | value.emplace(pair.first, pair.second->copy()); 686 | } 687 | 688 | compound_tag::compound_tag(value_type && map, bool root) : is_root(root), value(std::move(map)) {} 689 | 690 | std::unique_ptr compound_tag::read(std::istream & input) { 691 | auto ptr = std::make_unique(); 692 | input >> *ptr; 693 | return ptr; 694 | } 695 | 696 | void compound_write_text(std::ostream & output, const compound_tag & compound, const context & ctxt) { 697 | const auto & value = compound.value; 698 | output << '{'; 699 | auto iter = value.cbegin(); 700 | auto end = value.cend(); 701 | if (iter != end) { 702 | auto action = [&](const compound_tag::value_type::value_type & entry) { 703 | write_string(output, entry.first, ctxt); 704 | output << ':'; 705 | entry.second->write(output); 706 | }; 707 | action(*iter); 708 | for (++iter; iter != end; ++iter) { 709 | output << ','; 710 | action(*iter); 711 | } 712 | } 713 | output << '}'; 714 | } 715 | 716 | void compound_write_bin(std::ostream & output, const compound_tag & compound, const context & ctxt) { 717 | const auto & value = compound.value; 718 | for (const auto & entry : value) { 719 | output.put(static_cast(entry.second->id())); 720 | write_string(output, entry.first, ctxt); 721 | entry.second->write(output); 722 | } 723 | if (!compound.is_root) 724 | tags::end.write(output); 725 | } 726 | 727 | void compound_tag::write(std::ostream & output) const { 728 | const context & ctxt = context::get(output); 729 | if (ctxt.format == context::formats::mojangson) 730 | compound_write_text(output, *this, ctxt); 731 | else 732 | compound_write_bin(output, *this, ctxt); 733 | } 734 | 735 | std::unique_ptr compound_tag::copy() const { 736 | return std::make_unique(*this); 737 | } 738 | 739 | compound_tag & compound_tag::operator=(const compound_tag & other) { 740 | is_root = other.is_root; 741 | for (const auto & pair : other.value) 742 | value.emplace(pair.first, pair.second->copy()); 743 | return *this; 744 | } 745 | 746 | void compound_tag::make_heavy() { 747 | for (auto & pair : value) { 748 | tags::tag & each = *pair.second; 749 | if (each.id() == tag_id::tag_list) { 750 | list_tag & list = dynamic_cast(each); 751 | if (!list.heavy()) 752 | pair.second = std::make_unique(list.as_tags()); 753 | } 754 | } 755 | } 756 | 757 | std::unique_ptr compound_list_tag::operator[](size_t i) const { 758 | return value.at(i).copy(); 759 | } 760 | 761 | compound_list_tag::compound_list_tag(const value_type & list) : value(list) {} 762 | 763 | compound_list_tag::compound_list_tag(value_type && list) : value(std::move(list)) {} 764 | 765 | std::unique_ptr compound_list_tag::read_content(std::istream & input) { 766 | return load_list(input, [&input](const context &) -> compound_tag { 767 | compound_tag element; 768 | input >> element; 769 | return element; 770 | }); 771 | } 772 | 773 | void compound_list_tag::write(std::ostream & output) const { 774 | dump_list(output, eid, value, [&output](const element_type & element, const context &) { 775 | element.write(output); 776 | }); 777 | } 778 | 779 | std::unique_ptr compound_list_tag::copy() const { 780 | return std::make_unique(*this); 781 | } 782 | 783 | tag_list_tag compound_list_tag::as_tags() { 784 | tag_list_tag result(eid); 785 | result.value.reserve(value.size()); 786 | for (auto & each : value) { 787 | each.make_heavy(); 788 | result.value.push_back(std::make_unique(std::move(each))); 789 | } 790 | value.clear(); 791 | value.shrink_to_fit(); 792 | return result; 793 | } 794 | 795 | } // namespace tags 796 | 797 | void read_compound_text(std::istream & input, tags::compound_tag & compound, const context & ctxt) { 798 | skip_space(input); 799 | char a = cheof(input); 800 | if (a != '{') 801 | throw std::runtime_error("failed to open compound"); 802 | for (;;) { 803 | tag_id key_type = deduce_tag(input); 804 | switch (key_type) { 805 | case tag_id::tag_end: 806 | skip_space(input); 807 | if (cheof(input) != '}') 808 | throw std::runtime_error("failed to close compound tag"); 809 | return; 810 | case tag_id::tag_string: 811 | break; 812 | default: 813 | throw std::runtime_error( 814 | "invalid key tag (tag_string expected, got " + std::to_string(key_type) + ')' 815 | ); 816 | } 817 | std::string key = tags::read_string(input, ctxt); 818 | skip_space(input); 819 | char a = cheof(input); 820 | if (a != ':') 821 | throw std::runtime_error(std::string("key-value delimiter expected, got ") + a); 822 | tag_id value_type = deduce_tag(input); 823 | if (value_type == tag_id::tag_end) 824 | throw std::runtime_error(std::string("value expected")); 825 | compound.value.emplace(std::move(key), tags::read(value_type, input)); 826 | skip_space(input); 827 | a = cheof(input); 828 | if (a == '}') 829 | return; 830 | if (a != ',' && a != ';') 831 | throw std::runtime_error(std::string("next tag or end expected, got ") + a); 832 | } 833 | } 834 | 835 | void read_compound_bin(std::istream & input, tags::compound_tag & compound, const context & ctxt) { 836 | for (;;) { 837 | auto id_numeric = input.get(); 838 | if (!input) { 839 | compound.is_root = true; 840 | break; 841 | } 842 | auto id = static_cast(id_numeric); 843 | if (id > tag_id::tag_longarray) 844 | throw std::out_of_range("unknown tag id: " + std::to_string(id_numeric)); 845 | if (id == tag_id::tag_end) 846 | break; 847 | std::string key = tags::read_string(input, ctxt); 848 | compound.value.emplace(std::move(key), tags::read(id, input)); 849 | } 850 | } 851 | 852 | std::istream & operator>>(std::istream & input, tags::compound_tag & compound) { 853 | const context & ctxt = context::get(input); 854 | if (ctxt.format == context::formats::mojangson) 855 | read_compound_text(input, compound, ctxt); 856 | else 857 | read_compound_bin(input, compound, ctxt); 858 | return input; 859 | } 860 | 861 | std::ostream & operator<<(std::ostream & output, const tags::tag & tag) { 862 | tag.write(output); 863 | return output; 864 | } 865 | 866 | } // namespace nbt 867 | 868 | namespace std { 869 | 870 | string to_string(nbt::tag_id tid) { 871 | # define TAG_ID_CASE(name) \ 872 | case nbt::tag_id::name: return #name 873 | switch (tid) { 874 | TAG_ID_CASE(tag_end); 875 | TAG_ID_CASE(tag_byte); 876 | TAG_ID_CASE(tag_short); 877 | TAG_ID_CASE(tag_int); 878 | TAG_ID_CASE(tag_long); 879 | TAG_ID_CASE(tag_float); 880 | TAG_ID_CASE(tag_double); 881 | TAG_ID_CASE(tag_bytearray); 882 | TAG_ID_CASE(tag_string); 883 | TAG_ID_CASE(tag_list); 884 | TAG_ID_CASE(tag_compound); 885 | TAG_ID_CASE(tag_intarray); 886 | TAG_ID_CASE(tag_longarray); 887 | default: 888 | abort(); // unreachable 889 | } 890 | # undef TAG_ID_CASE 891 | } 892 | 893 | string to_string(const nbt::tags::tag & tag) { 894 | using namespace nbt; 895 | std::stringstream result; 896 | tag.write(result << contexts::mojangson); 897 | return result.str(); 898 | } 899 | 900 | } // namespace std 901 | -------------------------------------------------------------------------------- /src/nbt_internal.hpp: -------------------------------------------------------------------------------- 1 | #ifndef NBT_INTERNAL_HEAD_BFGUDYHGVWCEARSTYJGHI 2 | #define NBT_INTERNAL_HEAD_BFGUDYHGVWCEARSTYJGHI 3 | 4 | #include 5 | 6 | namespace nbt { 7 | 8 | template 9 | constexpr std::size_t varnum_max() { 10 | return sizeof(number_t) * 8 / 7 + 1; 11 | } 12 | 13 | template 14 | constexpr std::enable_if_t, std::size_t> zint_max() { 15 | return varnum_max(); 16 | } 17 | 18 | template 19 | constexpr std::enable_if_t, std::size_t> zint_max() { 20 | return (sizeof(numeric) * 8 + 1) / 7 + ((sizeof(numeric) * 8 + 1) % 7 > 0); 21 | } 22 | 23 | template 24 | number_t load_varnum(std::istream & input) { 25 | std::size_t numRead = 0; 26 | number_t value = 0; 27 | int read; 28 | do { 29 | read = cheof(input); 30 | number_t tmp = static_cast(read & 0b01111111); 31 | value |= (tmp << (7 * numRead)); 32 | numRead++; 33 | if (numRead > varnum_max()) 34 | throw std::runtime_error("varint is too big"); 35 | } while ((read & 0b10000000) != 0); 36 | return value; 37 | } 38 | 39 | template 40 | void dump_varnum(std::ostream & output, number_t value) { 41 | std::make_unsigned_t uval = value; 42 | do { 43 | auto temp = static_cast(uval & 0b01111111u); 44 | uval >>= 7; 45 | if (uval != 0) 46 | temp |= 0b10000000; 47 | output.put(temp); 48 | } while (uval != 0); 49 | } 50 | 51 | template 52 | std::enable_if_t, numeric> load_zint(std::istream & input) { 53 | return load_varnum(input); 54 | } 55 | 56 | template 57 | std::enable_if_t, numeric> load_zint(std::istream & input) { 58 | std::uint8_t read = cheof(input); 59 | bool sign = read & 1; 60 | numeric value = (read >> 1) & 0b00111111; 61 | unsigned numRead = 0; 62 | while ((read & 0b10000000) != 0) { 63 | read = cheof(input); 64 | numeric tmp = (read & 0b01111111); 65 | value |= (tmp << (7 * numRead + 6)); 66 | ++numRead; 67 | if (numRead > zint_max() - 1) 68 | throw std::runtime_error("szint is too big"); 69 | } 70 | if (sign) 71 | return -value; 72 | else 73 | return value; 74 | } 75 | 76 | template 77 | std::enable_if_t> dump_zint(std::ostream & output, numeric value) { 78 | dump_varnum(output, value); 79 | } 80 | 81 | template 82 | std::enable_if_t> dump_zint(std::ostream & output, numeric value) { 83 | std::uint8_t sign = value < 0; 84 | std::make_unsigned_t uval = sign ? -value : value; 85 | std::uint8_t temp = (static_cast(uval & 0b00111111) << 1) | sign; 86 | uval >>= 6; 87 | if (uval != 0) 88 | temp |= 0b10000000; 89 | output.put(temp); 90 | while (uval != 0) { 91 | temp = static_cast(uval & 0b01111111); 92 | uval >>= 7; 93 | if (uval != 0) 94 | temp |= 0b10000000; 95 | output.put(temp); 96 | } 97 | } 98 | 99 | } // namespace nbt 100 | 101 | #endif // NBT_INTERNAL_HEAD_BFGUDYHGVWCEARSTYJGHI 102 | -------------------------------------------------------------------------------- /src/nbtc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | const std::map contexts_by_name { 8 | { "java", nbt::contexts::java }, 9 | { "bedrock-net", nbt::contexts::bedrock_net }, 10 | { "bedrock", nbt::contexts::bedrock_disk }, 11 | { "bedrock-disk", nbt::contexts::bedrock_disk }, 12 | { "kbt", nbt::contexts::kbt }, 13 | { "mojangson", nbt::contexts::mojangson } 14 | }; 15 | 16 | void usage(std::ostream & output, const char * program) { 17 | output << "Usage: " << program << R"===( 18 | 19 | Parameters: 20 | FROM read stdin as FROM NBT format 21 | TO write NBT tag to stdout as TO format 22 | NBT formats: 23 | java Minecraft Java Edition NBT 24 | bedrock Minecraft Bedrock Edition storage NBT format 25 | bedrock-disk 26 | bedrock-net Minecraft Bedrock Edition network NBT format 27 | mojangson text NBT representation 28 | kbt handtruth NBT extension 29 | )==="; 30 | } 31 | 32 | int main(int argc, char ** argv) { 33 | if (argc == 2 && std::string(argv[1]) == "--help") { 34 | usage(std::cout, argv[0]); 35 | return EXIT_SUCCESS; 36 | } 37 | if (argc != 3) { 38 | std::cerr << "exactly 2 arguments required" << std::endl; 39 | usage(std::cerr, argv[0]); 40 | return EXIT_FAILURE; 41 | } 42 | auto iter_from = contexts_by_name.find(argv[1]); 43 | auto iter_to = contexts_by_name.find(argv[2]); 44 | if (iter_from == contexts_by_name.end()) { 45 | std::cerr << "unknown FROM format: " << argv[1] << std::endl; 46 | usage(std::cerr, argv[0]); 47 | return EXIT_FAILURE; 48 | } 49 | if (iter_to == contexts_by_name.end()) { 50 | std::cerr << "unknown TO format: " << argv[2] << std::endl; 51 | usage(std::cerr, argv[0]); 52 | return EXIT_FAILURE; 53 | } 54 | using namespace nbt; 55 | const context & from = iter_from->second; 56 | const context & to = iter_to->second; 57 | std::unique_ptr root; 58 | auto & input = std::cin; 59 | input >> from; 60 | std::cout << to; 61 | try { 62 | if (from.format == context::formats::mojangson) { 63 | tag_id id = deduce_tag(input); 64 | std::unique_ptr tag = tags::read(id, input); 65 | if (tag->id() == tag_id::tag_compound) { 66 | root = std::move(tag); 67 | } else { 68 | auto compound = std::make_unique(true); 69 | compound->value[""] = std::move(tag); 70 | root = std::move(compound); 71 | } 72 | } else { 73 | root = tags::read(input); 74 | } 75 | } catch (const std::exception & e) { 76 | std::cerr 77 | << "failed to read NBT (stream position " 78 | << input.tellg() << "): " << e.what() << std::endl; 79 | return EXIT_FAILURE; 80 | } 81 | try { 82 | std::cout << *root; 83 | if (context::formats::mojangson == to.format) 84 | std::cout << std::endl; 85 | } catch (const std::exception & e) { 86 | std::cerr 87 | << "failed to write NBT (stream position " 88 | << std::cout.tellp() << "): " << e.what() << std::endl; 89 | return EXIT_FAILURE; 90 | } 91 | return EXIT_SUCCESS; 92 | } 93 | -------------------------------------------------------------------------------- /subprojects/.ignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handtruth/nbt-cpp/cb43446e86a20c9c0446de793dba680ab8bf0647/subprojects/.ignore -------------------------------------------------------------------------------- /test/bedrock_level.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handtruth/nbt-cpp/cb43446e86a20c9c0446de793dba680ab8bf0647/test/bedrock_level.dat -------------------------------------------------------------------------------- /test/bigtest.nbt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/handtruth/nbt-cpp/cb43446e86a20c9c0446de793dba680ab8bf0647/test/bigtest.nbt -------------------------------------------------------------------------------- /test/issue2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "nbt.hpp" 4 | 5 | #include "test.hpp" 6 | 7 | using namespace nbt; 8 | 9 | test { 10 | std::stringstream input(R"({ "voidByteArray": [B; ] })"); 11 | tags::compound_tag root; 12 | input >> contexts::mojangson >> root; 13 | std::string key("voidByteArray"); 14 | auto & bytes = root.get>(key); 15 | assert_true(bytes.empty()); 16 | std::cout << contexts::mojangson << root; 17 | } 18 | -------------------------------------------------------------------------------- /test/issue3.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "nbt.hpp" 4 | 5 | #include "test.hpp" 6 | 7 | using namespace nbt; 8 | 9 | test { 10 | const std::string subject = R"({"list":[[B;54b]]})"; 11 | 12 | std::stringstream input(subject); 13 | tags::compound_tag root; 14 | input >> contexts::mojangson >> root; 15 | const std::string key("list"); 16 | auto & list = dynamic_cast(*root.value[key]).value; 17 | assert_equals(1u, list.size()); 18 | const auto & bytes = list.front(); 19 | assert_equals(1u, bytes.size()); 20 | assert_equals(54, bytes.front()); 21 | 22 | std::stringstream output; 23 | output << contexts::mojangson << root; 24 | assert_equals(subject, output.str()); 25 | } 26 | -------------------------------------------------------------------------------- /test/meson.build: -------------------------------------------------------------------------------- 1 | test_names = [ 2 | 'nbt', 3 | 'issue2', 4 | 'issue3' 5 | ] 6 | 7 | test_files = [] 8 | 9 | foreach test_name : test_names 10 | test_files += files(test_name + '.cpp') 11 | test_exe = executable(test_name + '.test', test_files[-1], link_with : lib, include_directories : [includes, src], dependencies : module_deps) 12 | test(test_name, test_exe, suite : 'regular', workdir : meson.current_source_dir()) 13 | endforeach 14 | -------------------------------------------------------------------------------- /test/nbt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "nbt.hpp" 6 | 7 | #include "test.hpp" 8 | 9 | std::string read_content(const std::string & filename) { 10 | std::ifstream file(filename); 11 | return std::string((std::istreambuf_iterator(file)),std::istreambuf_iterator()); 12 | } 13 | 14 | template 15 | void test_for(const std::string & filename, const nbt::context & ctxt, const std::string & tagname) { 16 | using namespace nbt; 17 | std::string content = read_content(filename); 18 | std::stringstream input(content); 19 | std::array header; 20 | input.read(header.data(), header_size); 21 | tags::compound_tag root; 22 | input >> ctxt >> root; 23 | assert_equals(1u, root.value.size()); 24 | //const auto & values = root.get>>(tagname); 25 | //for (const auto & each : values) 26 | // std::cerr << each.first << ' ' << each.second->id() << std::endl; 27 | std::stringstream buffer; 28 | //output.write(header.data(), header_size); 29 | //output << ctxt << root; 30 | std::cout << contexts::mojangson << root << std::endl; 31 | buffer << contexts::mojangson << root; 32 | tags::compound_tag new_root(true); 33 | try { 34 | buffer >> contexts::mojangson >> new_root; 35 | } catch (...) { 36 | std::cerr << "POSITION: " << buffer.tellg() << std::endl; 37 | throw; 38 | } 39 | std::cout << contexts::mojangson << new_root; 40 | //assert_equals(content, result); 41 | } 42 | 43 | test { 44 | using namespace nbt; 45 | using namespace std::string_literals; 46 | test_for<0>("bigtest.nbt", contexts::java, "Level"); 47 | test_for<8>("bedrock_level.dat", contexts::bedrock_disk, ""); 48 | } 49 | -------------------------------------------------------------------------------- /test/test.hpp: -------------------------------------------------------------------------------- 1 | #ifdef __TEST_HEAD 2 | # error "test.hpp header can't be included several times" 3 | #else 4 | # define __TEST_HEAD 5 | #endif 6 | 7 | // LCOV_EXCL_START 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace std { 16 | inline string to_string(const std::string & str) { 17 | return str; 18 | } 19 | } 20 | 21 | namespace tests { 22 | 23 | class assertion_error : public std::runtime_error { 24 | public: 25 | explicit assertion_error(const std::string & message) : std::runtime_error(message) {} 26 | }; 27 | 28 | struct { 29 | int success = 0; 30 | template 31 | void assert_eq(const first & expect, const second & actual, const char * file, std::size_t line) { 32 | if (actual != expect) { 33 | std::string message = std::string(file) + ":" + std::to_string(line) + ": assertion failed (\"" + std::to_string(expect) + 34 | "\" expected, got \"" + std::to_string(actual) + "\")"; 35 | std::cout << "test failed: " << message << std::endl; 36 | throw assertion_error(message); 37 | } else 38 | success++; 39 | } 40 | template 41 | void assert_ne(const first & expect, const second & actual, const char * file, std::size_t line) { 42 | if (actual == expect) { 43 | std::string message = std::string(file) + ":" + std::to_string(line) + ": assertion failed (\"" + std::to_string(expect) + 44 | "\" equals to \"" + std::to_string(actual) + "\")"; 45 | std::cout << "test failed: " << message << std::endl; 46 | throw assertion_error(message); 47 | } else 48 | success++; 49 | } 50 | template 51 | void assert_ex_with(F fun, const char * file, std::size_t line) { 52 | try { 53 | fun(); 54 | } catch (const E & e) { 55 | success++; 56 | return; 57 | } catch (...) { 58 | std::cout << "test failed: " << std::string(file) << ':' << std::to_string(line) 59 | << ": cought unexpected exception type." << std::endl; 60 | throw; 61 | } 62 | std::string message = std::string(file) + ":" + std::to_string(line) + ": no exception cought."; 63 | std::cout << "test failed: " << message << std::endl; 64 | throw assertion_error(message); 65 | } 66 | template 67 | void assert_ex(F fun, const char * file, std::size_t line) { 68 | try { 69 | fun(); 70 | } catch (...) { 71 | success++; 72 | return; 73 | } 74 | std::string message = std::string(file) + ":" + std::to_string(line) + ": no exception cought."; 75 | std::cout << "test failed: " << message << std::endl; 76 | throw assertion_error(message); 77 | } 78 | void assert_tr(bool value, const char * file, std::size_t line, const char * expression) { 79 | if (value) { 80 | success++; 81 | } else { 82 | std::string message = std::string(file) + ":" + std::to_string(line) + ": value (" + expression + ") was false."; 83 | std::cout << "test failed: " << message << std::endl; 84 | throw assertion_error(message); 85 | } 86 | } 87 | void assert_fa(bool value, const char * file, std::size_t line, const char * expression) { 88 | if (!value) { 89 | success++; 90 | } else { 91 | std::string message = std::string(file) + ":" + std::to_string(line) + ": value (" + expression + ") was true."; 92 | std::cout << "test failed: " << message << std::endl; 93 | throw assertion_error(message); 94 | } 95 | } 96 | } assert; 97 | 98 | #define test \ 99 | void test_function() 100 | 101 | #define assert_equals(expect, actual) \ 102 | ::tests::assert.assert_eq(expect, actual, __FILE__, __LINE__) 103 | 104 | #define assert_not_equals(expect, actual) \ 105 | ::tests::assert.assert_ne(expect, actual, __FILE__, __LINE__) 106 | 107 | #define assert_fails_with(E, fun) \ 108 | ::tests::assert.assert_ex_with([&]() fun, __FILE__, __LINE__) 109 | 110 | #define assert_fails(fun) \ 111 | ::tests::assert.assert_ex([&]() fun, __FILE__, __LINE__) 112 | 113 | #define assert_true(value) \ 114 | ::tests::assert.assert_tr(value, __FILE__, __LINE__, #value) 115 | 116 | #define assert_false(value) \ 117 | ::tests::assert.assert_fa(value, __FILE__, __LINE__, #value) 118 | 119 | } 120 | 121 | void test_function(); 122 | 123 | int main() { 124 | std::cout << "entering test..." << std::endl; 125 | test_function(); 126 | std::cout << "success: " << tests::assert.success << " assertions passed"; 127 | } 128 | 129 | // LCOV_EXCL_STOP 130 | --------------------------------------------------------------------------------