├── .editorconfig ├── .github └── workflows │ └── cmake-multi-platform.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── LICENSE-MIT ├── README.md ├── docs ├── assets │ ├── footer.png │ ├── frog-before.png │ ├── frog-bring-your-own-containers.png │ ├── frog-errors-happen.png │ ├── frog-getting-started.png │ ├── frog-i-can-help-you-avoid-segfaults.png │ ├── frog-remember.png │ ├── frog-uh.png │ ├── frog-what-else-is-in-the-box.png │ ├── frog-writing-portable-code-is-easy-peasy.png │ └── header.png └── examples │ ├── buffer_types.cpp │ ├── containers.cpp │ ├── dynamic_buffer_with_asio.cpp │ ├── endian.cpp │ ├── error_handling.cpp │ ├── find_first_of.cpp │ ├── inject_error.cpp │ ├── polymorphic_binary_stream.cpp │ ├── serialise_custom_type.cpp │ ├── span_deserialisation.cpp │ ├── string_handling.cpp │ ├── string_view_deserialisation.cpp │ └── write_seeking.cpp ├── include ├── CMakeLists.txt └── hexi │ ├── allocators │ ├── block_allocator.h │ ├── default_allocator.h │ └── tls_block_allocator.h │ ├── binary_stream.h │ ├── buffer_adaptor.h │ ├── buffer_sequence.h │ ├── concepts.h │ ├── detail │ └── intrusive_storage.h │ ├── dynamic_buffer.h │ ├── dynamic_tls_buffer.h │ ├── endian.h │ ├── exception.h │ ├── file_buffer.h │ ├── hexi.h │ ├── null_buffer.h │ ├── pmc │ ├── binary_stream.h │ ├── binary_stream_reader.h │ ├── binary_stream_writer.h │ ├── buffer.h │ ├── buffer_adaptor.h │ ├── buffer_base.h │ ├── buffer_read.h │ ├── buffer_read_adaptor.h │ ├── buffer_write.h │ ├── buffer_write_adaptor.h │ └── stream_base.h │ ├── shared.h │ ├── static_buffer.h │ └── stream_adaptors.h ├── single_include ├── hexi.h └── hexi_fwd.h ├── tests ├── CMakeLists.txt ├── binary_stream.cpp ├── binary_stream_pmc.cpp ├── buffer_adaptor.cpp ├── buffer_adaptor_pmc.cpp ├── buffer_utility.cpp ├── data │ └── filebuffer ├── dynamic_buffer.cpp ├── file_buffer.cpp ├── final_action.h ├── helpers.h ├── intrusive_storage.cpp ├── null_buffer.cpp ├── static_buffer.cpp └── tls_block_allocator.cpp └── tools └── amalgamate ├── LICENSE.md ├── README.md ├── amalgamate.json └── amalgamate.py /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | tab_width = 4 7 | end_of_line = lf -------------------------------------------------------------------------------- /.github/workflows/cmake-multi-platform.yml: -------------------------------------------------------------------------------- 1 | # This starter workflow is for a CMake project running on multiple platforms. There is a different starter workflow if you just want a single platform. 2 | # See: https://github.com/actions/starter-workflows/blob/main/ci/cmake-single-platform.yml 3 | name: Unit tests 4 | 5 | on: 6 | push: 7 | branches: [ "master" ] 8 | pull_request: 9 | branches: [ "master" ] 10 | 11 | jobs: 12 | build: 13 | runs-on: ${{ matrix.os }} 14 | 15 | strategy: 16 | # Set fail-fast to false to ensure that feedback is delivered for all matrix combinations. Consider changing this to true when your workflow is stable. 17 | fail-fast: false 18 | 19 | # Set up a matrix to run the following 3 configurations: 20 | # 1. 21 | # 2. 22 | # 3. 23 | # 24 | # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. 25 | matrix: 26 | os: [ubuntu-latest, windows-latest] 27 | build_type: [Release] 28 | c_compiler: [gcc, clang, cl] 29 | include: 30 | - os: windows-latest 31 | c_compiler: cl 32 | cpp_compiler: cl 33 | - os: ubuntu-latest 34 | c_compiler: gcc 35 | cpp_compiler: g++ 36 | - os: ubuntu-latest 37 | c_compiler: clang 38 | cpp_compiler: clang++ 39 | exclude: 40 | - os: windows-latest 41 | c_compiler: gcc 42 | - os: windows-latest 43 | c_compiler: clang 44 | - os: ubuntu-latest 45 | c_compiler: cl 46 | 47 | steps: 48 | - uses: actions/checkout@v4 49 | 50 | - name: Set reusable strings 51 | # Turn repeated input strings (such as the build output directory) into step outputs. These step outputs can be used throughout the workflow file. 52 | id: strings 53 | shell: bash 54 | run: | 55 | echo "build-output-dir=${{ github.workspace }}/build" >> "$GITHUB_OUTPUT" 56 | 57 | - name: Configure CMake 58 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 59 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 60 | run: > 61 | cmake -B ${{ steps.strings.outputs.build-output-dir }} 62 | -DCMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }} 63 | -DCMAKE_C_COMPILER=${{ matrix.c_compiler }} 64 | -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} 65 | -DENABLE_TESTING=1 66 | -S ${{ github.workspace }} 67 | 68 | - name: Build 69 | # Build your program with the given configuration. Note that --config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). 70 | run: cmake --build ${{ steps.strings.outputs.build-output-dir }} --config ${{ matrix.build_type }} 71 | 72 | - name: Test 73 | working-directory: ${{ steps.strings.outputs.build-output-dir }} 74 | # Execute tests defined by the CMake configuration. Note that --build-config is needed because the default Windows generator is a multi-config generator (Visual Studio generator). 75 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 76 | run: ctest --build-config ${{ matrix.build_type }} 77 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | 11 | [Dd]ebug/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | [Bb]in/ 16 | [Oo]bj/ 17 | 18 | # MSTest test Results 19 | [Tt]est[Rr]esult*/ 20 | [Bb]uild[Ll]og.* 21 | 22 | *.Cppcat* 23 | *_i.c 24 | *_p.c 25 | *.ilk 26 | *.meta 27 | *.obj 28 | *.pch 29 | *.pdb 30 | *.pgc 31 | *.pgd 32 | *.rsp 33 | *.sbr 34 | *.tlb 35 | *.tli 36 | *.tlh 37 | *.tmp 38 | *.tmp_proj 39 | *.log 40 | *.vspscc 41 | *.vssscc 42 | .builds 43 | *.pidb 44 | *.log 45 | *.scc 46 | 47 | # Visual C++ cache files 48 | ipch/ 49 | *.aps 50 | *.ncb 51 | *.opensdf 52 | *.sdf 53 | *.cachefile 54 | 55 | # Visual Studio profiler 56 | *.psess 57 | *.vsp 58 | *.vspx 59 | 60 | # Visual Studio 61 | .vs/ 62 | 63 | # Visual Studio Code 64 | .vscode/ 65 | 66 | # Guidance Automation Toolkit 67 | *.gpState 68 | 69 | # ReSharper is a .NET coding add-in 70 | _ReSharper*/ 71 | *.[Rr]e[Ss]harper 72 | 73 | # TeamCity is a build add-in 74 | _TeamCity* 75 | 76 | # DotCover is a Code Coverage Tool 77 | *.dotCover 78 | 79 | # NCrunch 80 | *.ncrunch* 81 | .*crunch*.local.xml 82 | 83 | # Installshield output folder 84 | [Ee]xpress/ 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish/ 98 | 99 | # Publish Web Output 100 | *.Publish.xml 101 | *.pubxml 102 | 103 | # NuGet Packages Directory 104 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 105 | #packages/ 106 | 107 | # Windows Azure Build Output 108 | csx 109 | *.build.csdef 110 | 111 | # Windows Store app package directory 112 | AppPackages/ 113 | 114 | # Others 115 | *.Cache 116 | ClientBin/ 117 | [Ss]tyle[Cc]op.* 118 | ~$* 119 | *~ 120 | *.dbmdl 121 | *.[Pp]ublish.xml 122 | *.pfx 123 | *.publishsettings 124 | 125 | # RIA/Silverlight projects 126 | Generated_Code/ 127 | 128 | # Backup & report files from converting an old project file to a newer 129 | # Visual Studio version. Backup files are not needed, because we have git ;-) 130 | _UpgradeReport_Files/ 131 | Backup*/ 132 | UpgradeLog*.XML 133 | UpgradeLog*.htm 134 | 135 | # SQL Server files 136 | App_Data/*.mdf 137 | App_Data/*.ldf 138 | 139 | # Eclipse 140 | .classpath 141 | .project 142 | .settings/ 143 | 144 | # Intellij 145 | .idea/ 146 | *.iml 147 | *.iws 148 | 149 | # ========================= 150 | # Windows detritus 151 | # ========================= 152 | 153 | # Windows image file caches 154 | Thumbs.db 155 | ehthumbs.db 156 | 157 | # Folder config file 158 | Desktop.ini 159 | 160 | # Recycle Bin used on file shares 161 | $RECYCLE.BIN/ 162 | 163 | # Mac crap 164 | .DS_Store -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # _ _ 2 | # | |__ _____ _(_) 3 | # | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | # | | | | __/> <| | Version 1.0 5 | # |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | cmake_minimum_required(VERSION 3.29) 8 | set(HEXI_PROJECT_NAME hexi) 9 | project(${HEXI_PROJECT_NAME}) 10 | 11 | set(CMAKE_CXX_STANDARD 23) 12 | set(CMAKE_CXX_STANDARD_REQUIRED OFF) 13 | 14 | option(ENABLE_TESTING OFF) 15 | 16 | ############################## 17 | # Google Test # 18 | ############################## 19 | if(ENABLE_TESTING) 20 | find_package(Threads REQUIRED) 21 | include(FetchContent) 22 | 23 | FetchContent_Declare( 24 | googletest 25 | URL https://github.com/google/googletest/releases/download/v1.16.0/googletest-1.16.0.tar.gz 26 | URL_HASH SHA512=bec8dad2a5abbea8e9e5f0ceedd8c9dbdb8939e9f74785476b0948f21f5db5901018157e78387e106c6717326558d6642fc0e39379c62af57bf1205a9df8a18b 27 | ) 28 | 29 | # For Windows: Prevent overriding the parent project's compiler/linker settings 30 | set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) 31 | FetchContent_MakeAvailable(googletest) 32 | set(FETCHCONTENT_FULLY_DISCONNECTED ON) 33 | 34 | include(GoogleTest) 35 | 36 | set(cmake_ctest_arguments "CTEST_OUTPUT_ON_FAILURE") 37 | enable_testing() 38 | add_subdirectory(tests) 39 | endif() 40 | 41 | add_subdirectory(include) 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2025 Hexi 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Hexi 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. -------------------------------------------------------------------------------- /docs/assets/footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/footer.png -------------------------------------------------------------------------------- /docs/assets/frog-before.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/frog-before.png -------------------------------------------------------------------------------- /docs/assets/frog-bring-your-own-containers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/frog-bring-your-own-containers.png -------------------------------------------------------------------------------- /docs/assets/frog-errors-happen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/frog-errors-happen.png -------------------------------------------------------------------------------- /docs/assets/frog-getting-started.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/frog-getting-started.png -------------------------------------------------------------------------------- /docs/assets/frog-i-can-help-you-avoid-segfaults.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/frog-i-can-help-you-avoid-segfaults.png -------------------------------------------------------------------------------- /docs/assets/frog-remember.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/frog-remember.png -------------------------------------------------------------------------------- /docs/assets/frog-uh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/frog-uh.png -------------------------------------------------------------------------------- /docs/assets/frog-what-else-is-in-the-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/frog-what-else-is-in-the-box.png -------------------------------------------------------------------------------- /docs/assets/frog-writing-portable-code-is-easy-peasy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/frog-writing-portable-code-is-easy-peasy.png -------------------------------------------------------------------------------- /docs/assets/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/docs/assets/header.png -------------------------------------------------------------------------------- /docs/examples/buffer_types.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main() { 9 | /** 10 | * std buffer types 11 | */ 12 | 13 | { // read-only std::span 14 | std::array buffer; 15 | std::span span(buffer); 16 | hexi::buffer_adaptor adaptor(span); 17 | hexi::binary_stream stream(adaptor); 18 | } 19 | 20 | { // read-write std::span 21 | std::vector buffer; 22 | std::span span(buffer); 23 | hexi::buffer_adaptor adaptor(span); 24 | hexi::binary_stream stream(adaptor); 25 | } 26 | 27 | { // read-write std::string 28 | std::string buffer; 29 | hexi::buffer_adaptor adaptor(buffer); 30 | hexi::binary_stream stream(adaptor); 31 | } 32 | 33 | { // read-write std::vector 34 | std::vector buffer; 35 | hexi::buffer_adaptor adaptor(buffer); 36 | hexi::binary_stream stream(adaptor); 37 | } 38 | 39 | { // read-only std::array 40 | std::array buffer; 41 | hexi::buffer_adaptor adaptor(buffer); 42 | hexi::binary_stream stream(adaptor); 43 | } 44 | 45 | { // read-write std::array 46 | // by default, writing to an std::array will trigger an error 47 | // to prevent overwriting existing data 48 | // use init_empty to or call .clear() to override 49 | std::array buffer; 50 | hexi::buffer_adaptor adaptor(buffer, hexi::init_empty); 51 | // or: adaptor.clear(); 52 | hexi::binary_stream stream(adaptor); 53 | } 54 | 55 | { // read-only std::string_view 56 | std::string_view buffer; 57 | hexi::buffer_adaptor adaptor(buffer); 58 | hexi::binary_stream stream(adaptor); 59 | } 60 | 61 | /** 62 | * hexi buffer types 63 | */ 64 | 65 | { // read-write hexi::static_buffer 66 | hexi::static_buffer buffer; 67 | hexi::binary_stream stream(buffer); 68 | } 69 | 70 | { // read-write hexi::dynamic_buffer 71 | hexi::dynamic_buffer<128> buffer; 72 | hexi::binary_stream stream(buffer); 73 | } 74 | 75 | { // read-write hexi::file_buffer 76 | hexi::file_buffer buffer("file.tmp"); 77 | hexi::binary_stream stream(buffer); 78 | } 79 | } -------------------------------------------------------------------------------- /docs/examples/containers.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct Component { 7 | int weight; 8 | int combobulating_factor; 9 | 10 | bool operator==(const Component& rhs) const { 11 | return weight == rhs.weight 12 | && combobulating_factor == rhs.combobulating_factor; 13 | } 14 | }; 15 | 16 | class Widget { 17 | std::vector components; 18 | 19 | public: 20 | void add_component(Component&& c) { 21 | components.emplace_back(std::move(c)); 22 | } 23 | 24 | void serialise(auto& stream) { 25 | stream & hexi::prefixed(components); 26 | } 27 | 28 | bool operator==(const Widget& rhs) const { 29 | return components == rhs.components; 30 | } 31 | }; 32 | 33 | int main() { 34 | // note: you can use prefixed or prefixed_varint 35 | // prefixed: uses a 32-bit unsigned integer to store container size 36 | // prefixed_varint: compresses container size into a variable number of bytes (more compact but slower) 37 | std::vector buffer; 38 | hexi::buffer_adaptor ba(buffer); 39 | hexi::binary_stream stream(ba); 40 | 41 | { // trivial types just work 42 | std::vector input { 1, 2, 3, 4, 5 }; 43 | stream << hexi::prefixed(input); 44 | 45 | std::vector output; 46 | stream >> hexi::prefixed(output); 47 | std::cout << std::boolalpha << (input == output) << '\n'; 48 | } 49 | 50 | { // more complicated types 51 | std::vector input; 52 | 53 | // make some widgets 54 | for(auto i = 0; i < 5; ++i) { 55 | Widget w; 56 | w.add_component({ 100, i }); 57 | input.emplace_back(std::move(w)); 58 | } 59 | 60 | stream << hexi::prefixed(input); 61 | 62 | // get them back out 63 | std::vector output; 64 | stream >> hexi::prefixed(output); 65 | std::cout << std::boolalpha << (input == output); 66 | } 67 | } -------------------------------------------------------------------------------- /docs/examples/dynamic_buffer_with_asio.cpp: -------------------------------------------------------------------------------- 1 | #define HEXI_WITH_BOOST_ASIO // HEXI_WITH_ASIO for standalone Asio 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | constexpr auto buffer_block_size = 128uz; 9 | 10 | int main() { 11 | boost::asio::io_context ctx; 12 | boost::asio::ip::tcp::socket socket(ctx); 13 | 14 | // connect the socket and so on 15 | 16 | hexi::dynamic_buffer buffer; 17 | hexi::binary_stream stream(buffer); 18 | 19 | // write a bunch of dummy data to the buffer 20 | for (auto i = 0; i < 10'000; ++i) { 21 | stream << i; 22 | } 23 | 24 | // give Asio a lightweight copyable view of the buffer 25 | const hexi::buffer_sequence sequence(buffer); 26 | socket.send(sequence); 27 | } -------------------------------------------------------------------------------- /docs/examples/endian.cpp: -------------------------------------------------------------------------------- 1 | #define HEXI_WITH_BOOST_ASIO // HEXI_WITH_ASIO for standalone Asio 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main() { 8 | std::vector buffer; 9 | hexi::buffer_adaptor adaptor(buffer); 10 | hexi::binary_stream stream(adaptor); // performs no conversions by default 11 | 12 | { 13 | hexi::binary_stream be_stream(adaptor, hexi::endian::big); // will convert to/from big endian by default 14 | hexi::binary_stream be_stream(adaptor, hexi::endian::little); // will convert to/from little endian by default 15 | hexi::binary_stream be_stream(adaptor, hexi::endian::native); // default, will not convert by default 16 | } 17 | 18 | { // serialise foo & bar as big/little endian with put 19 | const std::uint64_t foo = 100; 20 | const std::uint32_t bar = 200; 21 | stream.put(foo); 22 | stream.put(bar); 23 | } 24 | 25 | { // deserialise foo & bar as big/little endian 26 | std::uint64_t foo = 0; 27 | 28 | // write to existing variable or return result 29 | stream.get(foo); 30 | std::ignore = stream.get(); 31 | } 32 | 33 | { // stream integers as various endian combinations 34 | stream << hexi::endian::be(9000); 35 | stream << hexi::endian::le(9001); // over 9000 36 | stream << hexi::endian::native_to_little(9002); 37 | stream << hexi::endian::little_to_native(9003); 38 | } 39 | 40 | { // retrieve stream integers as big or little endian 41 | std::uint64_t foo; 42 | stream >> hexi::endian::be(foo); 43 | stream >> hexi::endian::le(foo); 44 | } 45 | 46 | { // convert endianness inplace 47 | int foo = 10; 48 | hexi::endian::native_to_big_inplace(foo); 49 | hexi::endian::big_to_native_inplace(foo); 50 | hexi::endian::little_to_native_inplace(foo); 51 | hexi::endian::native_to_little_inplace(foo); 52 | } 53 | 54 | { // retrieve converted value 55 | auto foo = hexi::endian::native_to_big_inplace(1); 56 | auto bar = hexi::endian::big_to_native_inplace(2); 57 | auto baz = hexi::endian::little_to_native_inplace(3); 58 | auto qux = hexi::endian::native_to_little(4); 59 | } 60 | } -------------------------------------------------------------------------------- /docs/examples/error_handling.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | void error_codes(std::span buffer) { 8 | hexi::buffer_adaptor adaptor(buffer); 9 | hexi::binary_stream stream(adaptor, hexi::no_throw); // will not throw exceptions 10 | 11 | // intentionally trigger an error by trying to read out of bounds 12 | stream.skip(buffer.size() + 1); 13 | 14 | if (stream) { 15 | std::println("This shouldn't have worked"); 16 | } else { 17 | std::println("Stream error, {}", std::to_underlying(stream.state())); 18 | } 19 | } 20 | 21 | void exceptions(std::span buffer) try { 22 | hexi::buffer_adaptor adaptor(buffer); 23 | hexi::binary_stream stream(adaptor); 24 | 25 | // intentionally trigger an error by trying to read out of bounds 26 | stream.skip(buffer.size() + 1); 27 | } catch (const hexi::exception& e) { 28 | std::cout << e.what(); 29 | } 30 | 31 | int main() { 32 | std::vector buffer { 1, 2, 3, 4, 5 }; 33 | auto bytes = std::as_bytes(std::span(buffer)); 34 | 35 | // let's try to break stuff! 36 | error_codes(bytes); 37 | exceptions(bytes); 38 | } 39 | -------------------------------------------------------------------------------- /docs/examples/find_first_of.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() { 7 | std::string str { "The quick brown fox jumped over the lazy dog"}; 8 | 9 | // create stream 10 | std::vector buffer; 11 | hexi::buffer_adaptor adaptor(buffer); 12 | hexi::binary_stream stream(adaptor); 13 | 14 | // serialise our string 15 | stream << str; 16 | 17 | // write a sentinel value 18 | stream << 0xff; 19 | 20 | // search the buffer for the sentinel value 21 | const auto pos = adaptor.find_first_of(0xff); 22 | 23 | if (pos != adaptor.npos) { 24 | std::print("Found it!"); 25 | } else { 26 | std::print("Where did it go?"); 27 | } 28 | } -------------------------------------------------------------------------------- /docs/examples/inject_error.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | struct Data { 7 | uint8_t value = 0; // only powers of two are valid values in this example 8 | 9 | auto& operator<<(auto& stream) { 10 | if (value % 2 != 0) { // error if value is not a power of two 11 | stream.set_error_state(); 12 | return stream; 13 | } 14 | 15 | stream << value; 16 | return stream; 17 | } 18 | }; 19 | 20 | int main() { 21 | std::vector buffer; 22 | hexi::buffer_adaptor adaptor(buffer); 23 | hexi::binary_stream stream(adaptor); 24 | 25 | Data foo { 26 | .value = 2 27 | }; 28 | 29 | stream << foo; 30 | 31 | if (!stream) { 32 | std::print("Serialisation failed, should not happen here"); 33 | return EXIT_FAILURE; 34 | } 35 | 36 | foo.value = 3; 37 | 38 | stream << foo; 39 | 40 | if (!stream) { 41 | std::print("Serialisation failed successfully!"); 42 | return EXIT_SUCCESS; 43 | } 44 | 45 | return EXIT_FAILURE; 46 | } -------------------------------------------------------------------------------- /docs/examples/polymorphic_binary_stream.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct UserPacket { 9 | uint64_t user_id; 10 | std::string username; 11 | int64_t timestamp; 12 | uint8_t has_optional_field; 13 | uint32_t optional_field; 14 | 15 | /** 16 | * If serialisation and deserialisation logic differs, we can also 17 | * define operator<< and operator>> for use with stream << type 18 | * and stream >> type 19 | */ 20 | void serialise(auto& stream) { 21 | stream(user_id, username, timestamp, has_optional_field); 22 | 23 | if (has_optional_field) { 24 | stream(optional_field); 25 | 26 | // can also use this syntax: 27 | // stream & optional_field; 28 | } 29 | } 30 | 31 | bool operator==(const UserPacket& rhs) const { 32 | return user_id == rhs.user_id 33 | && username == rhs.username 34 | && timestamp == rhs.timestamp 35 | && has_optional_field == rhs.has_optional_field 36 | && optional_field == rhs.optional_field; 37 | } 38 | }; 39 | int main() { 40 | std::vector buffer; 41 | hexi::pmc::buffer_adaptor adaptor(buffer); 42 | hexi::pmc::binary_stream stream(adaptor); 43 | 44 | UserPacket packet_in { 45 | .user_id = 0, 46 | .username = "Administrator", 47 | .timestamp = time(NULL), 48 | .has_optional_field = 1, 49 | .optional_field = 9001 50 | }; 51 | 52 | // serialise 53 | stream.serialise(packet_in); 54 | 55 | // round-trip deserialise 56 | UserPacket packet_out{}; 57 | stream.deserialise(packet_out); 58 | 59 | if(packet_in == packet_out) { 60 | std::print("Everything went great!"); 61 | } else { 62 | std::print("Something went wrong!"); 63 | } 64 | } -------------------------------------------------------------------------------- /docs/examples/serialise_custom_type.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct UserPacket { 9 | uint64_t user_id; 10 | std::string username; 11 | int64_t timestamp; 12 | uint8_t has_optional_field; 13 | uint32_t optional_field; 14 | 15 | /** 16 | * If serialisation and deserialisation logic differs, we can also 17 | * define operator<< and operator>> for use with stream << type 18 | * and stream >> type 19 | */ 20 | void serialise(auto& stream) { 21 | stream(user_id, username, timestamp, has_optional_field); 22 | 23 | if (has_optional_field) { 24 | stream(optional_field); 25 | 26 | // can also use this syntax: 27 | // stream & optional_field; 28 | } 29 | } 30 | 31 | bool operator==(const UserPacket& rhs) const { 32 | return user_id == rhs.user_id 33 | && username == rhs.username 34 | && timestamp == rhs.timestamp 35 | && has_optional_field == rhs.has_optional_field 36 | && optional_field == rhs.optional_field; 37 | } 38 | }; 39 | 40 | int main() { 41 | std::vector buffer; 42 | hexi::buffer_adaptor adaptor(buffer); 43 | hexi::binary_stream stream(adaptor); 44 | 45 | UserPacket packet_in { 46 | .user_id = 0, 47 | .username = "Administrator", 48 | .timestamp = time(NULL), 49 | .has_optional_field = 1, 50 | .optional_field = 9001 51 | }; 52 | 53 | // serialise 54 | stream.serialise(packet_in); 55 | 56 | // round-trip deserialise 57 | UserPacket packet_out{}; 58 | stream.deserialise(packet_out); 59 | 60 | if(packet_in == packet_out) { 61 | std::print("Everything went great!"); 62 | } else { 63 | std::print("Something went wrong!"); 64 | } 65 | } -------------------------------------------------------------------------------- /docs/examples/span_deserialisation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main() { 9 | // put some random data into a vector 10 | std::vector random_data(10); 11 | std::ranges::generate(random_data, rand); 12 | 13 | // create buffer & stream 14 | std::vector buffer; 15 | hexi::buffer_adaptor adaptor(buffer); 16 | hexi::binary_stream stream(adaptor); 17 | 18 | // write random ints to the stream 19 | stream.put(random_data); 20 | 21 | // get a view over the data - nearly free deserialisation! 22 | auto span = stream.span(10); 23 | 24 | if (std::ranges::equal(span, random_data)) { 25 | std::print("Great!"); 26 | } else { 27 | std::print("Uh oh, something isn't right."); 28 | } 29 | } -------------------------------------------------------------------------------- /docs/examples/string_handling.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | /** 7 | * Hexi provides string adaptors that allow for control over how std::string and 8 | * std::string_views are serialised. This is to ensure the element of least surprise 9 | * in the case of handling strings that contain embedded null bytes. 10 | * 11 | * raw: serialise the string exactly as-is 12 | * 13 | * null_terminated: ensure the string is null-terminated. Strings with null bytes 14 | * will trigger an assert in debug builds. This is to ensure that the input of 15 | * a null_terminated serialisation exactly matches the output. 16 | * 17 | * prefixed: writes the length of the string before the string's data. These strings 18 | * can contain embedded null bytes. 19 | * 20 | * prefixed_varint: writes the length of the string before the string's data with a compact 21 | * varint coding. This is slower than prefixed but more compact. 22 | * These strings can contain embedded null bytes. 23 | * 24 | * const char* strings will always be written as null terminated strings and should be 25 | * retrieved as such. 26 | * 27 | * If no adaptor is provided, prefixed will be implicitly used. This is to ensure that 28 | * the output will always match the input even in cases of embedded nulls. 29 | */ 30 | int main() { 31 | std::string str { "The quick brown fox jumped over the lazy dog"}; 32 | 33 | // create stream 34 | std::vector buffer; 35 | hexi::buffer_adaptor adaptor(buffer); 36 | hexi::binary_stream stream(adaptor); 37 | 38 | // serialise our string - will be prefixed by default 39 | stream << str; 40 | 41 | // serialise our string with null_terminated, will always be null terminated 42 | stream << hexi::null_terminated(str); 43 | 44 | // serialise our string by length prefixing 45 | stream << hexi::prefixed(str); 46 | 47 | // serialise our string by prefixing with a length varint 48 | stream << hexi::prefixed_varint(str); 49 | 50 | // serialise our string with raw, will not be terminated 51 | stream << hexi::raw(str); 52 | 53 | // read our strings back 54 | std::string_view view; 55 | stream >> view; 56 | stream >> hexi::null_terminated(view); 57 | stream >> hexi::prefixed(view); 58 | stream >> hexi::prefixed_varint(view); 59 | 60 | // retrieving a raw string is more complex - we need to know the size 61 | // alternatively, we can write a terminating/sentinel byte manually and 62 | // call find_first_of to determine length or to get a string_view with 63 | // a call to stream.view(); 64 | std::string output; 65 | output.resize(output.size()); 66 | stream.get(output.data(), output.size()); 67 | } -------------------------------------------------------------------------------- /docs/examples/string_view_deserialisation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main() { 7 | std::string str { "The quick brown fox jumped over the lazy dog"}; 8 | 9 | // create stream 10 | std::vector buffer; 11 | hexi::buffer_adaptor adaptor(buffer); 12 | hexi::binary_stream stream(adaptor); 13 | 14 | // serialise our string 15 | stream << str; 16 | 17 | // read it back but into a string_view, no allocations, no copying! 18 | std::string_view view; 19 | stream >> view; 20 | 21 | if (str == view) { 22 | std::print("It matched! :)"); 23 | } else { 24 | std::print("Something went awry... :()"); 25 | } 26 | } -------------------------------------------------------------------------------- /docs/examples/write_seeking.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | struct UserPacket { 9 | uint64_t user_id; 10 | std::string username; 11 | int64_t timestamp; 12 | uint8_t has_optional_field; 13 | uint32_t optional_field; 14 | 15 | auto& operator>>(auto& stream) { 16 | stream >> user_id >> username >> timestamp >> has_optional_field; 17 | 18 | if (has_optional_field) { 19 | stream >> optional_field; 20 | } 21 | 22 | return stream; 23 | } 24 | 25 | auto& operator<<(auto& stream) const { 26 | stream << user_id << username << timestamp << has_optional_field; 27 | 28 | if (has_optional_field) { 29 | stream << optional_field; 30 | } 31 | 32 | return stream; 33 | } 34 | 35 | bool operator==(const UserPacket& rhs) const { 36 | return user_id == rhs.user_id 37 | && username == rhs.username 38 | && timestamp == rhs.timestamp 39 | && has_optional_field == rhs.has_optional_field 40 | && optional_field == rhs.optional_field; 41 | } 42 | }; 43 | 44 | int main() { 45 | std::vector buffer; 46 | hexi::buffer_adaptor adaptor(buffer); 47 | hexi::binary_stream stream(adaptor); 48 | 49 | UserPacket packet_in { 50 | .user_id = 0, 51 | .username = "Administrator", 52 | .timestamp = time(NULL), 53 | .has_optional_field = 1, 54 | .optional_field = 9001 55 | }; 56 | 57 | // reserve bytes at the start of the stream & serialise 58 | stream << std::size_t(0) << packet_in; 59 | 60 | // get number of bytes written 61 | std::size_t written = stream.size(); 62 | 63 | // move write cursor back to the beginning 64 | stream.write_seek(hexi::stream_seek::sk_stream_absolute, 0); 65 | 66 | // write packet size to the beginning of the stream 67 | stream << written; 68 | 69 | // write cursor must be put back before reading 70 | stream.write_seek(hexi::stream_seek::sk_stream_absolute, written); 71 | 72 | // pretend to deserialise 73 | std::size_t read_size{}; 74 | UserPacket packet_out; 75 | stream >> read_size >> packet_out; 76 | 77 | if (read_size == written && packet_in == packet_out) { 78 | std::print("Everything worked!"); 79 | } else { 80 | std::printf("Something went wrong..."); 81 | } 82 | } -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # _ _ 2 | # | |__ _____ _(_) 3 | # | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | # | | | | __/> <| | Version 1.0 5 | # |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | set(HEADERS 8 | hexi/hexi.h 9 | hexi/exception.h 10 | hexi/endian.h 11 | hexi/dynamic_buffer.h 12 | hexi/dynamic_tls_buffer.h 13 | hexi/shared.h 14 | hexi/exception.h 15 | hexi/buffer_adaptor.h 16 | hexi/buffer_sequence.h 17 | hexi/binary_stream.h 18 | hexi/static_buffer.h 19 | hexi/concepts.h 20 | hexi/detail/intrusive_storage.h 21 | hexi/file_buffer.h 22 | hexi/null_buffer.h 23 | hexi/stream_adaptors.h 24 | hexi/pmc/buffer_base.h 25 | hexi/pmc/buffer_read.h 26 | hexi/pmc/buffer_write.h 27 | hexi/pmc/buffer.h 28 | hexi/pmc/binary_stream.h 29 | hexi/pmc/binary_stream_reader.h 30 | hexi/pmc/binary_stream_writer.h 31 | hexi/pmc/stream_base.h 32 | hexi/pmc/buffer_adaptor.h 33 | hexi/pmc/buffer_read_adaptor.h 34 | hexi/pmc/buffer_write_adaptor.h 35 | hexi/allocators/default_allocator.h 36 | hexi/allocators/tls_block_allocator.h 37 | hexi/allocators/block_allocator.h 38 | ) 39 | 40 | add_library(${HEXI_PROJECT_NAME} INTERFACE ${HEADERS}) 41 | target_include_directories(${HEXI_PROJECT_NAME} INTERFACE ../) 42 | -------------------------------------------------------------------------------- /include/hexi/allocators/block_allocator.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #ifndef NDEBUG 20 | #define HEXI_DEBUG_ALLOCATORS 21 | #endif 22 | 23 | namespace hexi { 24 | 25 | namespace detail { 26 | 27 | struct free_block { 28 | free_block* next; 29 | }; 30 | 31 | template 32 | concept gt_zero = size > 0; 33 | 34 | template 35 | concept sizeof_gte = sizeof(T) >= sizeof(U); 36 | 37 | } // detail 38 | 39 | using namespace detail; 40 | 41 | struct no_validate_dealloc {}; 42 | struct validate_dealloc : no_validate_dealloc {}; 43 | 44 | /** 45 | * Basic fixed-size block stack allocator that preallocates a slab of memory 46 | * capable of holding a compile-time determined number of elements. 47 | * When constructed, a linked list of chunks is built within the slab and 48 | * each allocation request will take the head node. Since the allocations 49 | * are fixed-size, the list does not need to be traversed for a suitable 50 | * size. Deallocations place the chunk as the new head (LIFO). 51 | * 52 | * If the preallocated slab runs out of chunks, it will fall back to using the 53 | * system allocator rather than allocating additional slabs. This means sizing 54 | * the initial allocation correctly is important for maximum performance, so 55 | * it's better to be pessimistic. This is a server application and RAM is cheap. :) 56 | * 57 | * PagePolicy: 'lock' requests that the OS does not page out the memory slab to disk. 58 | * 59 | * ThreadPolicy: 'same_thread' triggers an assert if an allocated object 60 | * is deallocated from a different thread. Used by the TLS allocator, since 61 | * implementing the functionality there is messier (and slower). 62 | */ 63 | template ValidatePolicy = no_validate_dealloc> 66 | requires gt_zero<_elements> && sizeof_gte<_ty, free_block> 67 | class block_allocator { 68 | using tid_type = std::conditional_t< 69 | std::is_same_v, std::thread::id, std::monostate 70 | >; 71 | 72 | struct mem_block { 73 | _ty obj; 74 | 75 | struct { 76 | [[no_unique_address]] tid_type thread_id; 77 | bool using_new; 78 | } meta; 79 | }; 80 | 81 | static constexpr auto block_size = sizeof(mem_block); 82 | 83 | free_block* head_ = nullptr; 84 | [[no_unique_address]] tid_type thread_id_; 85 | std::array storage_; 86 | 87 | void initialise_free_list() { 88 | auto storage = storage_.data(); 89 | 90 | for(std::size_t i = 0; i < _elements; ++i) { 91 | auto block = reinterpret_cast(storage + (block_size * i)); 92 | push(block); 93 | } 94 | } 95 | 96 | inline void push(free_block* block) { 97 | assert(block); 98 | block->next = head_; 99 | head_ = block; 100 | } 101 | 102 | [[nodiscard]] inline free_block* pop() { 103 | if(!head_) { 104 | return nullptr; 105 | } 106 | 107 | auto block = head_; 108 | head_ = block->next; 109 | return block; 110 | } 111 | 112 | public: 113 | #ifdef HEXI_DEBUG_ALLOCATORS 114 | std::size_t storage_active_count = 0; 115 | std::size_t new_active_count = 0; 116 | std::size_t active_count = 0; 117 | std::size_t total_allocs = 0; 118 | std::size_t total_deallocs = 0; 119 | #endif 120 | 121 | block_allocator() requires std::same_as 122 | : thread_id_(std::this_thread::get_id()) { 123 | initialise_free_list(); 124 | } 125 | 126 | block_allocator() { 127 | initialise_free_list(); 128 | } 129 | 130 | template 131 | [[nodiscard]] inline _ty* allocate(Args&&... args) { 132 | auto block = reinterpret_cast(pop()); 133 | 134 | if(block) [[likely]] { 135 | #ifdef HEXI_DEBUG_ALLOCATORS 136 | ++storage_active_count; 137 | #endif 138 | block->meta.using_new = false; 139 | } else { 140 | #ifdef HEXI_DEBUG_ALLOCATORS 141 | ++new_active_count; 142 | #endif 143 | block = new mem_block; 144 | block->meta.using_new = true; 145 | } 146 | 147 | if constexpr(std::is_same_v) { 148 | block->meta.thread_id = thread_id_; 149 | } 150 | 151 | #ifdef HEXI_DEBUG_ALLOCATORS 152 | ++total_allocs; 153 | ++active_count; 154 | #endif 155 | return new (&block->obj) _ty(std::forward(args)...); 156 | } 157 | 158 | inline void deallocate(_ty* t) { 159 | assert(t); 160 | auto block = reinterpret_cast(t); 161 | 162 | if constexpr(std::is_same_v) { 163 | assert(block->meta.thread_id == thread_id_ 164 | && "thread policy violation or clobbered block"); 165 | } 166 | 167 | if(block->meta.using_new) [[unlikely]] { 168 | #ifdef HEXI_DEBUG_ALLOCATORS 169 | --new_active_count; 170 | #endif 171 | t->~_ty(); 172 | delete block; 173 | } else { 174 | #ifdef HEXI_DEBUG_ALLOCATORS 175 | --storage_active_count; 176 | #endif 177 | t->~_ty(); 178 | push(reinterpret_cast(t)); 179 | } 180 | 181 | #ifdef HEXI_DEBUG_ALLOCATORS 182 | ++total_deallocs; 183 | --active_count; 184 | #endif 185 | } 186 | 187 | ~block_allocator() { 188 | #ifdef HEXI_DEBUG_ALLOCATORS 189 | assert(active_count == 0); 190 | #endif 191 | } 192 | }; 193 | 194 | } // hexi 195 | -------------------------------------------------------------------------------- /include/hexi/allocators/default_allocator.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | namespace hexi { 12 | 13 | template 14 | struct default_allocator final { 15 | template 16 | [[nodiscard]] inline T* allocate(Args&&... args) const { 17 | return new T(std::forward(args)...); 18 | } 19 | 20 | inline void deallocate(T* t) const { 21 | delete t; 22 | } 23 | }; 24 | 25 | } // hexi 26 | -------------------------------------------------------------------------------- /include/hexi/allocators/tls_block_allocator.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifndef NDEBUG 17 | #define HEXI_DEBUG_ALLOCATORS 18 | #endif 19 | 20 | namespace hexi { 21 | 22 | struct safe_entrant {}; 23 | struct no_ref_counting {}; 24 | 25 | struct unsafe_entrant : safe_entrant {}; 26 | struct ref_counting : no_ref_counting {}; 27 | 28 | template ref_count_policy = no_ref_counting, 31 | std::derived_from entrant_policy = safe_entrant 32 | > 33 | class tls_block_allocator final { 34 | using allocator_type = block_allocator<_ty, _elements>; 35 | 36 | using ref_count = std::conditional_t< 37 | std::is_same_v, int, std::monostate 38 | >; 39 | 40 | using tls_handle_cache = std::conditional_t< 41 | std::is_same_v, allocator_type*, std::monostate 42 | >; 43 | 44 | static inline thread_local std::unique_ptr allocator_; 45 | static inline thread_local ref_count ref_count_{}; 46 | 47 | [[no_unique_address]] tls_handle_cache cached_handle_{}; 48 | 49 | // Compiler will optimise calls to this out when using unsafe_entrant 50 | inline void initialise() { 51 | if constexpr(std::is_same_v) { 52 | if(!allocator_) { 53 | allocator_ = std::make_unique(); 54 | } 55 | } 56 | } 57 | 58 | inline allocator_type* allocator_handle() { 59 | if constexpr(std::is_same_v) { 60 | return cached_handle_; 61 | } else { 62 | return allocator_.get(); 63 | } 64 | } 65 | 66 | public: 67 | #ifdef HEXI_DEBUG_ALLOCATORS 68 | std::size_t total_allocs = 0; 69 | std::size_t total_deallocs = 0; 70 | std::size_t active_allocs = 0; 71 | #endif 72 | 73 | tls_block_allocator() { 74 | thread_enter(); 75 | } 76 | 77 | /* 78 | * When used in conjunction with unsafe_entrant, allows the owning object 79 | * to be executed on another thread without paying for checks on every 80 | * allocation 81 | * 82 | * @note If ref counting is enabled, the count will be incremented. 83 | */ 84 | inline void thread_enter() { 85 | if(!allocator_) { 86 | allocator_ = std::make_unique(); 87 | } 88 | 89 | if constexpr(std::is_same_v) { 90 | cached_handle_ = allocator_.get(); 91 | } 92 | 93 | if constexpr(std::is_same_v) { 94 | ++ref_count_; 95 | } 96 | } 97 | 98 | /* 99 | * When used in conjunction with unsafe_entrant, signals that the current 100 | * thread not make further calls into the allocator. 101 | * 102 | * @note If ref counting is enabled, the count will be decremented. 103 | */ 104 | inline void thread_exit() { 105 | if constexpr(std::is_same_v) { 106 | assert(ref_count_); 107 | 108 | --ref_count_; 109 | 110 | if(ref_count_ == 0) { 111 | allocator_.reset(); 112 | } 113 | } 114 | } 115 | 116 | /* 117 | * @brief Allocates and constructs an object. 118 | * 119 | * @tparam Args Variadic arguments to be forwarded to the object's constructor. 120 | * 121 | * @return Pointer to the allocated object. 122 | */ 123 | template 124 | [[nodiscard]] inline _ty* allocate(Args&&... args) { 125 | /* 126 | * When safe_entrant is set, need to do this here & in ctor unless 127 | * we can be 100% sure that any object using the allocator is created 128 | * on the same thread that ends up using it. 129 | */ 130 | initialise(); 131 | 132 | #ifdef HEXI_DEBUG_ALLOCATORS 133 | ++total_allocs; 134 | ++active_allocs; 135 | #endif 136 | return allocator_handle()->allocate(std::forward(args)...); 137 | } 138 | 139 | /* 140 | * @brief Deallocates and destructs an object. 141 | * 142 | * @param t The object to be deallocated. 143 | */ 144 | inline void deallocate(_ty* t) { 145 | assert(t); 146 | 147 | #ifdef HEXI_DEBUG_ALLOCATORS 148 | ++total_deallocs; 149 | --active_allocs; 150 | #endif 151 | allocator_handle()->deallocate(t); 152 | } 153 | 154 | #ifdef HEXI_DEBUG_ALLOCATORS 155 | auto allocator() { 156 | initialise(); 157 | return allocator_handle(); 158 | } 159 | #endif 160 | 161 | ~tls_block_allocator() { 162 | thread_exit(); 163 | 164 | #ifdef HEXI_DEBUG_ALLOCATORS 165 | assert(active_allocs == 0); 166 | #endif 167 | } 168 | }; 169 | 170 | } // hexi 171 | -------------------------------------------------------------------------------- /include/hexi/buffer_adaptor.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace hexi { 20 | 21 | using namespace detail; 22 | 23 | template 24 | requires std::ranges::contiguous_range 25 | class buffer_adaptor final { 26 | public: 27 | using value_type = typename buf_type::value_type; 28 | using size_type = typename buf_type::size_type; 29 | using offset_type = typename buf_type::size_type; 30 | using contiguous = is_contiguous; 31 | using seeking = supported; 32 | 33 | static constexpr auto npos { static_cast(-1) }; 34 | 35 | private: 36 | buf_type& buffer_; 37 | size_type read_; 38 | size_type write_; 39 | 40 | public: 41 | buffer_adaptor(buf_type& buffer) 42 | : buffer_(buffer), 43 | read_(0), 44 | write_(buffer.size()) {} 45 | 46 | buffer_adaptor(buf_type& buffer, init_empty_t) 47 | : buffer_(buffer), 48 | read_(0), 49 | write_(0) {} 50 | 51 | buffer_adaptor(buffer_adaptor&& rhs) = delete; 52 | buffer_adaptor& operator=(buffer_adaptor&&) = delete; 53 | buffer_adaptor& operator=(const buffer_adaptor&) = delete; 54 | buffer_adaptor(const buffer_adaptor&) = delete; 55 | 56 | /** 57 | * @brief Reads a number of bytes to the provided buffer. 58 | * 59 | * @note The destination buffer must not overlap with the underlying buffer 60 | * being used by the buffer_adaptor. 61 | * 62 | * @tparam T The destination type. 63 | * @param[out] destination The buffer to copy the data to. 64 | */ 65 | template 66 | void read(T* destination) { 67 | read(destination, sizeof(T)); 68 | } 69 | 70 | /** 71 | * @brief Reads a number of bytes to the provided buffer. 72 | * 73 | * @note The destination buffer must not overlap with the underlying buffer 74 | * being used by the buffer_adaptor. 75 | * 76 | * @param[out] destination The buffer to copy the data to. 77 | * @param length The number of bytes to read into the buffer. 78 | */ 79 | void read(void* destination, size_type length) { 80 | assert(destination); 81 | copy(destination, length); 82 | read_ += length; 83 | 84 | if constexpr(space_optimise) { 85 | if(read_ == write_) { 86 | read_ = write_ = 0; 87 | } 88 | } 89 | } 90 | 91 | /** 92 | * @brief Copies a number of bytes to the provided buffer but without advancing 93 | * the read cursor. 94 | * 95 | * @param[out] destination The buffer to copy the data to. 96 | */ 97 | template 98 | void copy(T* destination) const { 99 | copy(destination, sizeof(T)); 100 | } 101 | 102 | /** 103 | * @brief Copies a number of bytes to the provided buffer but without advancing 104 | * the read cursor. 105 | * 106 | * @note The destination buffer must not overlap with the underlying buffer 107 | * being used by the buffer_adaptor. 108 | * 109 | * @param[out] destination The buffer to copy the data to. 110 | * @param length The number of bytes to copy. 111 | */ 112 | void copy(void* destination, size_type length) const { 113 | assert(destination && !region_overlap(buffer_.data(), buffer_.size(), destination, length)); 114 | std::memcpy(destination, read_ptr(), length); 115 | } 116 | 117 | /** 118 | * @brief Skip over a number of bytes. 119 | * 120 | * Skips over a number of bytes from the container. This should be used 121 | * if the container holds data that you don't care about but don't want 122 | * to have to read it to another buffer to access data beyond it. 123 | * 124 | * @param length The number of bytes to skip. 125 | */ 126 | void skip(size_type length) { 127 | read_ += length; 128 | 129 | if constexpr(space_optimise) { 130 | if(read_ == write_) { 131 | read_ = write_ = 0; 132 | } 133 | } 134 | } 135 | 136 | /** 137 | * @brief Write data to the container. 138 | * 139 | * @note The source buffer must not overlap with the underlying buffer 140 | * being used by the buffer_adaptor. 141 | * 142 | * @param source Pointer to the data to be written. 143 | */ 144 | void write(const auto& source) { 145 | write(&source, sizeof(source)); 146 | } 147 | 148 | /** 149 | * @brief Write provided data to the container. 150 | * 151 | * @note The source buffer must not overlap with the underlying buffer 152 | * being used by the buffer_adaptor. 153 | * 154 | * @param source Pointer to the data to be written. 155 | * @param length Number of bytes to write from the source. 156 | */ 157 | void write(const void* source, size_type length) { 158 | assert(source && !region_overlap(source, length, buffer_.data(), buffer_.size())); 159 | const auto min_req_size = write_ + length; 160 | 161 | if(buffer_.size() < min_req_size) [[likely]] { 162 | if constexpr(has_resize_overwrite) { 163 | buffer_.resize_and_overwrite(min_req_size, [](char*, size_type size) { 164 | return size; 165 | }); 166 | } else if constexpr(has_resize) { 167 | buffer_.resize(min_req_size); 168 | } else { 169 | HEXI_THROW(buffer_overflow(length, write_, free())); 170 | } 171 | } 172 | 173 | std::memcpy(write_ptr(), source, length); 174 | write_ += length; 175 | } 176 | 177 | /** 178 | * @brief Reserves a number of bytes within the container for future use. 179 | * 180 | * @note This is a non-binding request, meaning the buffer may not reserve 181 | * any additional space, such as in the case where it is not supported. 182 | * 183 | * @param length The number of bytes that the container should reserve. 184 | */ 185 | void reserve(const size_type length) { 186 | if constexpr(has_reserve) { 187 | buffer_.reserve(length); 188 | } 189 | } 190 | 191 | /** 192 | * @brief Attempts to locate the provided value within the container. 193 | * 194 | * @param value The value to locate. 195 | * 196 | * @return The position of value or npos if not found. 197 | */ 198 | size_type find_first_of(value_type val) const { 199 | const auto data = read_ptr(); 200 | 201 | for(size_type i = 0, j = size(); i < j; ++i) { 202 | if(data[i] == val) { 203 | return i; 204 | } 205 | } 206 | 207 | return npos; 208 | } 209 | 210 | /** 211 | * @brief Returns the size of the container. 212 | * 213 | * @return The number of bytes of data available to read within the container. 214 | */ 215 | size_type size() const { 216 | return write_ - read_; 217 | } 218 | 219 | /** 220 | * @brief Whether the container is empty. 221 | * 222 | * @return Returns true if the container is empty (has no data to be read). 223 | */ 224 | [[nodiscard]] 225 | bool empty() const { 226 | return read_ == write_; 227 | } 228 | 229 | /** 230 | * @brief Retrieves a reference to the specified index within the container. 231 | * 232 | * @param index The index within the container. 233 | * 234 | * @return A reference to the value at the specified index. 235 | */ 236 | value_type& operator[](const size_type index) { 237 | return read_ptr()[index]; 238 | } 239 | 240 | /** 241 | * @brief Retrieves a reference to the specified index within the container. 242 | * 243 | * @param index The index within the container. 244 | * 245 | * @return A reference to the value at the specified index. 246 | */ 247 | const value_type& operator[](const size_type index) const { 248 | return read_ptr()[index]; 249 | } 250 | 251 | /** 252 | * @brief Determine whether the adaptor supports write seeking. 253 | * 254 | * This is determined at compile-time and does not need to checked at 255 | * run-time. 256 | * 257 | * @return True if write seeking is supported, otherwise false. 258 | */ 259 | constexpr static bool can_write_seek() { 260 | return std::is_same_v; 261 | } 262 | 263 | /** 264 | * @brief Performs write seeking within the container. 265 | * 266 | * @param direction Specify whether to seek in a given direction or to absolute seek. 267 | * @param offset The offset relative to the seek direction or the absolute value 268 | * when using absolute seeking. 269 | */ 270 | void write_seek(const buffer_seek direction, const offset_type offset) { 271 | switch(direction) { 272 | case buffer_seek::sk_backward: 273 | write_ -= offset; 274 | break; 275 | case buffer_seek::sk_forward: 276 | write_ += offset; 277 | break; 278 | case buffer_seek::sk_absolute: 279 | write_ = offset; 280 | } 281 | } 282 | 283 | /** 284 | * @return Pointer to the data available for reading. 285 | */ 286 | auto read_ptr() const { 287 | return buffer_.data() + read_; 288 | } 289 | 290 | /** 291 | * @return Pointer to the data available for reading. 292 | */ 293 | auto read_ptr() { 294 | return buffer_.data() + read_; 295 | } 296 | 297 | /** 298 | * @return Pointer to the location within the buffer where the next write 299 | * will be made. 300 | */ 301 | auto write_ptr() const { 302 | return buffer_.data() + write_; 303 | } 304 | 305 | /** 306 | * @return Pointer to the location within the buffer where the next write 307 | * will be made. 308 | */ 309 | auto write_ptr() { 310 | return buffer_.data() + write_; 311 | } 312 | 313 | /** 314 | * @return Pointer to the data available for reading. 315 | */ 316 | auto data() const { 317 | return buffer_.data() + read_; 318 | } 319 | 320 | /** 321 | * @return Pointer to the data available for reading. 322 | */ 323 | auto data() { 324 | return buffer_.data() + read_; 325 | } 326 | 327 | /** 328 | * @return Pointer to the underlying storage. 329 | */ 330 | auto storage() const { 331 | return buffer_.data(); 332 | } 333 | 334 | /** 335 | * @return Pointer to the underlying storage. 336 | */ 337 | auto storage() { 338 | return buffer_.data(); 339 | } 340 | 341 | /** 342 | * @brief Advances the write cursor. 343 | * 344 | * @param size The number of bytes by which to advance the write cursor. 345 | */ 346 | void advance_write(size_type bytes) { 347 | assert(buffer_.size() >= (write_ + bytes)); 348 | write_ += bytes; 349 | } 350 | 351 | /** 352 | * @brief The amount of space left in the container. 353 | * 354 | * @return The number of free bytes. 355 | */ 356 | auto free() const { 357 | return buffer_.size() - write_; 358 | } 359 | 360 | /* 361 | * @brief Resets both the read and write cursors back to the beginning 362 | * of the buffer. 363 | * 364 | * @note The underlying buffer will not be cleared but should be treated 365 | * as thought it has been. 366 | */ 367 | void clear() { 368 | read_ = write_ = 0; 369 | } 370 | }; 371 | 372 | } // hexi 373 | -------------------------------------------------------------------------------- /include/hexi/buffer_sequence.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #if defined HEXI_WITH_ASIO || defined HEXI_WITH_BOOST_ASIO 8 | 9 | #ifdef HEXI_WITH_ASIO 10 | #include 11 | #elif defined HEXI_WITH_BOOST_ASIO 12 | #include 13 | #endif 14 | 15 | #ifdef HEXI_BUFFER_DEBUG 16 | #include 17 | #endif 18 | 19 | namespace hexi { 20 | 21 | #ifdef HEXI_WITH_BOOST_ASIO 22 | namespace asio = boost::asio; 23 | #endif 24 | 25 | template 26 | class buffer_sequence { 27 | const buffer_type& buffer_; 28 | 29 | public: 30 | buffer_sequence(const buffer_type& buffer) 31 | : buffer_(buffer) { } 32 | 33 | class const_iterator { 34 | using node = typename buffer_type::node_type; 35 | 36 | public: 37 | const_iterator(const buffer_type& buffer, const node* curr_node) 38 | : buffer_(buffer), 39 | curr_node_(curr_node) {} 40 | 41 | const_iterator& operator++() { 42 | curr_node_ = curr_node_->next; 43 | return *this; 44 | } 45 | 46 | const_iterator operator++(int) { 47 | const_iterator current(*this); 48 | curr_node_ = curr_node_->next; 49 | return current; 50 | } 51 | 52 | asio::const_buffer operator*() const { 53 | const auto buffer = buffer_.buffer_from_node(curr_node_); 54 | return buffer->read_data(); 55 | } 56 | 57 | bool operator==(const const_iterator& rhs) const { 58 | return curr_node_ == rhs.curr_node_; 59 | } 60 | 61 | bool operator!=(const const_iterator& rhs) const { 62 | return curr_node_ != rhs.curr_node_; 63 | } 64 | 65 | const_iterator& operator=(const_iterator&) = delete; 66 | 67 | #ifdef HEXI_BUFFER_DEBUG 68 | std::span get_buffer() { 69 | auto buffer = buffer_.buffer_from_node(curr_node_); 70 | return { 71 | reinterpret_cast(buffer->read_ptr()), buffer->size() 72 | }; 73 | } 74 | #endif 75 | 76 | private: 77 | const buffer_type& buffer_; 78 | const node* curr_node_; 79 | }; 80 | 81 | const_iterator begin() const { 82 | return const_iterator(buffer_, buffer_.root_.next); 83 | } 84 | 85 | const_iterator end() const { 86 | return const_iterator(buffer_, &buffer_.root_); 87 | } 88 | 89 | friend class const_iterator; 90 | }; 91 | 92 | } // hexi 93 | 94 | #endif // #if defined HEXI_WITH_ASIO || defined HEXI_WITH_BOOST_ASIO -------------------------------------------------------------------------------- /include/hexi/concepts.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace hexi { 16 | 17 | template 18 | concept writeable = 19 | requires(buf_type t, void* v, typename buf_type::size_type s) { 20 | { t.write(v, s) } -> std::same_as; 21 | }; 22 | 23 | template 24 | concept seekable = requires(buf_type t) { 25 | std::is_same_v; 26 | }; 27 | 28 | template 29 | concept contiguous = requires(buf_type t) { 30 | std::is_same_v; 31 | }; 32 | 33 | template 34 | concept arithmetic = std::integral || std::floating_point; 35 | 36 | template 37 | concept byte_type = sizeof(T) == 1; 38 | 39 | template 40 | concept byte_oriented = byte_type; 41 | 42 | template 43 | concept pod = std::is_standard_layout_v && std::is_trivial_v; 44 | 45 | template 46 | concept has_resize_overwrite = 47 | requires(T t) { 48 | { t.resize_and_overwrite(typename T::size_type(), [](char*, T::size_type) {}) } -> std::same_as; 49 | }; 50 | 51 | template 52 | concept has_resize = 53 | requires(T t) { 54 | { t.resize(typename T::size_type() ) } -> std::same_as; 55 | }; 56 | 57 | template 58 | concept has_reserve = 59 | requires(T t) { 60 | { t.reserve(typename T::size_type() ) } -> std::same_as; 61 | }; 62 | 63 | template 64 | concept has_clear = 65 | requires(T t) { 66 | { t.clear() } -> std::same_as; 67 | }; 68 | 69 | template 70 | concept has_shl_override = 71 | requires(T t, U& u) { 72 | { t.operator<<(u) } -> std::same_as; 73 | }; 74 | 75 | template 76 | concept has_shr_override = 77 | requires(T t, U& u) { 78 | { t.operator>>(u) } -> std::same_as; 79 | }; 80 | 81 | template 82 | concept has_serialise = 83 | requires(T t, stream_write_adaptor& u) { 84 | { t.serialise(u) } -> std::same_as; 85 | }; 86 | 87 | template 88 | concept has_deserialise = 89 | requires(T t, stream_read_adaptor& u) { 90 | { t.serialise(u) } -> std::same_as; 91 | }; 92 | 93 | template 94 | concept is_iterable = 95 | requires(T t) { 96 | t.begin(); t.end(); 97 | }; 98 | 99 | template 100 | concept memcpy_read = 101 | pod && std::ranges::contiguous_range 102 | && !has_shr_override 103 | && !has_deserialise; 104 | 105 | template 106 | concept memcpy_write = 107 | pod && std::ranges::contiguous_range 108 | && !has_shl_override 109 | && !has_serialise; 110 | 111 | } // hexi 112 | -------------------------------------------------------------------------------- /include/hexi/detail/intrusive_storage.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #if _MSC_VER 10 | #pragma warning(disable : 4996) 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace hexi::detail { 24 | 25 | struct intrusive_node { 26 | intrusive_node* next; 27 | intrusive_node* prev; 28 | }; 29 | 30 | template 31 | struct intrusive_storage final { 32 | using value_type = storage_type; 33 | using offset_type = std::remove_const_t; 34 | 35 | offset_type read_offset = 0; 36 | offset_type write_offset = 0; 37 | intrusive_node node {}; 38 | std::array storage; 39 | 40 | /** 41 | * @brief Clear the container. 42 | * 43 | * Resets the read and write offsets, does not explicitly 44 | * erase the data contained within the buffer but should 45 | * be treated as such. 46 | */ 47 | void clear() { 48 | read_offset = 0; 49 | write_offset = 0; 50 | } 51 | 52 | /** 53 | * @brief Write provided data to the container. 54 | * 55 | * If the container size is lower than requested number of bytes, 56 | * the request will be capped at the number of bytes available. 57 | * 58 | * @note The source buffer must not overlap with the underlying buffer. 59 | * 60 | * @param source Pointer to the data to be written. 61 | * @param length Number of bytes to write from the source. 62 | * 63 | * @return The number of bytes copied, which may be less than requested. 64 | */ 65 | std::size_t write(const auto source, std::size_t length) { 66 | assert(!region_overlap(source, length, storage.data(), storage.size())); 67 | std::size_t write_len = block_size - write_offset; 68 | 69 | if(write_len > length) { 70 | write_len = length; 71 | } 72 | 73 | std::memcpy(storage.data() + write_offset, source, write_len); 74 | write_offset += static_cast(write_len); 75 | return write_len; 76 | } 77 | 78 | /** 79 | * @brief Copies a number of bytes to the provided buffer but without 80 | * advancing the read cursor. 81 | * 82 | * If the container size is lower than requested number of bytes, 83 | * the request will be capped at the number of bytes available. 84 | * 85 | * @note The destination buffer must not overlap with the underlying buffer. 86 | * 87 | * @param[out] destination The buffer to copy the data to. 88 | * @param length The number of bytes to copy. 89 | * 90 | * @return The number of bytes copied, which may be less than requested. 91 | */ 92 | std::size_t copy(auto destination, const std::size_t length) const { 93 | assert(!region_overlap(storage.data(), storage.size(), destination, length)); 94 | std::size_t read_len = block_size - read_offset; 95 | 96 | if(read_len > length) { 97 | read_len = length; 98 | } 99 | 100 | std::memcpy(destination, storage.data() + read_offset, read_len); 101 | return read_len; 102 | } 103 | 104 | /** 105 | * @brief Reads a number of bytes to the provided buffer. 106 | * 107 | * If the container size is lower than requested number of bytes, 108 | * the request will be capped at the number of bytes available. 109 | * 110 | * @note The destination buffer must not overlap with the underlying buffer. 111 | * 112 | * @param length The number of bytes to skip. 113 | * @param[out] destination The buffer to copy the data to. 114 | * @param allow_optimise Whether the buffer can reuse its space where possible. 115 | * 116 | * @return The number of bytes read, which may be less than requested. 117 | */ 118 | std::size_t read(auto destination, const std::size_t length, const bool allow_optimise = false) { 119 | std::size_t read_len = copy(destination, length); 120 | read_offset += static_cast(read_len); 121 | 122 | if(read_offset == write_offset && allow_optimise) { 123 | clear(); 124 | } 125 | 126 | return read_len; 127 | } 128 | 129 | /** 130 | * @brief Skip over a number of bytes. 131 | * 132 | * Skips over a number of bytes in the container. This should be used 133 | * if the container holds data that you don't care about but don't want 134 | * to have to read it to another buffer to access data beyond it. 135 | * 136 | * If the container size is lower than requested number of bytes, 137 | * the request will be capped at the number of bytes available. 138 | * 139 | * @param length The number of bytes to skip. 140 | * @param allow_optimise Whether the buffer can reuse its space where possible. 141 | * 142 | * @return The number of bytes skipped, which may be less than requested. 143 | */ 144 | std::size_t skip(const std::size_t length, const bool allow_optimise = false) { 145 | std::size_t skip_len = block_size - read_offset; 146 | 147 | if(skip_len > length) { 148 | skip_len = length; 149 | } 150 | 151 | read_offset += static_cast(skip_len); 152 | 153 | if(read_offset == write_offset && allow_optimise) { 154 | clear(); 155 | } 156 | 157 | return skip_len; 158 | } 159 | 160 | /** 161 | * @brief Returns the size of the container. 162 | * 163 | * @return The number of bytes available to read within the container. 164 | */ 165 | std::size_t size() const { 166 | return write_offset - read_offset; 167 | } 168 | 169 | /** 170 | * @brief The amount of free space. 171 | * 172 | * @return The number of bytes of free space within the container. 173 | */ 174 | std::size_t free() const { 175 | return block_size - write_offset; 176 | } 177 | 178 | /** 179 | * @brief Performs write seeking within the container. 180 | * 181 | * @param direction Specify whether to seek in a given direction or to absolute seek. 182 | * @param offset The offset relative to the seek direction or the absolute value 183 | * when using absolute seeking. 184 | */ 185 | void write_seek(const buffer_seek direction, const std::size_t offset) { 186 | switch(direction) { 187 | case buffer_seek::sk_absolute: 188 | write_offset = offset; 189 | break; 190 | case buffer_seek::sk_backward: 191 | write_offset -= static_cast(offset); 192 | break; 193 | case buffer_seek::sk_forward: 194 | write_offset += static_cast(offset); 195 | break; 196 | } 197 | } 198 | 199 | /** 200 | * @brief Advances the write cursor. 201 | * 202 | * If the requested number of bytes exceeds the free space, the 203 | * request will be capped at the amount of free space. 204 | * 205 | * @param size The number of bytes by which to advance the write cursor. 206 | * 207 | * @return The number of bytes the write cursor was advanced by, which 208 | * may be less than requested. 209 | */ 210 | std::size_t advance_write(std::size_t size) { 211 | const auto remaining = free(); 212 | 213 | if(remaining < size) { 214 | size = remaining; 215 | } 216 | 217 | write_offset += static_cast(size); 218 | return size; 219 | } 220 | 221 | /** 222 | * @brief Retrieve a pointer to the readable portion of the buffer. 223 | * 224 | * @return Pointer to the readable portion of the buffer. 225 | */ 226 | const value_type* read_ptr() const { 227 | return storage.data() + read_offset; 228 | } 229 | 230 | /** 231 | * @brief Retrieve a pointer to the readable portion of the buffer. 232 | * 233 | * @return Pointer to the readable portion of the buffer. 234 | */ 235 | value_type* read_ptr() { 236 | return storage.data() + read_offset; 237 | } 238 | 239 | /** 240 | * @brief Retrieve a pointer to the writeable portion of the buffer. 241 | * 242 | * @return Pointer to the writeable portion of the buffer. 243 | */ 244 | const value_type* write_ptr () const { 245 | return storage.data() + write_offset; 246 | } 247 | 248 | /** 249 | * @brief Retrieve a pointer to the writeable portion of the buffer. 250 | * 251 | * @return Pointer to the writeable portion of the buffer. 252 | */ 253 | value_type* write_ptr() { 254 | return storage.data() + write_offset; 255 | } 256 | 257 | /** 258 | * @brief Retrieve a span to the readable portion of the buffer. 259 | * 260 | * @return Span to the readable portion of the buffer. 261 | */ 262 | std::span read_data() const { 263 | return { storage.data() + read_offset, size() }; 264 | } 265 | 266 | /** 267 | * @brief Retrieve a span to the readable portion of the buffer. 268 | * 269 | * @return Span to the readable portion of the buffer. 270 | */ 271 | std::span read_data() { 272 | return { storage.data() + read_offset, size() } ; 273 | } 274 | 275 | /** 276 | * @brief Retrieve a span to the writeable portion of the buffer. 277 | * 278 | * @return Span to the writeable portion of the buffer. 279 | */ 280 | std::span write_data() const { 281 | return { storage.data() + write_offset, free() } ; 282 | } 283 | 284 | /** 285 | * @brief Retrieve a span to the writeable portion of the buffer. 286 | * 287 | * @return Span to the writeable portion of the buffer. 288 | */ 289 | std::span write_data() { 290 | return { storage.data() + write_offset, free() } ; 291 | } 292 | 293 | /** 294 | * @brief Retrieves a reference to the specified index within the container. 295 | * 296 | * @param index The index within the container. 297 | * 298 | * @return A reference to the value at the specified index. 299 | */ 300 | value_type& operator[](const std::size_t index) { 301 | return *(storage.data() + index); 302 | } 303 | 304 | /** 305 | * @brief Retrieves a reference to the specified index within the container. 306 | * 307 | * @param index The index within the container. 308 | * 309 | * @return A reference to the value at the specified index. 310 | */ 311 | const value_type& operator[](const std::size_t index) const { 312 | return *(storage.data() + index); 313 | } 314 | }; 315 | 316 | } // detail, hexi 317 | -------------------------------------------------------------------------------- /include/hexi/dynamic_tls_buffer.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace hexi { 14 | 15 | // dynamic_buffer backed by thread-local storage, meaning every 16 | // instance on the same thread shares the same underlying memory. 17 | // As a rule of thumb, an instance should never be touched by any thread 18 | // other than the one on which it was created, not even if synchronised 19 | // ... unless you're positive it won't result in the allocator being called. 20 | // 21 | // Minimum memory usage is intrusive_storage * count. 22 | // Additional blocks are not added if the original is exhausted ('colony' structure), 23 | // so the allocator will fall back to the system allocator instead. 24 | // 25 | // Pros: extremely fast allocation/deallocation for many instances per thread 26 | // Cons: everything else. 27 | // 28 | // TL;DR Do not use unless you know what you're doing. 29 | template 34 | using dynamic_tls_buffer = dynamic_buffer::storage_type, count, no_ref_counting, entrant_policy> 36 | >; 37 | 38 | } // hexi 39 | -------------------------------------------------------------------------------- /include/hexi/endian.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace hexi::endian { 16 | 17 | enum class conversion { 18 | big_to_native, 19 | native_to_big, 20 | little_to_native, 21 | native_to_little 22 | }; 23 | 24 | constexpr auto conditional_reverse(arithmetic auto value, std::endian from, std::endian to) { 25 | using type = decltype(value); 26 | 27 | if(from != to) { 28 | if constexpr(std::is_same_v) { 29 | value = std::bit_cast(std::byteswap(std::bit_cast(value))); 30 | } else if constexpr(std::is_same_v) { 31 | value = std::bit_cast(std::byteswap(std::bit_cast(value))); 32 | } else { 33 | value = std::byteswap(value); 34 | } 35 | } 36 | 37 | return value; 38 | } 39 | 40 | template 41 | constexpr auto conditional_reverse(arithmetic auto value) { 42 | using type = decltype(value); 43 | 44 | if constexpr(from != to) { 45 | if constexpr(std::is_same_v) { 46 | value = std::bit_cast(std::byteswap(std::bit_cast(value))); 47 | } else if constexpr(std::is_same_v) { 48 | value = std::bit_cast(std::byteswap(std::bit_cast(value))); 49 | } else { 50 | value = std::byteswap(value); 51 | } 52 | } 53 | 54 | return value; 55 | } 56 | 57 | constexpr auto little_to_native(arithmetic auto value) { 58 | return conditional_reverse(value); 59 | } 60 | 61 | constexpr auto big_to_native(arithmetic auto value) { 62 | return conditional_reverse(value); 63 | } 64 | 65 | constexpr auto native_to_little(arithmetic auto value) { 66 | return conditional_reverse(value); 67 | } 68 | 69 | constexpr auto native_to_big(arithmetic auto value) { 70 | return conditional_reverse(value); 71 | } 72 | 73 | template 74 | constexpr void conditional_reverse_inplace(arithmetic auto& value) { 75 | using type = std::remove_reference_t; 76 | 77 | if constexpr(from != to) { 78 | if constexpr(std::is_same_v) { 79 | value = std::bit_cast(std::byteswap(std::bit_cast(value))); 80 | } else if constexpr(std::is_same_v) { 81 | value = std::bit_cast(std::byteswap(std::bit_cast(value))); 82 | } else { 83 | value = std::byteswap(value); 84 | } 85 | } 86 | } 87 | 88 | constexpr void conditional_reverse_inplace(arithmetic auto& value, std::endian from, std::endian to) { 89 | using type = std::remove_reference_t; 90 | 91 | if(from != to) { 92 | if constexpr(std::is_same_v) { 93 | value = std::bit_cast(std::byteswap(std::bit_cast(value))); 94 | } else if constexpr(std::is_same_v) { 95 | value = std::bit_cast(std::byteswap(std::bit_cast(value))); 96 | } else { 97 | value = std::byteswap(value); 98 | } 99 | } 100 | } 101 | 102 | constexpr void little_to_native_inplace(arithmetic auto& value) { 103 | conditional_reverse_inplace(value); 104 | } 105 | 106 | constexpr void big_to_native_inplace(arithmetic auto& value) { 107 | conditional_reverse_inplace(value); 108 | } 109 | 110 | constexpr void native_to_little_inplace(arithmetic auto& value) { 111 | conditional_reverse_inplace(value); 112 | } 113 | 114 | constexpr void native_to_big_inplace(arithmetic auto& value) { 115 | conditional_reverse_inplace(value); 116 | } 117 | 118 | template 119 | constexpr auto convert(arithmetic auto value) -> decltype(value) { 120 | switch(_conversion) { 121 | case conversion::big_to_native: 122 | return big_to_native(value); 123 | case conversion::native_to_big: 124 | return native_to_big(value); 125 | case conversion::little_to_native: 126 | return little_to_native(value); 127 | case conversion::native_to_little: 128 | return native_to_little(value); 129 | default: 130 | std::unreachable(); 131 | }; 132 | } 133 | 134 | struct adaptor_tag_t {}; 135 | 136 | #define ENDIAN_ADAPTOR(name, func_to, func_from) \ 137 | template \ 138 | struct name final : adaptor_tag_t { \ 139 | T& value; \ 140 | \ 141 | name(T& t) : value(t) {} \ 142 | name(T&& t) : value(t) {} \ 143 | \ 144 | auto to() -> T { \ 145 | return func_to(value); \ 146 | } \ 147 | auto from() -> T { \ 148 | return func_from(value); \ 149 | } \ 150 | }; 151 | 152 | ENDIAN_ADAPTOR(be, native_to_big, big_to_native) 153 | ENDIAN_ADAPTOR(le, native_to_little, little_to_native) 154 | 155 | struct storage_tag {}; 156 | struct as_big_t final : storage_tag {}; 157 | struct as_little_t final : storage_tag {}; 158 | struct as_native_t final : storage_tag {}; 159 | 160 | [[maybe_unused]] constexpr static as_big_t big {}; 161 | [[maybe_unused]] constexpr static as_little_t little {}; 162 | [[maybe_unused]] constexpr static as_native_t native {}; 163 | 164 | inline auto storage_in(const arithmetic auto& value, as_native_t) { 165 | return value; 166 | } 167 | 168 | inline auto storage_in(const arithmetic auto& value, as_little_t) { 169 | return native_to_little(value); 170 | } 171 | 172 | inline auto storage_in(const arithmetic auto& value, as_big_t) { 173 | return native_to_big(value); 174 | } 175 | 176 | inline void storage_out(arithmetic auto& value, as_native_t) {} 177 | 178 | inline void storage_out(arithmetic auto& value, as_little_t) { 179 | return little_to_native_inplace(value); 180 | } 181 | 182 | inline void storage_out(arithmetic auto& value, as_big_t) { 183 | return big_to_native_inplace(value); 184 | } 185 | 186 | 187 | } // endian, hexi -------------------------------------------------------------------------------- /include/hexi/exception.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace hexi { 15 | 16 | class exception : public std::runtime_error { 17 | public: 18 | exception(std::string msg) 19 | : std::runtime_error(std::move(msg)) {} 20 | }; 21 | 22 | class buffer_underrun final : public exception { 23 | public: 24 | const std::size_t buff_size, read_size, total_read; 25 | 26 | buffer_underrun(std::size_t read_size, std::size_t total_read, std::size_t buff_size) 27 | : exception(std::format( 28 | "Buffer underrun: {} byte read requested, buffer contains {} bytes and total bytes read was {}", 29 | read_size, buff_size, total_read)), 30 | buff_size(buff_size), read_size(read_size), total_read(total_read) {} 31 | }; 32 | 33 | class buffer_overflow final : public exception { 34 | public: 35 | const std::size_t free, write_size, total_write; 36 | 37 | buffer_overflow(std::size_t write_size, std::size_t total_write, std::size_t free) 38 | : exception(std::format( 39 | "Buffer overflow: {} byte write requested, free space is {} bytes and total bytes written was {}", 40 | write_size, free, total_write)), 41 | free(free), write_size(write_size), total_write(total_write) {} 42 | }; 43 | 44 | class stream_read_limit final : public exception { 45 | public: 46 | const std::size_t read_limit, read_size, total_read; 47 | 48 | stream_read_limit(std::size_t read_size, std::size_t total_read, std::size_t read_limit) 49 | : exception(std::format( 50 | "Read boundary exceeded: {} byte read requested, read limit was {} bytes and total bytes read was {}", 51 | read_size, read_limit, total_read)), 52 | read_limit(read_limit), read_size(read_size), total_read(total_read) {} 53 | }; 54 | 55 | } // hexi 56 | -------------------------------------------------------------------------------- /include/hexi/file_buffer.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace hexi { 18 | 19 | using namespace detail; 20 | 21 | class file_buffer final { 22 | public: 23 | using size_type = std::size_t; 24 | using offset_type = long; 25 | using value_type = char; 26 | using contiguous = is_non_contiguous; 27 | using seeking = unsupported; 28 | 29 | static constexpr auto npos { static_cast(-1) }; 30 | 31 | private: 32 | FILE* file_ = nullptr; 33 | offset_type read_ = 0; 34 | offset_type write_ = 0; 35 | bool error_ = false; 36 | 37 | /** 38 | * @brief Explicitly close the underlying file handle. 39 | * 40 | * This function will be called by the object's destructor 41 | * and does not need to be called explicitly. 42 | * 43 | * @note Idempotent. 44 | */ 45 | void close() { 46 | if(file_) { 47 | std::fclose(file_); 48 | file_ = nullptr; 49 | } 50 | } 51 | 52 | public: 53 | file_buffer(const std::filesystem::path& path) 54 | : file_buffer(path.string().c_str()) { } 55 | 56 | file_buffer(const char* path) { 57 | file_ = std::fopen(path, "a+b"); 58 | 59 | if(!file_) { 60 | error_ = true; 61 | return; 62 | } 63 | 64 | if(std::fseek(file_, 0, SEEK_END)) { 65 | error_ = true; 66 | return; 67 | } 68 | 69 | write_ = std::ftell(file_); 70 | 71 | if(write_ == -1) { 72 | error_ = true; 73 | } 74 | } 75 | 76 | file_buffer(file_buffer&& rhs) noexcept 77 | : file_(rhs.file_), 78 | read_(rhs.read_), 79 | write_(rhs.write_), 80 | error_(rhs.error_) { 81 | rhs.file_ = nullptr; 82 | } 83 | 84 | file_buffer& operator=(file_buffer&& rhs) noexcept { 85 | std::exchange(file_, rhs.file_); 86 | read_ = rhs.read_; 87 | write_ = rhs.write_; 88 | error_ = rhs.error_; 89 | return *this; 90 | } 91 | 92 | file_buffer& operator=(const file_buffer&) = delete; 93 | file_buffer(const file_buffer&) = delete; 94 | 95 | ~file_buffer() { 96 | close(); 97 | } 98 | 99 | 100 | /** 101 | * @brief Flush unwritten data. 102 | */ 103 | void flush() { 104 | std::fflush(file_); 105 | } 106 | 107 | /** 108 | * @brief Reads a number of bytes to the provided buffer. 109 | * 110 | * @param destination The buffer to copy the data to. 111 | */ 112 | template 113 | void read(T* destination) { 114 | read(destination, sizeof(T)); 115 | } 116 | 117 | /** 118 | * @brief Reads a number of bytes to the provided buffer. 119 | * 120 | * @param destination The buffer to copy the data to. 121 | * @param length The number of bytes to read into the buffer. 122 | */ 123 | void read(void* destination, size_type length) { 124 | if(error_) { 125 | return; 126 | } 127 | 128 | if(std::fseek(file_, read_, 0)) { 129 | error_ = true; 130 | return; 131 | } 132 | 133 | if(std::fread(destination, length, 1, file_) != 1) { 134 | error_ = true; 135 | return; 136 | } 137 | 138 | read_ += length; 139 | } 140 | 141 | /** 142 | * @brief Copies a number of bytes to the provided buffer but without advancing 143 | * the read cursor. 144 | * 145 | * @param destination The buffer to copy the data to. 146 | */ 147 | template 148 | void copy(T* destination) { 149 | copy(destination, sizeof(T)); 150 | } 151 | 152 | /** 153 | * @brief Copies a number of bytes to the provided buffer but without advancing 154 | * the read cursor. 155 | * 156 | * @param destination The buffer to copy the data to. 157 | * @param length The number of bytes to copy. 158 | */ 159 | void copy(void* destination, size_type length) { 160 | if(error_) { 161 | return; 162 | } else if(length > size()) { 163 | error_ = true; 164 | HEXI_THROW(buffer_underrun(length, read_, size())); 165 | } 166 | 167 | if(std::fseek(file_, read_, SEEK_SET)) { 168 | error_ = true; 169 | return; 170 | } 171 | 172 | if(std::fread(destination, length, 1, file_) != 1) { 173 | error_ = true; 174 | return; 175 | } 176 | } 177 | 178 | /** 179 | * @brief Attempts to locate the provided value within the container. 180 | * 181 | * @param value The value to locate. 182 | * 183 | * @return The position of value or npos if not found. 184 | */ 185 | size_type find_first_of(value_type val) noexcept { 186 | if(error_) { 187 | return npos; 188 | } 189 | 190 | if(std::fseek(file_, read_, SEEK_SET)) { 191 | error_ = true; 192 | return npos; 193 | } 194 | 195 | value_type buffer{}; 196 | 197 | for(size_type i = 0, j = size(); i < j; ++i) { 198 | if(std::fread(&buffer, sizeof(value_type), 1, file_) != 1) { 199 | error_ = true; 200 | return npos; 201 | } 202 | 203 | if(buffer == val) { 204 | return i; 205 | } 206 | } 207 | 208 | return npos; 209 | } 210 | 211 | /** 212 | * @brief Skip a requested number of bytes. 213 | * 214 | * Skips over a number of bytes from the file. This should be used 215 | * if the file holds data that you don't care about but don't want 216 | * to have to read it to another buffer to access data beyond it. 217 | * 218 | * @param length The number of bytes to skip. 219 | */ 220 | void skip(const size_type length) { 221 | read_ += length; 222 | } 223 | 224 | /** 225 | * @brief Whether the container is empty. 226 | * 227 | * @return Returns true if the container is empty (has no data to be read). 228 | */ 229 | [[nodiscard]] 230 | bool empty() const { 231 | return write_ == read_; 232 | } 233 | 234 | /** 235 | * @brief Determine whether the adaptor supports write seeking. 236 | * 237 | * This is determined at compile-time and does not need to checked at 238 | * run-time. 239 | * 240 | * @return True if write seeking is supported, otherwise false. 241 | */ 242 | constexpr static bool can_write_seek() { 243 | return std::is_same_v; 244 | } 245 | 246 | /** 247 | * @brief Write data to the container. 248 | * 249 | * @param source Pointer to the data to be written. 250 | */ 251 | void write(const auto& source) { 252 | write(&source, sizeof(source)); 253 | } 254 | 255 | /** 256 | * @brief Write provided data to the file. 257 | * 258 | * @param source Pointer to the data to be written. 259 | * @param length Number of bytes to write from the source. 260 | */ 261 | void write(const void* source, size_type length) { 262 | if(error_) { 263 | return; 264 | } 265 | 266 | if(std::fseek(file_, write_, SEEK_SET)) { 267 | error_ = true; 268 | return; 269 | } 270 | 271 | if(std::fwrite(source, length, 1, file_) != 1) { 272 | error_ = true; 273 | return; 274 | } 275 | 276 | write_ += length; 277 | } 278 | 279 | 280 | /** 281 | * @brief Returns the amount of data available in the file. 282 | * 283 | * @return The number of bytes of data available to read from the file. 284 | */ 285 | size_type size() const { 286 | return static_cast(write_) - read_; 287 | } 288 | 289 | /** 290 | * @brief Retrieve the file handle. 291 | * 292 | * @return The file handle. 293 | */ 294 | FILE* handle() { 295 | return file_; 296 | } 297 | 298 | /** 299 | * @brief Retrieve the file handle. 300 | * 301 | * @return The file handle. 302 | */ 303 | const FILE* handle() const { 304 | return file_; 305 | } 306 | 307 | /** 308 | * @return True if an error has occurred during file operations. 309 | */ 310 | bool error() const { 311 | return error_; 312 | } 313 | 314 | /** 315 | * @return True if no error has occurred during file operations. 316 | */ 317 | operator bool() const { 318 | return !error(); 319 | } 320 | }; 321 | 322 | } // hexi 323 | -------------------------------------------------------------------------------- /include/hexi/hexi.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include -------------------------------------------------------------------------------- /include/hexi/null_buffer.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace hexi { 15 | 16 | class null_buffer final : public pmc::buffer_write { 17 | public: 18 | using size_type = std::size_t; 19 | using offset_type = std::size_t; 20 | using value_type = std::byte; 21 | using contiguous = is_contiguous; 22 | using seeking = unsupported; 23 | 24 | void write(const auto& /*elem*/) {} 25 | void write(const void* /*source*/, size_type /*length*/) override {}; 26 | void read(auto* /*elem*/) {} 27 | void read(void* /*destination*/, size_type /*length*/) {}; 28 | void copy(auto* /*elem*/) const {} 29 | void copy(void* /*destination*/, size_type /*length*/) const {}; 30 | void reserve(const size_type /*length*/) override {}; 31 | size_type size() const override{ return 0; }; 32 | [[nodiscard]] bool empty() const override { return true; }; 33 | bool can_write_seek() const override { return false; } 34 | 35 | void write_seek(const buffer_seek /*direction*/, const std::size_t /*offset*/) override { 36 | HEXI_THROW(exception("Don't do this on a null_buffer")); 37 | }; 38 | }; 39 | 40 | } // hexi 41 | -------------------------------------------------------------------------------- /include/hexi/pmc/binary_stream.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace hexi::pmc { 16 | 17 | class binary_stream final : public binary_stream_reader, public binary_stream_writer { 18 | public: 19 | explicit binary_stream(hexi::pmc::buffer& source, std::size_t read_limit = 0) 20 | : stream_base(source), 21 | binary_stream_reader(source, read_limit), 22 | binary_stream_writer(source) {} 23 | 24 | explicit binary_stream(hexi::pmc::buffer& source, hexi::no_throw_t, std::size_t read_limit = 0) 25 | : stream_base(source), 26 | binary_stream_reader(source, no_throw, read_limit), 27 | binary_stream_writer(source, no_throw) {} 28 | 29 | ~binary_stream() override = default; 30 | }; 31 | 32 | } // pmc, hexi 33 | -------------------------------------------------------------------------------- /include/hexi/pmc/binary_stream_writer.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace hexi::pmc { 25 | 26 | using namespace detail; 27 | 28 | class binary_stream_writer : virtual public stream_base { 29 | buffer_write& buffer_; 30 | std::size_t total_write_; 31 | 32 | inline void write(const void* data, const std::size_t size) { 33 | HEXI_TRY { 34 | if(state() == stream_state::ok) [[likely]] { 35 | buffer_.write(data, size); 36 | total_write_ += size; 37 | } 38 | } HEXI_CATCH(...) { 39 | set_state(stream_state::buff_write_err); 40 | 41 | if(allow_throw()) { 42 | HEXI_THROW(); 43 | } 44 | } 45 | } 46 | 47 | template 48 | void write_container(container_type& container) { 49 | using cvalue_type = typename container_type::value_type; 50 | 51 | if constexpr(memcpy_write) { 52 | const auto bytes = container.size() * sizeof(cvalue_type); 53 | write(container.data(), bytes); 54 | } else { 55 | for(auto& element : container) { 56 | *this << element; 57 | } 58 | } 59 | } 60 | 61 | public: 62 | explicit binary_stream_writer(buffer_write& source) 63 | : stream_base(source), 64 | buffer_(source), 65 | total_write_(0) {} 66 | 67 | explicit binary_stream_writer(buffer_write& source, no_throw_t) 68 | : stream_base(source, false), 69 | buffer_(source), 70 | total_write_(0) {} 71 | 72 | binary_stream_writer(binary_stream_writer&& rhs) noexcept 73 | : stream_base(rhs), 74 | buffer_(rhs.buffer_), 75 | total_write_(rhs.total_write_) { 76 | rhs.total_write_ = static_cast(-1); 77 | rhs.set_state(stream_state::invalid_stream); 78 | } 79 | 80 | binary_stream_writer& operator=(binary_stream_writer&&) = delete; 81 | binary_stream_writer& operator=(const binary_stream_writer&) = delete; 82 | binary_stream_writer(const binary_stream_writer&) = delete; 83 | 84 | /** 85 | * @brief Serialises an object that provides a serialise function: 86 | * void serialise(auto& stream); 87 | * 88 | * @param object The object to be serialised. 89 | */ 90 | void serialise(auto&& object) { 91 | stream_write_adaptor adaptor(*this); 92 | object.serialise(adaptor); 93 | } 94 | 95 | /** 96 | * @brief Serialises an object that provides a serialise function: 97 | * void serialise(auto& stream); 98 | * 99 | * @param data The object to be serialised. 100 | * 101 | * @return Reference to the current stream. 102 | */ 103 | template 104 | requires has_serialise 105 | binary_stream_writer& operator<<(T& data) { 106 | serialise(data); 107 | return *this; 108 | } 109 | 110 | /** 111 | * @brief Serialises an object that provides a serialise function: 112 | * auto& operator<<(auto& stream); 113 | * 114 | * @param data The object to be serialised. 115 | * 116 | * @return Reference to the current stream. 117 | */ 118 | binary_stream_writer& operator<<(has_shl_override auto&& data) { 119 | return *this << data; 120 | } 121 | 122 | /** 123 | * @brief Serialises an arithmetic type with the requested byte order. 124 | * 125 | * @param adaptor An endian adaptor. 126 | * 127 | * @return Reference to the current stream. 128 | */ 129 | template endian_func> 130 | binary_stream_writer& operator<<(endian_func adaptor) { 131 | const auto converted = adaptor.to(); 132 | write(&converted, sizeof(converted)); 133 | return *this; 134 | } 135 | 136 | /** 137 | * @brief Serialises a POD type. 138 | * 139 | * @note This overload will not be selected for types that provide a 140 | * user-defined serialise function. 141 | * 142 | * @param data The object to serialise. 143 | * 144 | * @return Reference to the current stream. 145 | */ 146 | template 147 | requires (!has_shl_override) 148 | binary_stream_writer& operator<<(const T& data) { 149 | write(&data, sizeof(data)); 150 | return *this; 151 | } 152 | 153 | template 154 | requires std::is_same_v, std::string_view> 155 | binary_stream_writer& operator<<(null_terminated adaptor) { 156 | assert(adaptor->find_first_of('\0') == adaptor->npos); 157 | write(adaptor->data(), adaptor->size()); 158 | const char terminator = '\0'; 159 | write(&terminator, 1); 160 | return *this; 161 | } 162 | 163 | /** 164 | * @brief Serialises a string_view as a null terminated string. 165 | * 166 | * @tparam T The string type. 167 | * @param adaptor null_terminated adaptor that will instruct the stream to write 168 | * a null terminated string with no prefix. 169 | * 170 | * @return Reference to the current stream. 171 | */ 172 | template 173 | requires std::is_same_v, std::string> 174 | binary_stream_writer& operator<<(null_terminated adaptor) { 175 | assert(adaptor->find_first_of('\0') == adaptor->npos); 176 | write(adaptor->data(), adaptor->size() + 1); // yes, the standard allows this 177 | return *this; 178 | } 179 | 180 | /** 181 | * @brief Serialises a string, string_view or any type providing data() 182 | * and a size() member functions. 183 | * 184 | * @tparam T The type. 185 | * @param adaptor raw adaptor referencing the type to be serialised. 186 | * 187 | * @return Reference to the current stream. 188 | */ 189 | template 190 | binary_stream_writer& operator<<(raw adaptor) { 191 | write(adaptor->data(), adaptor->size()); 192 | return *this; 193 | } 194 | 195 | /** 196 | * @brief Serialises an std::string_view with a fixed-length prefix. 197 | * 198 | * @param string std::string_view to be serialised with a prefix. 199 | * 200 | * @return Reference to the current stream. 201 | */ 202 | binary_stream_writer& operator<<(std::string_view string) { 203 | return *this << prefixed(string); 204 | } 205 | 206 | /** 207 | * @brief Serialises an std::string with a fixed-length prefix. 208 | * 209 | * @param string std::string to be serialised with a prefix. 210 | * 211 | * @return Reference to the current stream. 212 | */ 213 | binary_stream_writer& operator<<(const std::string& string) { 214 | return *this << prefixed(string); 215 | } 216 | 217 | /** 218 | * @brief Serialises a C-style string. 219 | * 220 | * @param data string to be serialised. 221 | * 222 | * @note The null terminator for this string will also be written. It will 223 | * not be prefixed. 224 | * 225 | * @return Reference to the current stream. 226 | */ 227 | binary_stream_writer& operator<<(const char* data) { 228 | assert(data); 229 | const auto len = std::strlen(data); 230 | write(data, len + 1); // include terminator 231 | return *this; 232 | } 233 | 234 | binary_stream_writer& operator<<(const is_iterable auto& data) { 235 | write_container(data); 236 | return *this; 237 | } 238 | 239 | /** 240 | * @brief Serialises an iterable container. 241 | * 242 | * @tparam T The iterable container type. 243 | * @param adaptor The container to be serialised. 244 | * 245 | * @note A fixed-length prefix representing the number of elements in the 246 | * container will be written before the elements. 247 | * 248 | * @return Reference to the current stream. 249 | */ 250 | template 251 | binary_stream_writer& operator<<(prefixed adaptor) { 252 | const auto count = endian::native_to_little(static_cast(adaptor->size())); 253 | write(&count, sizeof(count)); 254 | write_container(adaptor.str); 255 | return *this; 256 | } 257 | 258 | /** 259 | * @brief Serialises an iterable container. 260 | * 261 | * @tparam T The iterable container type. 262 | * @param adaptor The container to be serialised. 263 | * 264 | * @note A variable-length prefix representing the number of elements in the 265 | * container will be written before the elements. 266 | * 267 | * @return Reference to the current stream. 268 | */ 269 | template 270 | binary_stream_writer& operator<<(prefixed_varint adaptor) { 271 | varint_encode(*this, adaptor->size()); 272 | write_container(adaptor.str); 273 | return *this; 274 | } 275 | 276 | /** 277 | * @brief Writes a contiguous range to the stream. 278 | * 279 | * @param data The contiguous range to be written to the stream. 280 | */ 281 | template 282 | void put(range& data) { 283 | const auto write_size = data.size() * sizeof(typename range::value_type); 284 | write(data.data(), write_size); 285 | } 286 | 287 | /** 288 | * @brief Writes a the provided value to the stream. 289 | * 290 | * @param data The value to be written to the stream. 291 | */ 292 | void put(const arithmetic auto& data) { 293 | write(&data, sizeof(data)); 294 | } 295 | 296 | /** 297 | * @brief Writes data to the stream. 298 | * 299 | * @param data The element to be written to the stream. 300 | */ 301 | template endian_func> 302 | void put(const endian_func& adaptor) { 303 | const auto swapped = adaptor.to(); 304 | write(&swapped, sizeof(swapped)); 305 | } 306 | 307 | /** 308 | * @brief Writes bytes from the provided buffer to the stream. 309 | * 310 | * @tparam T POD type. 311 | * @param data Pointer to the buffer from which data will be copied to the stream. 312 | * @param count The number of elements to write. 313 | */ 314 | template 315 | void put(const T* data, std::size_t count) { 316 | assert(data); 317 | const auto write_size = count * sizeof(T); 318 | write(data, write_size); 319 | } 320 | 321 | /** 322 | * @brief Writes the data from the iterator range to the stream. 323 | * 324 | * @tparam It The iterator type. 325 | * @param begin Iterator to the beginning of the data. 326 | * @param end Iterator to the end of the data. 327 | */ 328 | template 329 | void put(It begin, const It end) { 330 | for(auto it = begin; it != end; ++it) { 331 | *this << *it; 332 | } 333 | } 334 | 335 | /** 336 | * @brief Allows for writing a provided byte value a specified number of times to 337 | * the stream. 338 | * 339 | * @tparam size The number of bytes to generate. 340 | * @param value The byte value that will fill the specified number of bytes. 341 | */ 342 | template 343 | void fill(const std::uint8_t value) { 344 | const auto filled = generate_filled(value); 345 | write(filled.data(), filled.size()); 346 | } 347 | 348 | /** Misc functions **/ 349 | 350 | /** 351 | * @brief Determines whether this container can write seek. 352 | * 353 | * @return Whether this container is capable of write seeking. 354 | */ 355 | bool can_write_seek() const { 356 | return buffer_.can_write_seek(); 357 | } 358 | 359 | /** 360 | * @brief Performs write seeking within the container. 361 | * 362 | * @param direction Specify whether to seek in a given direction or to absolute seek. 363 | * @param offset The offset relative to the seek direction or the absolute value 364 | * when using absolute seeking. 365 | */ 366 | void write_seek(const stream_seek direction, const std::size_t offset) { 367 | if(direction == stream_seek::sk_stream_absolute) { 368 | if(offset >= total_write_) { 369 | buffer_.write_seek(buffer_seek::sk_forward, offset - total_write_); 370 | } else { 371 | buffer_.write_seek(buffer_seek::sk_backward, total_write_ - offset); 372 | } 373 | 374 | total_write_ = offset; 375 | } else { 376 | buffer_.write_seek(static_cast(direction), offset); 377 | } 378 | } 379 | 380 | /** 381 | * @brief Returns the size of the container. 382 | * 383 | * @return The number of bytes of data available to read within the stream. 384 | */ 385 | std::size_t size() const { 386 | return buffer_.size(); 387 | } 388 | 389 | /** 390 | * @brief Whether the container is empty. 391 | * 392 | * @return Returns true if the container is empty (has no data to be read). 393 | */ 394 | [[nodiscard]] 395 | bool empty() const { 396 | return buffer_.empty(); 397 | } 398 | 399 | /** 400 | * @return The total number of bytes written to the stream. 401 | */ 402 | std::size_t total_write() const { 403 | return total_write_; 404 | } 405 | 406 | /** 407 | * @brief Get a pointer to the buffer write interface. 408 | * 409 | * @return Pointer to the buffer write interface. 410 | */ 411 | buffer_write* buffer() { 412 | return &buffer_; 413 | } 414 | 415 | /** 416 | * @brief Get a pointer to the buffer write interface. 417 | * 418 | * @return Pointer to the buffer write interface. 419 | */ 420 | const buffer_write* buffer() const { 421 | return &buffer_; 422 | } 423 | }; 424 | 425 | } // pmc, hexi 426 | -------------------------------------------------------------------------------- /include/hexi/pmc/buffer.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | namespace hexi::pmc { 13 | 14 | class buffer : public buffer_read, public buffer_write { 15 | public: 16 | using value_type = std::byte; 17 | 18 | using buffer_read::operator[]; 19 | 20 | virtual std::byte& operator[](const std::size_t index) = 0; 21 | virtual ~buffer() = default; 22 | }; 23 | 24 | } // pmc, hexi 25 | -------------------------------------------------------------------------------- /include/hexi/pmc/buffer_adaptor.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace hexi::pmc { 16 | 17 | template 18 | requires std::ranges::contiguous_range 19 | class buffer_adaptor final : public buffer_read_adaptor, 20 | public buffer_write_adaptor, 21 | public buffer { 22 | void conditional_clear() { 23 | if(buffer_read_adaptor::read_ptr() == buffer_write_adaptor::write_ptr()) { 24 | clear(); 25 | } 26 | } 27 | 28 | public: 29 | explicit buffer_adaptor(buf_type& buffer) 30 | : buffer_read_adaptor(buffer), 31 | buffer_write_adaptor(buffer) {} 32 | 33 | explicit buffer_adaptor(buf_type& buffer, init_empty_t) 34 | : buffer_read_adaptor(buffer, init_empty), 35 | buffer_write_adaptor(buffer, init_empty) {} 36 | 37 | /** 38 | * @brief Reads a number of bytes to the provided buffer. 39 | * 40 | * @param destination The buffer to copy the data to. 41 | */ 42 | template 43 | void read(T* destination) { 44 | buffer_read_adaptor::read(destination); 45 | 46 | if constexpr(allow_optimise) { 47 | conditional_clear(); 48 | } 49 | } 50 | 51 | /** 52 | * @brief Reads a number of bytes to the provided buffer. 53 | * 54 | * @param destination The buffer to copy the data to. 55 | * @param length The number of bytes to read into the buffer. 56 | */ 57 | void read(void* destination, std::size_t length) override { 58 | buffer_read_adaptor::read(destination, length); 59 | 60 | if constexpr(allow_optimise) { 61 | conditional_clear(); 62 | } 63 | }; 64 | 65 | /** 66 | * @brief Write data to the container. 67 | * 68 | * @param source Pointer to the data to be written. 69 | */ 70 | void write(const auto& source) { 71 | buffer_write_adaptor::write(source); 72 | }; 73 | 74 | /** 75 | * @brief Write provided data to the container. 76 | * 77 | * @param source Pointer to the data to be written. 78 | * @param length Number of bytes to write from the source. 79 | */ 80 | void write(const void* source, std::size_t length) override { 81 | buffer_write_adaptor::write(source, length); 82 | }; 83 | 84 | void copy(auto* destination) const { 85 | buffer_read_adaptor::copy(destination); 86 | }; 87 | 88 | /** 89 | * @brief Copies a number of bytes to the provided buffer but without advancing 90 | * the read cursor. 91 | * 92 | * @param destination The buffer to copy the data to. 93 | * @param length The number of bytes to copy. 94 | */ 95 | void copy(void* destination, std::size_t length) const override { 96 | buffer_read_adaptor::copy(destination, length); 97 | }; 98 | 99 | /** 100 | * @brief Skip over a number of bytes. 101 | * 102 | * Skips over a number of bytes from the container. This should be used 103 | * if the container holds data that you don't care about but don't want 104 | * to have to read it to another buffer to access data beyond it. 105 | * 106 | * @param length The number of bytes to skip. 107 | */ 108 | void skip(std::size_t length) override { 109 | buffer_read_adaptor::skip(length); 110 | 111 | if constexpr(allow_optimise) { 112 | conditional_clear(); 113 | } 114 | }; 115 | 116 | /** 117 | * @brief Retrieves a reference to the specified index within the container. 118 | * 119 | * @param index The index within the container. 120 | * 121 | * @return A reference to the value at the specified index. 122 | */ 123 | const std::byte& operator[](const std::size_t index) const override { 124 | return buffer_read_adaptor::operator[](index); 125 | }; 126 | 127 | /** 128 | * @brief Retrieves a reference to the specified index within the container. 129 | * 130 | * @param index The index within the container. 131 | * 132 | * @return A reference to the value at the specified index. 133 | */ 134 | std::byte& operator[](const std::size_t index) override { 135 | const auto offset = buffer_read_adaptor::read_offset(); 136 | auto buffer = buffer_write_adaptor::storage(); 137 | return reinterpret_cast(buffer + offset)[index]; 138 | } 139 | 140 | /** 141 | * @brief Reserves a number of bytes within the container for future use. 142 | * 143 | * @param length The number of bytes that the container should reserve. 144 | */ 145 | void reserve(std::size_t length) override { 146 | buffer_write_adaptor::reserve(length); 147 | }; 148 | 149 | /** 150 | * @brief Determines whether this container can write seek. 151 | * 152 | * @return Whether this container is capable of write seeking. 153 | */ 154 | bool can_write_seek() const override { 155 | return buffer_write_adaptor::can_write_seek(); 156 | }; 157 | 158 | /** 159 | * @brief Performs write seeking within the container. 160 | * 161 | * @param direction Specify whether to seek in a given direction or to absolute seek. 162 | * @param offset The offset relative to the seek direction or the absolute value 163 | * when using absolute seeking. 164 | */ 165 | void write_seek(buffer_seek direction, std::size_t offset) override { 166 | buffer_write_adaptor::write_seek(direction, offset); 167 | }; 168 | 169 | /** 170 | * @brief Returns the size of the container. 171 | * 172 | * @return The number of bytes of data available to read within the stream. 173 | */ 174 | std::size_t size() const override { 175 | return buffer_read_adaptor::size(); 176 | }; 177 | 178 | /** 179 | * @brief Whether the container is empty. 180 | * 181 | * @return Returns true if the container is empty (has no data to be read). 182 | */ 183 | [[nodiscard]] 184 | bool empty() const override { 185 | return buffer_read_adaptor::read_offset() 186 | == buffer_write_adaptor::write_offset(); 187 | } 188 | 189 | /** 190 | * @brief Attempts to locate the provided value within the container. 191 | * 192 | * @param value The value to locate. 193 | * 194 | * @return The position of value or npos if not found. 195 | */ 196 | std::size_t find_first_of(std::byte val) const override { 197 | return buffer_read_adaptor::find_first_of(val); 198 | } 199 | 200 | /** 201 | * @brief Clears the underlying buffer, resetting both the read 202 | * and write cursors. 203 | */ 204 | void clear() { 205 | buffer_read_adaptor::clear(); 206 | buffer_write_adaptor::clear(); 207 | } 208 | }; 209 | 210 | } // pmc, hexi 211 | -------------------------------------------------------------------------------- /include/hexi/pmc/buffer_base.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | namespace hexi::pmc { 12 | 13 | class buffer_base { 14 | protected: 15 | std::size_t read_ = 0; 16 | std::size_t write_ = 0; 17 | 18 | public: 19 | virtual std::size_t size() const = 0; 20 | virtual bool empty() const = 0; 21 | virtual ~buffer_base() = default; 22 | }; 23 | 24 | } // pmc, hexi 25 | -------------------------------------------------------------------------------- /include/hexi/pmc/buffer_read.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | namespace hexi::pmc { 13 | 14 | class buffer_read : virtual public buffer_base { 15 | public: 16 | using value_type = std::byte; 17 | 18 | static constexpr auto npos { static_cast(-1) }; 19 | 20 | virtual ~buffer_read() = default; 21 | virtual void read(void* destination, std::size_t length) = 0; 22 | virtual void copy(void* destination, std::size_t length) const = 0; 23 | virtual void skip(std::size_t length) = 0; 24 | virtual const std::byte& operator[](const std::size_t index) const = 0; 25 | virtual std::size_t find_first_of(std::byte val) const = 0; 26 | }; 27 | 28 | } // pmc, hexi 29 | -------------------------------------------------------------------------------- /include/hexi/pmc/buffer_read_adaptor.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace hexi::pmc { 20 | 21 | using namespace detail; 22 | 23 | template 24 | requires std::ranges::contiguous_range 25 | class buffer_read_adaptor : public buffer_read { 26 | buf_type& buffer_; 27 | 28 | public: 29 | buffer_read_adaptor(buf_type& buffer) 30 | : buffer_(buffer) { 31 | write_ = buffer_.size(); 32 | } 33 | 34 | buffer_read_adaptor(buf_type& buffer, init_empty_t) 35 | : buffer_(buffer) {} 36 | 37 | /** 38 | * @brief Reads a number of bytes to the provided buffer. 39 | * 40 | * @note The destination buffer must not overlap with the underlying buffer 41 | * being used by the buffer_adaptor. 42 | * 43 | * @tparam T The destination type. 44 | * @param[out] destination The buffer to copy the data to. 45 | */ 46 | template 47 | void read(T* destination) { 48 | read(destination, sizeof(T)); 49 | } 50 | 51 | /** 52 | * @brief Reads a number of bytes to the provided buffer. 53 | * 54 | * @note The destination buffer must not overlap with the underlying buffer 55 | * being used by the buffer_adaptor. 56 | * 57 | * @param destination The buffer to copy the data to. 58 | * @param length The number of bytes to read into the buffer. 59 | */ 60 | void read(void* destination, std::size_t length) override { 61 | assert(destination && !region_overlap(buffer_.data(), buffer_.size(), destination, length)); 62 | std::memcpy(destination, buffer_.data() + read_, length); 63 | read_ += length; 64 | } 65 | 66 | /** 67 | * @brief Copies a number of bytes to the provided buffer but without advancing 68 | * the read cursor. 69 | * 70 | * @param[out] destination The buffer to copy the data to. 71 | */ 72 | template 73 | void copy(T* destination) const { 74 | copy(destination, sizeof(T)); 75 | } 76 | 77 | /** 78 | * @brief Copies a number of bytes to the provided buffer but without advancing 79 | * the read cursor. 80 | * 81 | * @note The destination buffer must not overlap with the underlying buffer 82 | * being used by the buffer_adaptor. 83 | * 84 | * @param[out] destination The buffer to copy the data to. 85 | * @param length The number of bytes to copy. 86 | */ 87 | void copy(void* destination, std::size_t length) const override { 88 | assert(destination && !region_overlap(buffer_.data(), buffer_.size(), destination, length)); 89 | std::memcpy(destination, buffer_.data() + read_, length); 90 | } 91 | 92 | /** 93 | * @brief Skip over a number of bytes. 94 | * 95 | * Skips over a number of bytes from the container. This should be used 96 | * if the container holds data that you don't care about but don't want 97 | * to have to read it to another buffer to access data beyond it. 98 | * 99 | * @param length The number of bytes to skip. 100 | */ 101 | void skip(std::size_t length) override { 102 | read_ += length; 103 | } 104 | 105 | /** 106 | * @brief Returns the size of the container. 107 | * 108 | * @note The value returned may be higher than the total number of bytes 109 | * that can be read from this stream, if a read limit was set during 110 | * construction. Use read_max() to determine how many bytes can be 111 | * read from this stream. 112 | * 113 | * @return The number of bytes of data available to read within the stream. 114 | */ 115 | std::size_t size() const override { 116 | return write_ - read_; 117 | } 118 | 119 | /** 120 | * @brief Whether the container is empty. 121 | * 122 | * @return Returns true if the container is empty (has no data to be read). 123 | */ 124 | [[nodiscard]] 125 | bool empty() const override { 126 | return read_ == write_; 127 | } 128 | 129 | /** 130 | * @brief Retrieves a reference to the specified index within the container. 131 | * 132 | * @param index The index within the container. 133 | * 134 | * @return A reference to the value at the specified index. 135 | */ 136 | const std::byte& operator[](const std::size_t index) const override { 137 | return reinterpret_cast(buffer_.data() + read_)[index]; 138 | } 139 | 140 | /** 141 | * @return Pointer to the data available for reading. 142 | */ 143 | auto read_ptr() const { 144 | return buffer_.data() + read_; 145 | } 146 | 147 | /** 148 | * @return The current read offset. 149 | */ 150 | auto read_offset() const { 151 | return read_; 152 | } 153 | 154 | /** 155 | * @brief Attempts to locate the provided value within the container. 156 | * 157 | * @param value The value to locate. 158 | * 159 | * @return The position of value or npos if not found. 160 | */ 161 | std::size_t find_first_of(std::byte val) const override { 162 | const auto data = read_ptr(); 163 | 164 | for(std::size_t i = 0, j = size(); i < j; ++i) { 165 | if(static_cast(data[i]) == val) { 166 | return i; 167 | } 168 | } 169 | 170 | return npos; 171 | } 172 | 173 | /* 174 | * @brief Resets both the read and write cursors back to the beginning 175 | * of the buffer. 176 | * 177 | * @note The underlying buffer will not be cleared but should be treated 178 | * as thought it has been. 179 | */ 180 | void clear() { 181 | read_ = 0; 182 | 183 | if constexpr(has_clear) { 184 | buffer_.clear(); 185 | } 186 | } 187 | }; 188 | 189 | } // pmc, hexi 190 | -------------------------------------------------------------------------------- /include/hexi/pmc/buffer_write.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace hexi::pmc { 14 | 15 | class buffer_write : virtual public buffer_base { 16 | public: 17 | using value_type = std::byte; 18 | 19 | virtual ~buffer_write() = default; 20 | virtual void write(const void* source, std::size_t length) = 0; 21 | virtual void reserve(std::size_t length) = 0; 22 | virtual bool can_write_seek() const = 0; 23 | virtual void write_seek(buffer_seek direction, std::size_t offset) = 0; 24 | }; 25 | 26 | } // pmc, hexi 27 | -------------------------------------------------------------------------------- /include/hexi/pmc/buffer_write_adaptor.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | namespace hexi::pmc { 19 | 20 | using namespace detail; 21 | 22 | template 23 | requires std::ranges::contiguous_range 24 | class buffer_write_adaptor : public buffer_write { 25 | buf_type& buffer_; 26 | 27 | public: 28 | buffer_write_adaptor(buf_type& buffer) 29 | : buffer_(buffer) { 30 | write_ = buffer.size(); 31 | } 32 | 33 | buffer_write_adaptor(buf_type& buffer, init_empty_t) 34 | : buffer_(buffer) {} 35 | 36 | /** 37 | * @brief Write data to the container. 38 | * 39 | * @note The source buffer must not overlap with the underlying buffer 40 | * being used by the buffer_adaptor. 41 | * 42 | * @param source Pointer to the data to be written. 43 | */ 44 | void write(auto& source) { 45 | write(&source, sizeof(source)); 46 | } 47 | 48 | /** 49 | * @brief Write provided data to the container. 50 | * 51 | * @note The source buffer must not overlap with the underlying buffer 52 | * being used by the buffer_adaptor. 53 | * 54 | * @param source Pointer to the data to be written. 55 | * @param length Number of bytes to write from the source. 56 | */ 57 | void write(const void* source, std::size_t length) override { 58 | assert(source && !region_overlap(source, length, buffer_.data(), buffer_.size())); 59 | const auto min_req_size = write_ + length; 60 | 61 | if(buffer_.size() < min_req_size) [[likely]] { 62 | if constexpr(has_resize_overwrite) { 63 | buffer_.resize_and_overwrite(min_req_size, [](char*, std::size_t size) { 64 | return size; 65 | }); 66 | } else if constexpr(has_resize) { 67 | buffer_.resize(min_req_size); 68 | } else { 69 | HEXI_THROW(buffer_overflow(free(), length, write_)); 70 | } 71 | } 72 | 73 | std::memcpy(buffer_.data() + write_, source, length); 74 | write_ += length; 75 | } 76 | 77 | /** 78 | * @brief Reserves a number of bytes within the container for future use. 79 | * 80 | * @note This is a non-binding request, meaning the buffer may not reserve 81 | * any additional space, such as in the case where it is not supported. 82 | * 83 | * @param length The number of bytes that the container should reserve. 84 | */ 85 | void reserve(const std::size_t length) override { 86 | if constexpr(has_reserve) { 87 | buffer_.reserve(length); 88 | } 89 | } 90 | 91 | /** 92 | * @brief Determines whether this container can write seek. 93 | * 94 | * @return Whether this container is capable of write seeking. 95 | */ 96 | bool can_write_seek() const override { 97 | return true; 98 | } 99 | 100 | /** 101 | * @brief Performs write seeking within the container. 102 | * 103 | * @param direction Specify whether to seek in a given direction or to absolute seek. 104 | * @param offset The offset relative to the seek direction or the absolute value 105 | * when using absolute seeking. 106 | */ 107 | void write_seek(const buffer_seek direction, const std::size_t offset) override { 108 | switch(direction) { 109 | case buffer_seek::sk_backward: 110 | write_ -= offset; 111 | break; 112 | case buffer_seek::sk_forward: 113 | write_ += offset; 114 | break; 115 | case buffer_seek::sk_absolute: 116 | write_ = offset; 117 | } 118 | } 119 | 120 | /** 121 | * @return Pointer to the underlying storage. 122 | */ 123 | auto storage() const { 124 | return buffer_.data(); 125 | } 126 | 127 | /** 128 | * @return Pointer to the underlying storage. 129 | */ 130 | auto storage() { 131 | return buffer_.data(); 132 | } 133 | 134 | /** 135 | * @return Pointer to the location within the buffer where the next write 136 | * will be made. 137 | */ 138 | auto write_ptr() { 139 | return buffer_.data() + write_; 140 | } 141 | 142 | /** 143 | * @return Pointer to the location within the buffer where the next write 144 | * will be made. 145 | */ 146 | auto write_ptr() const { 147 | return buffer_.data() + write_; 148 | } 149 | 150 | /** 151 | * @return The current write offset. 152 | */ 153 | auto write_offset() const { 154 | return write_; 155 | } 156 | 157 | /** 158 | * @brief Clear the underlying buffer and reset state. 159 | */ 160 | void clear() { 161 | write_ = 0; 162 | 163 | if constexpr(has_clear) { 164 | buffer_.clear(); 165 | } 166 | } 167 | 168 | /** 169 | * @brief Advances the write cursor. 170 | * 171 | * @param size The number of bytes by which to advance the write cursor. 172 | */ 173 | void advance_write(std::size_t bytes) { 174 | assert(buffer_.size() >= (write_ + bytes)); 175 | write_ += bytes; 176 | } 177 | 178 | /** 179 | * @brief The amount of free space. 180 | * 181 | * @return The number of bytes of free space within the container. 182 | */ 183 | std::size_t free() const { 184 | return buffer_.size() - write_; 185 | } 186 | }; 187 | 188 | } // pmc, hexi 189 | -------------------------------------------------------------------------------- /include/hexi/pmc/stream_base.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | namespace hexi::pmc { 13 | 14 | class stream_base { 15 | buffer_base& buffer_; 16 | stream_state state_; 17 | bool allow_throw_; 18 | 19 | protected: 20 | void set_state(stream_state state) { 21 | state_ = state; 22 | } 23 | 24 | bool allow_throw() const { 25 | return allow_throw_; 26 | } 27 | 28 | public: 29 | explicit stream_base(buffer_base& buffer) 30 | : buffer_(buffer), 31 | state_(stream_state::ok), 32 | allow_throw_(true) { } 33 | 34 | explicit stream_base(buffer_base& buffer, bool allow_throw) 35 | : buffer_(buffer), 36 | state_(stream_state::ok), 37 | allow_throw_(allow_throw) { } 38 | 39 | std::size_t size() const { 40 | return buffer_.size(); 41 | } 42 | 43 | [[nodiscard]] 44 | bool empty() const { 45 | return buffer_.empty(); 46 | } 47 | 48 | stream_state state() const { 49 | return state_; 50 | } 51 | 52 | bool good() const { 53 | return state() == stream_state::ok; 54 | } 55 | 56 | void clear_state() { 57 | set_state(stream_state::ok); 58 | } 59 | 60 | operator bool() const { 61 | return good(); 62 | } 63 | 64 | void set_error_state() { 65 | set_state(stream_state::user_defined_err); 66 | } 67 | 68 | virtual ~stream_base() = default; 69 | }; 70 | 71 | } // pmc, hexi 72 | -------------------------------------------------------------------------------- /include/hexi/shared.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | namespace hexi { 18 | 19 | #if defined(_EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) 20 | #define HEXI_TRY try 21 | #define HEXI_CATCH(exception) catch(exception) 22 | #define HEXI_THROW(...) throw __VA_ARGS__ 23 | #define HEXI_EXCEPTION_TAG allow_throw_t 24 | #else 25 | #include 26 | #define HEXI_TRY if(true) 27 | #define HEXI_CATCH(exception) if(false) 28 | #define HEXI_THROW(...) std::abort() 29 | #define HEXI_EXCEPTION_TAG no_throw_t 30 | #endif 31 | 32 | struct is_contiguous {}; 33 | struct is_non_contiguous {}; 34 | struct supported {}; 35 | struct unsupported {}; 36 | struct except_tag {}; 37 | struct allow_throw_t : except_tag {}; 38 | struct no_throw_t : except_tag {}; 39 | 40 | [[maybe_unused]] constexpr static no_throw_t no_throw {}; 41 | [[maybe_unused]] constexpr static allow_throw_t allow_throw {}; 42 | 43 | struct init_empty_t {}; 44 | constexpr static init_empty_t init_empty {}; 45 | 46 | #define STRING_ADAPTOR(adaptor_name) \ 47 | template \ 48 | struct adaptor_name { \ 49 | string_type& str; \ 50 | string_type* operator->() { return &str; } \ 51 | }; \ 52 | /* deduction guide required for clang 17 support */ \ 53 | template \ 54 | adaptor_name(string_type&) -> adaptor_name; \ 55 | 56 | STRING_ADAPTOR(raw) 57 | STRING_ADAPTOR(prefixed) 58 | STRING_ADAPTOR(prefixed_varint) 59 | STRING_ADAPTOR(null_terminated) 60 | 61 | enum class buffer_seek { 62 | sk_absolute, sk_backward, sk_forward 63 | }; 64 | 65 | enum class stream_seek { 66 | // Seeks within the entire underlying buffer 67 | sk_buffer_absolute, 68 | sk_backward, 69 | sk_forward, 70 | // Seeks only within the range written by the current stream 71 | sk_stream_absolute 72 | }; 73 | 74 | enum class stream_state { 75 | ok, 76 | read_limit_err, 77 | buff_limit_err, 78 | buff_write_err, 79 | invalid_stream, 80 | user_defined_err 81 | }; 82 | 83 | namespace detail { 84 | 85 | template 86 | constexpr auto varint_decode(stream_type& stream) -> size_type { 87 | int shift { 0 }; 88 | size_type value { 0 }; 89 | std::uint8_t byte { 0 }; 90 | 91 | do { 92 | byte = 0; // clear in case an error occurs 93 | stream.get(&byte, 1); 94 | value |= (static_cast(byte & 0x7f) << shift); 95 | shift += 7; 96 | } while(byte & 0x80); 97 | 98 | return value; 99 | } 100 | 101 | template 102 | constexpr auto varint_encode(stream_type& stream, size_type value) -> size_type { 103 | size_type written = 0; 104 | 105 | while(value > 0x7f) { 106 | const std::uint8_t byte = (value & 0x7f) | 0x80; 107 | stream.put(&byte, 1); 108 | value >>= 7; 109 | ++written; 110 | } 111 | 112 | const std::uint8_t byte = value & 0x7f; 113 | stream.put(&byte, 1); 114 | return ++written; 115 | } 116 | 117 | template 118 | static constexpr auto generate_filled(const std::uint8_t value) { 119 | std::array target{}; 120 | std::ranges::fill(target, value); 121 | return target; 122 | } 123 | 124 | // Returns true if there's any overlap between source and destination ranges 125 | [[maybe_unused]] 126 | static inline bool region_overlap(const void* src, std::size_t src_len, 127 | const void* dst, std::size_t dst_len) { 128 | const auto src_beg = std::bit_cast(src); 129 | const auto src_end = src_beg + src_len; 130 | const auto dst_beg = std::bit_cast(dst); 131 | const auto dst_end = dst_beg + dst_len; 132 | return src_beg < dst_end && dst_beg < src_end; 133 | } 134 | 135 | } // detail 136 | 137 | } // hexi 138 | -------------------------------------------------------------------------------- /include/hexi/static_buffer.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | namespace hexi { 20 | 21 | using namespace detail; 22 | 23 | template 24 | class static_buffer final { 25 | std::array buffer_ = {}; 26 | std::size_t read_ = 0; 27 | std::size_t write_ = 0; 28 | 29 | public: 30 | using size_type = typename decltype(buffer_)::size_type; 31 | using offset_type = size_type; 32 | using value_type = storage_type; 33 | using contiguous = is_contiguous; 34 | using seeking = supported; 35 | 36 | static constexpr auto npos { static_cast(-1) }; 37 | 38 | static_buffer() = default; 39 | 40 | template 41 | static_buffer(T&&... vals) : buffer_{ std::forward(vals)... } { 42 | write_ = sizeof... (vals); 43 | } 44 | 45 | static_buffer(static_buffer&& rhs) = default; 46 | static_buffer& operator=(static_buffer&&) = default; 47 | static_buffer& operator=(const static_buffer&) = default; 48 | static_buffer(const static_buffer&) = default; 49 | 50 | /** 51 | * @brief Reads a number of bytes to the provided buffer. 52 | * 53 | * @param destination The buffer to copy the data to. 54 | */ 55 | template 56 | void read(T* destination) { 57 | read(destination, sizeof(T)); 58 | } 59 | 60 | /** 61 | * @brief Reads a number of bytes to the provided buffer. 62 | * 63 | * @param destination The buffer to copy the data to. 64 | * @param length The number of bytes to read into the buffer. 65 | */ 66 | void read(void* destination, size_type length) { 67 | copy(destination, length); 68 | read_ += length; 69 | 70 | if(read_ == write_) { 71 | read_ = write_ = 0; 72 | } 73 | } 74 | 75 | /** 76 | * @brief Copies a number of bytes to the provided buffer but without advancing 77 | * the read cursor. 78 | * 79 | * @param destination The buffer to copy the data to. 80 | */ 81 | template 82 | void copy(T* destination) const { 83 | copy(destination, sizeof(T)); 84 | } 85 | 86 | /** 87 | * @brief Copies a number of bytes to the provided buffer but without advancing 88 | * the read cursor. 89 | * 90 | * @note The destination buffer address must not belong to the static_buffer. 91 | * 92 | * @param destination The buffer to copy the data to. 93 | * @param length The number of bytes to copy. 94 | */ 95 | void copy(void* destination, size_type length) const { 96 | assert(!region_overlap(buffer_.data(), buffer_.size(), destination, length)); 97 | 98 | if(length > size()) { 99 | HEXI_THROW(buffer_underrun(length, read_, size())); 100 | } 101 | 102 | std::memcpy(destination, read_ptr(), length); 103 | } 104 | 105 | /** 106 | * @brief Attempts to locate the provided value within the container. 107 | * 108 | * @param value The value to locate. 109 | * 110 | * @return The position of value or npos if not found. 111 | */ 112 | size_type find_first_of(value_type val) const noexcept { 113 | const auto data = read_ptr(); 114 | 115 | for(size_type i = 0, j = size(); i < j; ++i) { 116 | if(data[i] == val) { 117 | return i; 118 | } 119 | } 120 | 121 | return npos; 122 | } 123 | 124 | /** 125 | * @brief Skip over a number of bytes. 126 | * 127 | * Skips over a number of bytes from the container. This should be used 128 | * if the container holds data that you don't care about but don't want 129 | * to have to read it to another buffer to access data beyond it. 130 | * 131 | * @param length The number of bytes to skip. 132 | */ 133 | void skip(const size_type length) { 134 | read_ += length; 135 | 136 | if(read_ == write_) { 137 | read_ = write_ = 0; 138 | } 139 | } 140 | 141 | /** 142 | * @brief Advances the write cursor. 143 | * 144 | * @param size The number of bytes by which to advance the write cursor. 145 | */ 146 | void advance_write(size_type bytes) { 147 | assert(free() >= bytes); 148 | write_ += bytes; 149 | } 150 | 151 | /** 152 | * @brief Resizes the buffer. 153 | * 154 | * @param size The new size of the buffer. 155 | * 156 | */ 157 | void resize(size_type size) { 158 | if(size > buffer_.size()) { 159 | HEXI_THROW(exception("attempted to resize static_buffer to larger than capacity")); 160 | } 161 | 162 | write_ = size; 163 | } 164 | 165 | /** 166 | * @brief Clears the container. 167 | */ 168 | void clear() { 169 | read_ = write_ = 0; 170 | } 171 | 172 | /** 173 | * @brief Moves any unread data to the front of the buffer, freeing space at the end. 174 | * If a move is performed, pointers obtained from read/write_ptr() will be invalidated. 175 | * 176 | * If the buffer contains no data available for reading, this function will have no effect. 177 | * 178 | * @return True if additional space was made available. 179 | */ 180 | bool defragment() { 181 | if(read_ == 0) { 182 | return false; 183 | } 184 | 185 | write_ = size(); 186 | std::memmove(buffer_.data(), read_ptr(), write_); 187 | read_ = 0; 188 | return true; 189 | } 190 | 191 | /** 192 | * @brief Retrieves a reference to the specified index within the container. 193 | * 194 | * @param index The index within the container. 195 | * 196 | * @return A reference to the value at the specified index. 197 | */ 198 | value_type& operator[](const size_type index) { 199 | return read_ptr()[index]; 200 | } 201 | 202 | /** 203 | * @brief Retrieves a reference to the specified index within the container. 204 | * 205 | * @param index The index within the container. 206 | * 207 | * @return A reference to the value at the specified index. 208 | */ 209 | const value_type& operator[](const size_type index) const { 210 | return read_ptr()[index]; 211 | } 212 | 213 | /** 214 | * @brief Whether the container is empty. 215 | * 216 | * @return Returns true if the container is empty (has no data to be read). 217 | */ 218 | [[nodiscard]] 219 | bool empty() const { 220 | return write_ == read_; 221 | } 222 | 223 | /** 224 | * @return Whether the container is full and cannot be further written to. 225 | */ 226 | bool full() const { 227 | return write_ == capacity(); 228 | } 229 | 230 | /** 231 | * @brief Determine whether the adaptor supports write seeking. 232 | * 233 | * This is determined at compile-time and does not need to checked at 234 | * run-time. 235 | * 236 | * @return True if write seeking is supported, otherwise false. 237 | */ 238 | constexpr static bool can_write_seek() { 239 | return std::is_same_v; 240 | } 241 | 242 | /** 243 | * @brief Write data to the container. 244 | * 245 | * @param source Pointer to the data to be written. 246 | */ 247 | void write(const auto& source) { 248 | write(&source, sizeof(source)); 249 | } 250 | 251 | /** 252 | * @brief Write provided data to the container. 253 | * 254 | * @note The source buffer address must not belong to the static_buffer. 255 | * 256 | * @param source Pointer to the data to be written. 257 | * @param length Number of bytes to write from the source. 258 | */ 259 | void write(const void* source, size_type length) { 260 | assert(!region_overlap(source, length, buffer_.data(), buffer_.size())); 261 | 262 | if(free() < length) { 263 | HEXI_THROW(buffer_overflow(length, write_, free())); 264 | } 265 | 266 | std::memcpy(write_ptr(), source, length); 267 | write_ += length; 268 | } 269 | 270 | /** 271 | * @brief Performs write seeking within the container. 272 | * 273 | * @param direction Specify whether to seek in a given direction or to absolute seek. 274 | * @param offset The offset relative to the seek direction or the absolute value 275 | * when using absolute seeking. 276 | */ 277 | void write_seek(const buffer_seek direction, const size_type offset) { 278 | switch(direction) { 279 | case buffer_seek::sk_backward: 280 | write_ -= offset; 281 | break; 282 | case buffer_seek::sk_forward: 283 | write_ += offset; 284 | break; 285 | case buffer_seek::sk_absolute: 286 | write_ = offset; 287 | } 288 | } 289 | 290 | /** 291 | * @return An iterator to the beginning of data available for reading. 292 | */ 293 | auto begin() { 294 | return buffer_.begin() + read_; 295 | } 296 | 297 | /** 298 | * @return An iterator to the beginning of data available for reading. 299 | */ 300 | auto begin() const { 301 | return buffer_.begin() + read_; 302 | } 303 | 304 | /** 305 | * @return An iterator to the end of data available for reading. 306 | */ 307 | auto end() { 308 | return buffer_.begin() + write_; 309 | } 310 | 311 | /** 312 | * @return An iterator to the end of data available for reading. 313 | */ 314 | auto end() const { 315 | return buffer_.begin() + write_; 316 | } 317 | 318 | /** 319 | * @brief Overall capacity of the container. 320 | * 321 | * @return The container's total size in bytes. 322 | */ 323 | constexpr static size_type capacity() { 324 | return buf_size; 325 | } 326 | 327 | /** 328 | * @brief Returns the size of the container. 329 | * 330 | * @return The number of bytes of data available to read within the container. 331 | */ 332 | size_type size() const { 333 | return write_ - read_; 334 | } 335 | 336 | /** 337 | * @brief The amount of free space. 338 | * 339 | * @return The number of bytes of free space within the container. 340 | */ 341 | size_type free() const { 342 | return buf_size - write_; 343 | } 344 | 345 | /** 346 | * @return Pointer to the data available for reading. 347 | */ 348 | const value_type* data() const { 349 | return read_ptr(); 350 | } 351 | 352 | /** 353 | * @return Pointer to the data available for reading. 354 | */ 355 | value_type* data() { 356 | return read_ptr(); 357 | } 358 | 359 | /** 360 | * @return Pointer to the data available for reading. 361 | */ 362 | const value_type* read_ptr() const { 363 | return buffer_.data() + read_; 364 | } 365 | 366 | /** 367 | * @return Pointer to the data available for reading. 368 | */ 369 | value_type* read_ptr() { 370 | return buffer_.data() + read_; 371 | } 372 | 373 | /** 374 | * @return Pointer to the location within the buffer where the next write 375 | * will be made. 376 | */ 377 | const value_type* write_ptr() const { 378 | return buffer_.data() + write_; 379 | } 380 | 381 | /** 382 | * @return Pointer to the location within the buffer where the next write 383 | * will be made. 384 | */ 385 | value_type* write_ptr() { 386 | return buffer_.data() + write_; 387 | } 388 | 389 | /** 390 | * @return Pointer to the underlying storage. 391 | */ 392 | value_type* storage() { 393 | return buffer_.data(); 394 | } 395 | 396 | /** 397 | * @return Pointer to the underlying storage. 398 | */ 399 | const value_type* storage() const { 400 | return buffer_.data(); 401 | } 402 | 403 | /** 404 | * @brief Retrieves a span representing the data available for reading contained within 405 | * underlying storage. 406 | * 407 | * @return A span over the data waiting to be read from the container. 408 | */ 409 | std::span read_span() const { 410 | return { read_ptr(), size() }; 411 | } 412 | 413 | /** 414 | * @brief Retrieves a span representing the free space within the underlying storage. 415 | * 416 | * @return A span over the container's free space. 417 | */ 418 | std::span write_span() { 419 | return { write_ptr(), free() }; 420 | } 421 | }; 422 | 423 | } // hexi 424 | -------------------------------------------------------------------------------- /include/hexi/stream_adaptors.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | namespace hexi { 12 | 13 | template 14 | class stream_read_adaptor final { 15 | stream_type& _stream; 16 | 17 | public: 18 | stream_read_adaptor(stream_type& stream) 19 | : _stream(stream) {} 20 | 21 | void operator&(auto&& arg) { 22 | _stream >> arg; 23 | } 24 | 25 | template 26 | void operator()(Ts&&... args) { 27 | (_stream >> ... >> args); 28 | } 29 | 30 | template 31 | void forward(Ts&&... args) { 32 | _stream.get(std::forward(args)...); 33 | } 34 | }; 35 | 36 | template 37 | class stream_write_adaptor final { 38 | stream_type& _stream; 39 | 40 | public: 41 | stream_write_adaptor(stream_type& stream) 42 | : _stream(stream) {} 43 | 44 | void operator&(auto&& arg) { 45 | _stream << arg; 46 | } 47 | 48 | template 49 | void operator()(Ts&&... args) { 50 | (_stream << ... << args); 51 | } 52 | 53 | template 54 | void forward(Ts&&... args) { 55 | _stream.put(std::forward(args)...); 56 | } 57 | }; 58 | 59 | } // hexi -------------------------------------------------------------------------------- /single_include/hexi_fwd.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | namespace hexi { 12 | 13 | template 14 | class binary_stream; 15 | 16 | template 17 | class buffer_adaptor; 18 | 19 | template 20 | class buffer_sequence; 21 | 22 | template 23 | class dynamic_buffer; 24 | 25 | class file_buffer; 26 | 27 | template 28 | class static_buffer; 29 | 30 | namespace pmc { 31 | 32 | class binary_stream; 33 | class binary_stream_reader; 34 | class binary_stream_writer; 35 | class stream_base; 36 | class null_buffer; 37 | 38 | template 39 | class buffer_adaptor; 40 | 41 | template 42 | class buffer_read_adaptor; 43 | 44 | template 45 | class buffer_write_adaptor; 46 | 47 | } // pmc 48 | 49 | } // hexi -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # _ _ 2 | # | |__ _____ _(_) 3 | # | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | # | | | | __/> <| | Version 1.0 5 | # |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | set(EXECUTABLE_NAME unit_tests) 8 | 9 | set(EXECUTABLE_SRC 10 | binary_stream.cpp 11 | binary_stream_pmc.cpp 12 | buffer_adaptor.cpp 13 | buffer_adaptor_pmc.cpp 14 | buffer_utility.cpp 15 | dynamic_buffer.cpp 16 | file_buffer.cpp 17 | intrusive_storage.cpp 18 | static_buffer.cpp 19 | tls_block_allocator.cpp 20 | null_buffer.cpp 21 | helpers.h 22 | final_action.h 23 | ) 24 | 25 | add_executable(${EXECUTABLE_NAME} ${EXECUTABLE_SRC}) 26 | target_link_libraries(${EXECUTABLE_NAME} gtest gtest_main) 27 | target_include_directories(${EXECUTABLE_NAME} PRIVATE ../include) 28 | gtest_discover_tests(${EXECUTABLE_NAME}) 29 | 30 | add_custom_command(TARGET ${EXECUTABLE_NAME} PRE_BUILD 31 | COMMAND ${CMAKE_COMMAND} -E copy_directory 32 | ${CMAKE_SOURCE_DIR}/tests/data ${CMAKE_CURRENT_BINARY_DIR}/data) 33 | 34 | INSTALL(TARGETS ${EXECUTABLE_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}) 35 | INSTALL(DIRECTORY data/ DESTINATION ${CMAKE_INSTALL_PREFIX}/data) -------------------------------------------------------------------------------- /tests/buffer_adaptor.cpp: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std::literals; 17 | 18 | TEST(buffer_adaptor, size_empty_initial) { 19 | std::array buffer; 20 | hexi::buffer_adaptor adaptor(buffer); 21 | ASSERT_EQ(adaptor.size(), 0); 22 | } 23 | 24 | TEST(buffer_adaptor, empty) { 25 | std::vector buffer; 26 | hexi::buffer_adaptor adaptor(buffer); 27 | ASSERT_TRUE(adaptor.empty()); 28 | buffer.emplace_back(1); 29 | adaptor.advance_write(1); 30 | ASSERT_FALSE(adaptor.empty()); 31 | } 32 | 33 | TEST(buffer_adaptor, size_populated_initial) { 34 | std::array buffer { 1 }; 35 | hexi::buffer_adaptor adaptor(buffer); 36 | ASSERT_EQ(adaptor.size(), buffer.size()); 37 | } 38 | 39 | TEST(buffer_adaptor, resize_match) { 40 | std::vector buffer { 1, 2, 3, 4, 5 }; 41 | hexi::buffer_adaptor adaptor(buffer); 42 | ASSERT_EQ(adaptor.size(), buffer.size()); 43 | buffer.emplace_back(6); 44 | adaptor.advance_write(1); 45 | ASSERT_EQ(adaptor.size(), buffer.size()); 46 | } 47 | 48 | TEST(buffer_adaptor, read_one) { 49 | std::array buffer { 1, 2, 3 }; 50 | hexi::buffer_adaptor adaptor(buffer); 51 | std::uint8_t value = 0; 52 | adaptor.read(&value, 1); 53 | ASSERT_EQ(adaptor.size(), buffer.size() - 1); 54 | ASSERT_EQ(value, 1); 55 | } 56 | 57 | TEST(buffer_adaptor, read_all) { 58 | std::array buffer { 1, 2, 3 }; 59 | hexi::buffer_adaptor adaptor(buffer); 60 | std::array values{}; 61 | adaptor.read(&values, values.size()); 62 | ASSERT_TRUE(std::ranges::equal(buffer, values)); 63 | } 64 | 65 | TEST(buffer_adaptor, single_skip_read) { 66 | std::array buffer { 1, 2, 3 }; 67 | hexi::buffer_adaptor adaptor(buffer); 68 | std::uint8_t value = 0; 69 | adaptor.skip(1); 70 | adaptor.read(&value, 1); 71 | ASSERT_EQ(adaptor.size(), 1); 72 | ASSERT_EQ(value, buffer[1]); 73 | } 74 | 75 | TEST(buffer_adaptor, multiskip_read) { 76 | std::array buffer { 1, 2, 3, 4, 5, 6 }; 77 | hexi::buffer_adaptor adaptor(buffer); 78 | std::uint8_t value = 0; 79 | adaptor.skip(5); 80 | adaptor.read(&value, 1); 81 | ASSERT_TRUE(adaptor.empty()); 82 | ASSERT_EQ(value, buffer[5]); 83 | } 84 | 85 | TEST(buffer_adaptor, write) { 86 | std::vector buffer; 87 | hexi::buffer_adaptor adaptor(buffer); 88 | std::array values { 1, 2, 3, 4, 5, 6 }; 89 | adaptor.write(values.data(), values.size()); 90 | ASSERT_EQ(adaptor.size(), values.size()); 91 | ASSERT_EQ(buffer.size(), values.size()); 92 | ASSERT_TRUE(std::ranges::equal(values, buffer)); 93 | const auto size = adaptor.size(); 94 | adaptor.write('\0'); 95 | ASSERT_EQ(adaptor.size(), size + 1); 96 | } 97 | 98 | TEST(buffer_adaptor, write_append) { 99 | std::vector buffer { 1, 2, 3 }; 100 | hexi::buffer_adaptor adaptor(buffer); 101 | std::array values { 4, 5, 6 }; 102 | adaptor.write(values.data(), values.size()); 103 | ASSERT_EQ(buffer.size(), 6); 104 | ASSERT_EQ(adaptor.size(), buffer.size()); 105 | std::array expected { 1, 2, 3, 4, 5, 6 }; 106 | ASSERT_TRUE(std::ranges::equal(expected, buffer)); 107 | } 108 | 109 | TEST(buffer_adaptor, can_write_seek) { 110 | std::vector buffer; 111 | hexi::buffer_adaptor adaptor(buffer); 112 | ASSERT_TRUE(adaptor.can_write_seek()); 113 | } 114 | 115 | TEST(buffer_adaptor, write_seek_back) { 116 | std::vector buffer { 1, 2, 3 }; 117 | hexi::buffer_adaptor adaptor(buffer); 118 | std::array values { 4, 5, 6 }; 119 | adaptor.write_seek(hexi::buffer_seek::sk_backward, 2); 120 | adaptor.write(values.data(), values.size()); 121 | ASSERT_EQ(buffer.size(), 4); 122 | ASSERT_EQ(adaptor.size(), buffer.size()); 123 | std::array expected { 1, 4, 5, 6 }; 124 | ASSERT_TRUE(std::ranges::equal(expected, buffer)); 125 | } 126 | 127 | TEST(buffer_adaptor, write_seek_start) { 128 | std::vector buffer { 1, 2, 3 }; 129 | hexi::buffer_adaptor adaptor(buffer); 130 | std::array values { 4, 5, 6 }; 131 | adaptor.write_seek(hexi::buffer_seek::sk_absolute, 0); 132 | adaptor.write(values.data(), values.size()); 133 | ASSERT_EQ(buffer.size(), values.size()); 134 | ASSERT_EQ(adaptor.size(), buffer.size()); 135 | ASSERT_TRUE(std::ranges::equal(buffer, values)); 136 | } 137 | 138 | TEST(buffer_adaptor, read_ptr) { 139 | std::array buffer { 1, 2, 3 }; 140 | hexi::buffer_adaptor adaptor(buffer); 141 | auto ptr = adaptor.read_ptr(); 142 | ASSERT_EQ(*ptr, buffer[0]); 143 | adaptor.skip(1); 144 | ptr = adaptor.read_ptr(); 145 | ASSERT_EQ(*ptr, buffer[1]); 146 | adaptor.skip(1); 147 | ptr = adaptor.read_ptr(); 148 | ASSERT_EQ(*ptr, buffer[2]); 149 | } 150 | 151 | TEST(buffer_adaptor, subscript) { 152 | std::array buffer { 1, 2, 3 }; 153 | hexi::buffer_adaptor adaptor(buffer); 154 | ASSERT_EQ(adaptor[0], 1); 155 | ASSERT_EQ(adaptor[1], 2); 156 | ASSERT_EQ(adaptor[2], 3); 157 | buffer[0] = 4; 158 | ASSERT_EQ(adaptor[0], 4); 159 | adaptor[0] = 5; 160 | ASSERT_EQ(adaptor[0], 5); 161 | ASSERT_EQ(adaptor[0], buffer[0]); 162 | } 163 | 164 | TEST(buffer_adaptor, find_first_of) { 165 | std::vector buffer; 166 | hexi::buffer_adaptor adaptor(buffer); 167 | const auto str = "The quick brown fox jumped over the lazy dog"sv; 168 | adaptor.write(str.data(), str.size()); 169 | auto pos = adaptor.find_first_of('\0'); 170 | ASSERT_EQ(pos, adaptor.npos); // direct string write is not terminated 171 | pos = adaptor.find_first_of('g'); 172 | ASSERT_EQ(pos, 43); 173 | pos = adaptor.find_first_of('T'); 174 | ASSERT_EQ(pos, 0); 175 | pos = adaptor.find_first_of('t'); 176 | ASSERT_EQ(pos, 32); 177 | } 178 | 179 | // test optimised write() for buffers supporting resize_and_overwrite 180 | TEST(buffer_adaptor, string_buffer) { 181 | std::string buffer; 182 | hexi::buffer_adaptor adaptor(buffer); 183 | const auto str = "The quick brown fox jumped over the lazy dog"sv; 184 | adaptor.write(str.data(), str.size()); 185 | ASSERT_EQ(buffer, str); 186 | } -------------------------------------------------------------------------------- /tests/buffer_adaptor_pmc.cpp: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std::literals; 17 | 18 | TEST(buffer_adaptor_pmc, size_empty_initial) { 19 | std::vector buffer; 20 | hexi::pmc::buffer_adaptor adaptor(buffer); 21 | ASSERT_EQ(adaptor.size(), 0); 22 | } 23 | 24 | TEST(buffer_adaptor_pmc, empty) { 25 | std::vector buffer; 26 | hexi::pmc::buffer_adaptor adaptor(buffer); 27 | ASSERT_TRUE(adaptor.empty()); 28 | buffer.emplace_back(1); 29 | adaptor.advance_write(1); 30 | ASSERT_FALSE(adaptor.empty()); 31 | } 32 | 33 | TEST(buffer_adaptor_pmc, size_populated_initial) { 34 | std::vector buffer { 1 }; 35 | hexi::pmc::buffer_adaptor adaptor(buffer); 36 | ASSERT_EQ(adaptor.size(), buffer.size()); 37 | } 38 | 39 | TEST(buffer_adaptor_pmc, resize_match) { 40 | std::vector buffer { 1, 2, 3, 4, 5 }; 41 | hexi::pmc::buffer_adaptor adaptor(buffer); 42 | ASSERT_EQ(adaptor.size(), buffer.size()); 43 | buffer.emplace_back(6); 44 | adaptor.advance_write(1); 45 | ASSERT_EQ(adaptor.size(), buffer.size()); 46 | } 47 | 48 | TEST(buffer_adaptor_pmc, read_one) { 49 | std::vector buffer { 1, 2, 3 }; 50 | hexi::pmc::buffer_adaptor adaptor(buffer); 51 | std::uint8_t value = 0; 52 | adaptor.read(&value, 1); 53 | ASSERT_EQ(adaptor.size(), buffer.size() - 1); 54 | ASSERT_EQ(value, 1); 55 | } 56 | 57 | TEST(buffer_adaptor_pmc, read_all) { 58 | // unless buffer reuse optimisation is disabled, the original buffer 59 | // will be emptied when fully read, so we need a copy for testing 60 | // the output 61 | std::array expected { 1, 2, 3 }; 62 | std::vector buffer(expected.begin(), expected.end()); 63 | hexi::pmc::buffer_adaptor adaptor(buffer); 64 | std::array values{}; 65 | adaptor.read(values.data(), values.size()); 66 | ASSERT_TRUE(std::ranges::equal(expected, values)); 67 | } 68 | 69 | TEST(buffer_adaptor_pmc, single_skip_read) { 70 | std::vector buffer { 1, 2, 3 }; 71 | hexi::pmc::buffer_adaptor adaptor(buffer); 72 | std::uint8_t value = 0; 73 | adaptor.skip(1); 74 | adaptor.read(&value, 1); 75 | ASSERT_EQ(adaptor.size(), 1); 76 | ASSERT_EQ(value, buffer[1]); 77 | } 78 | 79 | TEST(buffer_adaptor_pmc, multiskip_read) { 80 | std::vector buffer { 1, 2, 3, 4, 5, 6 }; 81 | hexi::pmc::buffer_adaptor adaptor(buffer); 82 | std::uint8_t value = 0; 83 | adaptor.skip(5); 84 | adaptor.read(&value, 1); 85 | ASSERT_TRUE(adaptor.empty()); 86 | ASSERT_EQ(value, buffer[5]); 87 | } 88 | 89 | TEST(buffer_adaptor_pmc, write) { 90 | std::vector buffer; 91 | hexi::pmc::buffer_adaptor adaptor(buffer); 92 | std::array values { 1, 2, 3, 4, 5, 6 }; 93 | adaptor.write(values.data(), values.size()); 94 | ASSERT_EQ(adaptor.size(), values.size()); 95 | ASSERT_EQ(buffer.size(), values.size()); 96 | ASSERT_TRUE(std::ranges::equal(values, buffer)); 97 | const auto size = adaptor.size(); 98 | adaptor.write('\0'); 99 | ASSERT_EQ(adaptor.size(), size + 1); 100 | } 101 | 102 | TEST(buffer_adaptor_pmc, write_append) { 103 | std::vector buffer { 1, 2, 3 }; 104 | hexi::pmc::buffer_adaptor adaptor(buffer); 105 | std::array values { 4, 5, 6 }; 106 | adaptor.write(values.data(), values.size()); 107 | ASSERT_EQ(buffer.size(), 6); 108 | ASSERT_EQ(adaptor.size(), buffer.size()); 109 | std::array expected { 1, 2, 3, 4, 5, 6 }; 110 | ASSERT_TRUE(std::ranges::equal(expected, buffer)); 111 | } 112 | 113 | TEST(buffer_adaptor_pmc, can_write_seek) { 114 | std::vector buffer; 115 | hexi::pmc::buffer_adaptor adaptor(buffer); 116 | ASSERT_TRUE(adaptor.can_write_seek()); 117 | } 118 | 119 | TEST(buffer_adaptor_pmc, write_seek_back) { 120 | std::vector buffer { 1, 2, 3 }; 121 | hexi::pmc::buffer_adaptor adaptor(buffer); 122 | std::array values { 4, 5, 6 }; 123 | adaptor.write_seek(hexi::buffer_seek::sk_backward, 2); 124 | adaptor.write(values.data(), values.size()); 125 | ASSERT_EQ(buffer.size(), 4); 126 | ASSERT_EQ(adaptor.size(), buffer.size()); 127 | std::array expected { 1, 4, 5, 6 }; 128 | ASSERT_TRUE(std::ranges::equal(expected, buffer)); 129 | } 130 | 131 | TEST(buffer_adaptor_pmc, write_seek_start) { 132 | std::vector buffer { 1, 2, 3 }; 133 | hexi::pmc::buffer_adaptor adaptor(buffer); 134 | std::array values { 4, 5, 6 }; 135 | adaptor.write_seek(hexi::buffer_seek::sk_absolute, 0); 136 | adaptor.write(values.data(), values.size()); 137 | ASSERT_EQ(buffer.size(), values.size()); 138 | ASSERT_EQ(adaptor.size(), buffer.size()); 139 | ASSERT_TRUE(std::ranges::equal(buffer, values)); 140 | } 141 | 142 | TEST(buffer_adaptor_pmc, read_ptr) { 143 | std::vector buffer { 1, 2, 3 }; 144 | hexi::pmc::buffer_adaptor adaptor(buffer); 145 | auto ptr = adaptor.read_ptr(); 146 | ASSERT_EQ(*ptr, buffer[0]); 147 | adaptor.skip(1); 148 | ptr = adaptor.read_ptr(); 149 | ASSERT_EQ(*ptr, buffer[1]); 150 | adaptor.skip(1); 151 | ptr = adaptor.read_ptr(); 152 | ASSERT_EQ(*ptr, buffer[2]); 153 | } 154 | 155 | TEST(buffer_adaptor_pmc, subscript) { 156 | std::vector buffer { 1, 2, 3 }; 157 | hexi::pmc::buffer_adaptor adaptor(buffer); 158 | ASSERT_EQ(adaptor[0], std::byte(1)); 159 | ASSERT_EQ(adaptor[1], std::byte(2)); 160 | ASSERT_EQ(adaptor[2], std::byte(3)); 161 | buffer[0] = 4; 162 | ASSERT_EQ(adaptor[0], std::byte(4)); 163 | } 164 | 165 | TEST(buffer_adaptor_pmc, find_first_of) { 166 | std::vector buffer; 167 | hexi::pmc::buffer_adaptor adaptor(buffer); 168 | const auto str = "The quick brown fox jumped over the lazy dog"sv; 169 | adaptor.write(str.data(), str.size()); 170 | auto pos = adaptor.find_first_of(std::byte('\0')); 171 | ASSERT_EQ(pos, adaptor.npos); // direct string write is not terminated 172 | pos = adaptor.find_first_of(std::byte('g')); 173 | ASSERT_EQ(pos, 43); 174 | pos = adaptor.find_first_of(std::byte('T')); 175 | ASSERT_EQ(pos, 0); 176 | pos = adaptor.find_first_of(std::byte('t')); 177 | ASSERT_EQ(pos, 32); 178 | } 179 | 180 | // test optimised write() for buffers supporting resize_and_overwrite 181 | TEST(buffer_adaptor_pmc, string_buffer) { 182 | std::string buffer; 183 | hexi::pmc::buffer_adaptor adaptor(buffer); 184 | const auto str = "The quick brown fox jumped over the lazy dog"sv; 185 | adaptor.write(str.data(), str.size()); 186 | ASSERT_EQ(buffer, str); 187 | } -------------------------------------------------------------------------------- /tests/buffer_utility.cpp: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace hexi::detail; 13 | 14 | TEST(buffer_utility, src_dest_overlap_start) { 15 | std::array buffer{}; 16 | ASSERT_TRUE(region_overlap(buffer.data(), buffer.size(), buffer.data(), buffer.size())); 17 | } 18 | 19 | TEST(buffer_utility, src_dest_overlap_end) { 20 | std::array buffer{}; 21 | const auto end = buffer.data() + buffer.size(); 22 | ASSERT_TRUE(region_overlap(buffer.data(), buffer.size(), end - 1, 1)); 23 | } 24 | 25 | TEST(buffer_utility, src_dest_overlap_beyond_end) { 26 | std::array buffer{}; 27 | const auto end = buffer.data() + buffer.size(); 28 | ASSERT_FALSE(region_overlap(buffer.data(), buffer.size(), end, 1)); 29 | } 30 | 31 | TEST(buffer_utility, src_dest_overlap_between) { 32 | std::array buffer{}; 33 | ASSERT_TRUE(region_overlap(buffer.data(), buffer.size(), buffer.data() + (buffer.size() - 5), 1)); 34 | } 35 | 36 | TEST(buffer_utility, src_dest_overlap_no_overlap) { 37 | std::array buffer{}, buffer2{}; 38 | ASSERT_FALSE(region_overlap(buffer.data(), buffer.size(), buffer2.data(), buffer2.size())); 39 | } -------------------------------------------------------------------------------- /tests/data/filebuffer: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmberEmu/Hexi/543bd01d3b9ce57e9d3758cab4eca248e88fcfde/tests/data/filebuffer -------------------------------------------------------------------------------- /tests/file_buffer.cpp: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #include "final_action.h" 8 | #include "helpers.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | TEST(file_buffer, read) { 19 | std::filesystem::path path("data/filebuffer"); 20 | ASSERT_TRUE(std::filesystem::exists(path)); 21 | 22 | hexi::file_buffer buffer(path); 23 | ASSERT_TRUE(buffer) << "File open failed"; 24 | 25 | std::uint8_t w = 0; 26 | std::uint16_t x = 0; 27 | std::uint32_t y = 0; 28 | std::uint64_t z = 0; 29 | 30 | std::string_view strcmp { "The quick brown fox jumped over the lazy dog." }; 31 | std::string str_out; 32 | str_out.resize(strcmp.size()); 33 | 34 | buffer.read(&w); 35 | buffer.read(&x); 36 | buffer.read(&y); 37 | buffer.read(&z); 38 | buffer.read(str_out.data(), str_out.size()); 39 | 40 | ASSERT_TRUE(buffer) << "File read error occurred"; 41 | 42 | hexi::endian::little_to_native_inplace(x); 43 | hexi::endian::little_to_native_inplace(y); 44 | hexi::endian::little_to_native_inplace(z); 45 | 46 | ASSERT_EQ(w, 47) << "Wrong uint8 value"; 47 | ASSERT_EQ(x, 49197) << "Wrong uint16 value"; 48 | ASSERT_EQ(y, 2173709693) << "Wrong uint32 value"; 49 | ASSERT_EQ(z, 1438110846748337907) << "Wrong uint64 value"; 50 | ASSERT_EQ(str_out, strcmp); 51 | } 52 | 53 | TEST(file_buffer, write) { 54 | std::filesystem::path path { "tmp_unittest_file_buffer_write" }; 55 | 56 | final_action fa([path] { 57 | std::filesystem::remove(path); 58 | }); 59 | 60 | // in case previous clean-up failed 61 | std::filesystem::remove(path); 62 | 63 | hexi::file_buffer buffer(path); 64 | ASSERT_TRUE(buffer) << "File open failed"; 65 | 66 | std::uint8_t w = 47; 67 | std::uint16_t x = 49197; 68 | std::uint32_t y = 2173709693; 69 | std::uint64_t z = 1438110846748337907; 70 | std::string str { "The quick brown fox jumped over the lazy dog." }; 71 | 72 | hexi::endian::native_to_little_inplace(x); 73 | hexi::endian::native_to_little_inplace(y); 74 | hexi::endian::native_to_little_inplace(z); 75 | 76 | buffer.write(w); 77 | buffer.write(x); 78 | buffer.write(y); 79 | buffer.write(z); 80 | buffer.write(str.data(), str.size() + 1); // write terminator 81 | buffer.flush(); // ensure data has been written before the read 82 | 83 | const auto reference = read_file("data/filebuffer"); 84 | const auto created = read_file(path); 85 | ASSERT_TRUE(std::ranges::equal(reference, created)); 86 | } 87 | 88 | TEST(file_buffer, copy) { 89 | std::filesystem::path path("data/filebuffer"); 90 | ASSERT_TRUE(std::filesystem::exists(path)); 91 | 92 | hexi::file_buffer buffer(path); 93 | 94 | std::uint8_t in = 0; 95 | buffer.copy(&in); 96 | ASSERT_EQ(in, 47); 97 | buffer.copy(&in); 98 | ASSERT_EQ(in, 47); 99 | buffer.read(&in); // advance to next byte without skip 100 | ASSERT_EQ(in, 47); 101 | buffer.copy(&in); 102 | ASSERT_EQ(in, 45); 103 | } 104 | 105 | TEST(file_buffer, skip) { 106 | std::filesystem::path path("data/filebuffer"); 107 | ASSERT_TRUE(std::filesystem::exists(path)); 108 | 109 | hexi::file_buffer buffer(path); 110 | 111 | { 112 | std::uint8_t in = 0; 113 | buffer.read(&in); 114 | ASSERT_EQ(in, 47); 115 | buffer.skip(1); 116 | buffer.read(&in); 117 | ASSERT_EQ(in, 192); 118 | } 119 | 120 | buffer.skip(4); 121 | 122 | { 123 | std::uint32_t in = 0; 124 | buffer.read(&in); 125 | ASSERT_EQ(in, 403842803); 126 | } 127 | 128 | ASSERT_TRUE(buffer) << "Read failure"; 129 | } 130 | 131 | TEST(file_buffer, initial_size) { 132 | std::filesystem::path path("data/filebuffer"); 133 | ASSERT_TRUE(std::filesystem::exists(path)); 134 | 135 | hexi::file_buffer buffer(path); 136 | ASSERT_TRUE(buffer) << "File open failed"; 137 | ASSERT_EQ(buffer.size(), 61) << "Wrong size"; 138 | } 139 | 140 | TEST(file_buffer, read_write_interleave) { 141 | std::filesystem::path path { "tmp_unittest_file_buffer_read_write_mix" }; 142 | 143 | final_action fa([path] { 144 | std::filesystem::remove(path); 145 | }); 146 | 147 | // in case previous clean-up failed 148 | std::filesystem::remove(path); 149 | 150 | hexi::file_buffer buffer(path); 151 | ASSERT_TRUE(buffer) << "File open failed"; 152 | 153 | { 154 | std::uint8_t in = 42; 155 | std::uint8_t out = 0; 156 | buffer.write(in); 157 | buffer.read(&out); 158 | ASSERT_EQ(in, out); 159 | } 160 | 161 | { 162 | std::uint16_t in = 64245; 163 | std::uint16_t out = 0; 164 | buffer.write(in); 165 | buffer.read(&out); 166 | ASSERT_EQ(in, out); 167 | } 168 | 169 | { 170 | std::uint32_t in = 80144; 171 | std::uint32_t out = 0; 172 | buffer.write(in); 173 | buffer.read(&out); 174 | ASSERT_EQ(in, out); 175 | } 176 | 177 | { 178 | std::uint64_t in = 1438110846748337907; 179 | std::uint64_t out = 0; 180 | buffer.write(in); 181 | buffer.read(&out); 182 | ASSERT_EQ(in, out); 183 | } 184 | 185 | { 186 | std::uint16_t x_in = 60925; 187 | std::uint16_t y_in = 1352; 188 | std::uint16_t x_out = 0; 189 | std::uint16_t y_out = 0; 190 | buffer.write(x_in); 191 | buffer.write(y_in); 192 | buffer.read(&x_out); 193 | buffer.read(&y_out); 194 | ASSERT_EQ(x_in, x_out); 195 | ASSERT_EQ(y_in, y_out); 196 | } 197 | } 198 | 199 | TEST(file_buffer, find_first_of) { 200 | std::filesystem::path path("data/filebuffer"); 201 | 202 | ASSERT_TRUE(std::filesystem::exists(path)); 203 | 204 | hexi::file_buffer buffer(path); 205 | ASSERT_TRUE(buffer) << "File open failed"; 206 | 207 | EXPECT_EQ(buffer.find_first_of(0x2f), 0); 208 | EXPECT_EQ(buffer.find_first_of(0x20), 18); 209 | EXPECT_EQ(buffer.find_first_of(0x6f), 27); 210 | EXPECT_EQ(buffer.find_first_of(0x6a), 35); 211 | EXPECT_EQ(buffer.find_first_of(0x00), 60); 212 | EXPECT_EQ(buffer.find_first_of(0xff), hexi::file_buffer::npos); 213 | ASSERT_TRUE(buffer) << "Error occurred during seeking"; 214 | } -------------------------------------------------------------------------------- /tests/final_action.h: -------------------------------------------------------------------------------- 1 | /////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // Copyright (c) 2015 Microsoft Corporation. All rights reserved. 4 | // 5 | // This code is licensed under the MIT License (MIT). 6 | // 7 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 8 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 9 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 10 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 11 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 12 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 13 | // THE SOFTWARE. 14 | // 15 | /////////////////////////////////////////////////////////////////////////////// 16 | 17 | #include 18 | #include 19 | 20 | #ifndef GSL_UTIL_H 21 | #define GSL_UTIL_H 22 | 23 | // final_action allows you to ensure something gets run at the end of a scope 24 | template 25 | class final_action 26 | { 27 | public: 28 | explicit final_action(const F& ff) noexcept : f{ff} { } 29 | explicit final_action(F&& ff) noexcept : f{std::move(ff)} { } 30 | 31 | ~final_action() noexcept { if (invoke) f(); } 32 | 33 | final_action(final_action&& other) noexcept 34 | : f(std::move(other.f)), invoke(std::exchange(other.invoke, false)) 35 | { } 36 | 37 | final_action(const final_action&) = delete; 38 | void operator=(const final_action&) = delete; 39 | void operator=(final_action&&) = delete; 40 | 41 | private: 42 | F f; 43 | bool invoke = true; 44 | }; 45 | 46 | // finally() - convenience function to generate a final_action 47 | template 48 | auto finally(F&& f) noexcept 49 | { 50 | return final_action>{std::forward(f)}; 51 | } 52 | 53 | #endif // GSL_UTIL_H 54 | -------------------------------------------------------------------------------- /tests/helpers.h: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | static std::vector read_file(const std::filesystem::path& path) { 13 | std::ifstream file(path, std::ios::ate | std::ios::binary); 14 | const auto pos = file.tellg(); 15 | 16 | if(!file) { 17 | throw std::runtime_error("File open failed"); 18 | } 19 | 20 | if(!pos) { 21 | return{}; 22 | } 23 | 24 | std::vector buffer(pos); 25 | file.seekg(0, std::ios::beg); 26 | file.read(buffer.data(), buffer.size()); 27 | 28 | if(!file) { 29 | throw std::runtime_error("File read failed"); 30 | } 31 | 32 | return buffer; 33 | } -------------------------------------------------------------------------------- /tests/intrusive_storage.cpp: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | 14 | TEST(intrusive_storage, size) { 15 | const int iterations = 5; 16 | hexi::detail::intrusive_storage buffer; 17 | int foo = 24221; 18 | std::size_t written = 0; 19 | 20 | for(int i = 0; i < 5; ++i) { 21 | written += buffer.write(&foo, sizeof(int)); 22 | } 23 | 24 | ASSERT_EQ(sizeof(int) * iterations, written) << "Number of bytes written is incorrect"; 25 | ASSERT_EQ(sizeof(int) * iterations, buffer.size()) << "Buffer size is incorrect"; 26 | 27 | // attempt to exceed capacity - write should return 0 28 | written = buffer.write(&foo, sizeof(int)); 29 | ASSERT_EQ(0, written) << "Number of bytes written is incorrect"; 30 | ASSERT_EQ(sizeof(int) * iterations, buffer.size()) << "Buffer size is incorrect"; 31 | } 32 | 33 | TEST(intrusive_storage, read_write_consistency) { 34 | const char text[] = "The quick brown fox jumps over the lazy dog"; 35 | hexi::detail::intrusive_storage buffer; 36 | 37 | std::size_t written = buffer.write(text, sizeof(text)); 38 | ASSERT_EQ(sizeof(text), written) << "Incorrect write size"; 39 | 40 | char text_out[sizeof(text)]; 41 | 42 | std::size_t read = buffer.read(text_out, sizeof(text)); 43 | ASSERT_EQ(sizeof(text), read) << "Incorrect read size"; 44 | 45 | ASSERT_STREQ(text, text_out) << "Read produced incorrect result"; 46 | ASSERT_EQ(0, buffer.size()) << "Buffer should be empty"; 47 | } 48 | 49 | TEST(intrusive_storage, skip) { 50 | const char text[] = "The quick brown fox jumps over the lazy dog"; 51 | hexi::detail::intrusive_storage buffer; 52 | 53 | buffer.write(text, sizeof(text)); 54 | 55 | std::array text_out{}; 56 | std::size_t skipped = buffer.skip(4); 57 | ASSERT_EQ(4, skipped) << "Incorrect skip length"; 58 | 59 | buffer.read(text_out.data(), sizeof(text) - 4); 60 | ASSERT_STREQ("quick brown fox jumps over the lazy dog", text_out.data()) 61 | << "Skip/read produced incorrect result"; 62 | } 63 | 64 | TEST(intrusive_storage, read_write_string_view) { 65 | hexi::detail::intrusive_storage<128, char> buffer; 66 | std::string_view str { "The quick brown fox jumped over the lazy dog" }; 67 | buffer.write(str.data(), str.size()); 68 | ASSERT_EQ(str.size(), buffer.size()); 69 | 70 | std::array out{}; 71 | buffer.read(out.data(), str.size()); 72 | ASSERT_STREQ(str.data(), out.data()); 73 | ASSERT_TRUE(buffer.size() == 0); 74 | } 75 | 76 | TEST(intrusive_storage, read_wrie_ints) { 77 | hexi::detail::intrusive_storage<128, char> buffer; 78 | std::array in { 42, 1657, 1558, -1563 }; 79 | buffer.write(in.data(), sizeof(in)); 80 | ASSERT_EQ(sizeof(in), buffer.size()); 81 | 82 | std::array out {}; 83 | buffer.read(out.data(), sizeof(out)); 84 | ASSERT_EQ(in, out); 85 | ASSERT_TRUE(buffer.size() == 0); 86 | } 87 | 88 | TEST(intrusive_storage, subscript) { 89 | hexi::detail::intrusive_storage<8, char> buffer; 90 | std::string_view str { "ABC" }; 91 | buffer.write(str.data(), str.size()); 92 | ASSERT_EQ(str[0], buffer[0]); 93 | ASSERT_EQ(str[1], buffer[1]); 94 | ASSERT_EQ(str[2], buffer[2]); 95 | buffer[0] = 'C'; 96 | buffer[1] = 'D'; 97 | buffer[2] = 'E'; 98 | ASSERT_EQ('C', buffer[0]); 99 | ASSERT_EQ('D', buffer[1]); 100 | ASSERT_EQ('E', buffer[2]); 101 | } 102 | 103 | TEST(intrusive_storage, advance_write) { 104 | hexi::detail::intrusive_storage<32, char> buffer; 105 | constexpr std::string_view str {"A short string"}; 106 | const std::size_t advance = 10; 107 | buffer.advance_write(advance); 108 | ASSERT_EQ(buffer.size(), advance); 109 | buffer.write(str.data(), str.size() + 1); 110 | ASSERT_EQ(buffer.size(), advance + str.size() + 1); 111 | std::array out{}; 112 | buffer.read(out.data(), advance); // skip the 'empty' data 113 | buffer.read(out.data(), str.size() + 1); 114 | ASSERT_STREQ(str.data(), out.data()); 115 | } 116 | -------------------------------------------------------------------------------- /tests/null_buffer.cpp: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | TEST(null_buffer, write_size) { 14 | hexi::null_buffer buffer; 15 | hexi::binary_stream stream(buffer); 16 | 17 | stream << std::uint8_t(0); 18 | ASSERT_EQ(stream.total_write(), 1); 19 | stream << std::uint16_t(0); 20 | ASSERT_EQ(stream.total_write(), 3); 21 | stream << std::uint32_t(0); 22 | ASSERT_EQ(stream.total_write(), 7); 23 | stream << std::uint64_t(0); 24 | ASSERT_EQ(stream.total_write(), 15); 25 | 26 | std::string_view str { "A library serves no purpose unless someone is using it." }; 27 | stream << hexi::null_terminated(str); 28 | ASSERT_EQ(stream.total_write(), 71); 29 | ASSERT_TRUE(buffer.empty()); 30 | } 31 | 32 | TEST(null_buffer, write_seek) { 33 | hexi::null_buffer buffer; 34 | hexi::binary_stream stream(buffer); 35 | ASSERT_THROW(buffer.write_seek(hexi::buffer_seek::sk_absolute, 0), hexi::exception); 36 | } -------------------------------------------------------------------------------- /tests/static_buffer.cpp: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | using namespace std::literals; 17 | 18 | TEST(static_buffer, initial_empty) { 19 | hexi::static_buffer buffer; 20 | ASSERT_TRUE(buffer.empty()); 21 | ASSERT_EQ(buffer.size(), 0); 22 | } 23 | 24 | TEST(static_buffer, initial_not_empty) { 25 | hexi::static_buffer buffer { '1', '2' }; 26 | ASSERT_FALSE(buffer.empty()); 27 | ASSERT_EQ(buffer.size(), 2); 28 | ASSERT_EQ(buffer[0], '1'); 29 | ASSERT_EQ(buffer[1], '2'); 30 | } 31 | 32 | TEST(static_buffer, empty) { 33 | hexi::static_buffer buffer; 34 | ASSERT_TRUE(buffer.empty()); 35 | char val = '\0'; 36 | buffer.write(&val, 1); 37 | ASSERT_FALSE(buffer.empty()); 38 | } 39 | 40 | TEST(static_buffer, read_one) { 41 | hexi::static_buffer buffer { '1', '2', '3' }; 42 | char value = 0; 43 | buffer.read(&value, 1); 44 | ASSERT_EQ(buffer.size(), 2); 45 | ASSERT_EQ(value, '1'); 46 | } 47 | 48 | TEST(static_buffer, read_all) { 49 | hexi::static_buffer buffer { '1', '2', '3' }; 50 | std::array expected{ '1', '2', '3' }; 51 | std::array values{}; 52 | buffer.read(values.data(), values.size()); 53 | ASSERT_TRUE(std::ranges::equal(values, expected)); 54 | } 55 | 56 | TEST(static_buffer, single_read_skip) { 57 | hexi::static_buffer buffer { '1', '2', '3' }; 58 | std::uint8_t value = 0; 59 | buffer.skip(1); 60 | buffer.read(&value, 1); 61 | ASSERT_EQ(buffer.size(), 1); 62 | ASSERT_EQ(buffer[0], '3'); 63 | } 64 | 65 | TEST(static_buffer, multiskip_read) { 66 | hexi::static_buffer buffer { '1', '2', '3', '4', '5', '6' }; 67 | std::uint8_t value = 0; 68 | buffer.skip(5); 69 | buffer.read(&value, 1); 70 | ASSERT_TRUE(buffer.empty()); 71 | ASSERT_EQ(value, '6'); 72 | } 73 | 74 | TEST(static_buffer, write) { 75 | hexi::static_buffer buffer; 76 | const std::array values { 1, 2, 3, 4, 5, 6 }; 77 | buffer.write(values.data(), values.size()); 78 | ASSERT_EQ(buffer.size(), values.size()); 79 | ASSERT_TRUE(std::ranges::equal(values, buffer)); 80 | } 81 | 82 | TEST(static_buffer, can_write_seek) { 83 | hexi::static_buffer buffer; 84 | ASSERT_TRUE(buffer.can_write_seek()); 85 | } 86 | 87 | TEST(static_buffer, write_seek_back) { 88 | hexi::static_buffer buffer { '1', '2', '3' }; 89 | std::array values { '5', '6' }; 90 | buffer.write_seek(hexi::buffer_seek::sk_backward, 2); 91 | buffer.write(values.data(), values.size()); 92 | std::array expected { '1', '5', '6' }; 93 | ASSERT_TRUE(std::ranges::equal(expected, buffer)); 94 | } 95 | 96 | TEST(static_buffer, write_seek_start) { 97 | hexi::static_buffer buffer { '1', '2', '3' }; 98 | std::array values { '4', '5', '6' }; 99 | buffer.write_seek(hexi::buffer_seek::sk_absolute, 0); 100 | buffer.write(values.data(), values.size()); 101 | ASSERT_EQ(buffer.size(), values.size()); 102 | ASSERT_TRUE(std::ranges::equal(buffer, values)); 103 | } 104 | 105 | TEST(static_buffer, read_ptr) { 106 | hexi::static_buffer buffer { '1', '2', '3' }; 107 | auto ptr = buffer.read_ptr(); 108 | ASSERT_EQ(*ptr, buffer[0]); 109 | buffer.skip(1); 110 | ptr = buffer.read_ptr(); 111 | ASSERT_EQ(*ptr, buffer[0]); 112 | buffer.skip(1); 113 | ptr = buffer.read_ptr(); 114 | ASSERT_EQ(*ptr, buffer[0]); 115 | } 116 | 117 | TEST(static_buffer, subscript) { 118 | hexi::static_buffer buffer { '1', '2', '3' }; 119 | ASSERT_EQ(buffer[0], '1'); 120 | ASSERT_EQ(buffer[1], '2'); 121 | ASSERT_EQ(buffer[2], '3'); 122 | buffer[0] = '4'; 123 | ASSERT_EQ(buffer[0], '4'); 124 | buffer[0] = '5'; 125 | ASSERT_EQ(buffer[0], '5'); 126 | ASSERT_EQ(buffer[0], buffer[0]); 127 | } 128 | 129 | TEST(static_buffer, find_first_of) { 130 | hexi::static_buffer buffer; 131 | const auto str = "The quick brown fox jumped over the lazy dog"sv; 132 | buffer.write(str.data(), str.size()); 133 | auto pos = buffer.find_first_of('\0'); 134 | ASSERT_EQ(pos, buffer.npos); // direct string write is not terminated 135 | pos = buffer.find_first_of('g'); 136 | ASSERT_EQ(pos, 43); 137 | pos = buffer.find_first_of('T'); 138 | ASSERT_EQ(pos, 0); 139 | pos = buffer.find_first_of('t'); 140 | ASSERT_EQ(pos, 32); 141 | } 142 | 143 | TEST(static_buffer, advance_write) { 144 | hexi::static_buffer buffer { 'a', 'b', 'c' }; 145 | buffer.write_seek(hexi::buffer_seek::sk_absolute, 0); 146 | const char val = 'd'; 147 | buffer.advance_write(1); 148 | buffer.write(&val, 1); 149 | ASSERT_EQ(buffer[1], val); 150 | } 151 | 152 | TEST(static_buffer, defragment) { 153 | hexi::static_buffer buffer { 'a', 'b', 'c' }; 154 | ASSERT_EQ(buffer.free(), 0); 155 | char value = 0; 156 | buffer.read(&value, sizeof(value)); 157 | ASSERT_EQ(value, 'a'); 158 | ASSERT_EQ(buffer.free(), 0); 159 | buffer.defragment(); 160 | ASSERT_EQ(buffer.free(), 1); 161 | buffer.copy(&value, sizeof(value)); 162 | ASSERT_EQ(value, 'b'); 163 | ASSERT_EQ(buffer[0], 'b'); 164 | ASSERT_EQ(*buffer.read_ptr(), 'b'); 165 | buffer.read(&value, sizeof(value)); 166 | ASSERT_EQ(value, 'b'); 167 | buffer.read(&value, sizeof(value)); 168 | ASSERT_EQ(value, 'c'); 169 | ASSERT_THROW(buffer.read(&value, sizeof(value)), hexi::buffer_underrun); 170 | } 171 | 172 | TEST(static_buffer, free_buff) { 173 | hexi::static_buffer buffer; 174 | ASSERT_EQ(buffer.free(), 3); 175 | char value = 0; 176 | buffer.write(&value, sizeof(value)); 177 | ASSERT_EQ(buffer.free(), 2); 178 | buffer.write(&value, sizeof(value)); 179 | ASSERT_EQ(buffer.free(), 1); 180 | buffer.write(&value, sizeof(value)); 181 | ASSERT_EQ(buffer.free(), 0); 182 | ASSERT_THROW(buffer.write(&value, sizeof(value)), hexi::buffer_overflow); 183 | } -------------------------------------------------------------------------------- /tests/tls_block_allocator.cpp: -------------------------------------------------------------------------------- 1 | // _ _ 2 | // | |__ _____ _(_) 3 | // | '_ \ / _ \ \/ / | MIT & Apache 2.0 dual licensed 4 | // | | | | __/> <| | Version 1.0 5 | // |_| |_|\___/_/\_\_| https://github.com/EmberEmu/hexi 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #define HEXI_DEBUG_ALLOCATORS 14 | #include 15 | 16 | TEST(tls_block_allocator, single_alloc) { 17 | hexi::tls_block_allocator tlsalloc; 18 | auto mem = tlsalloc.allocate(); 19 | ASSERT_EQ(tlsalloc.allocator()->storage_active_count, 1); 20 | ASSERT_EQ(tlsalloc.allocator()->new_active_count, 0); 21 | ASSERT_EQ(tlsalloc.total_allocs, 1); 22 | ASSERT_EQ(tlsalloc.total_deallocs, 0); 23 | tlsalloc.deallocate(mem); 24 | ASSERT_EQ(tlsalloc.allocator()->storage_active_count, 0); 25 | ASSERT_EQ(tlsalloc.allocator()->new_active_count, 0); 26 | ASSERT_EQ(tlsalloc.total_allocs, 1); 27 | ASSERT_EQ(tlsalloc.total_deallocs, 1); 28 | } 29 | 30 | TEST(tls_block_allocator, random_allocs) { 31 | const auto max_allocs = 100u; 32 | hexi::tls_block_allocator tlsalloc; 33 | std::array chunks{}; 34 | const auto time = std::chrono::system_clock::now().time_since_epoch(); 35 | const unsigned int seed = static_cast(time.count()); 36 | std::srand(seed); 37 | const auto allocs = std::rand() % max_allocs; 38 | const auto tls_total_alloc = tlsalloc.allocator()->total_allocs; 39 | const auto tls_total_dealloc = tlsalloc.allocator()->total_deallocs; 40 | 41 | for(std::size_t i = 0u; i < allocs; ++i) { 42 | auto mem = tlsalloc.allocate(); 43 | chunks[i] = mem; 44 | } 45 | 46 | ASSERT_EQ(tlsalloc.total_allocs, allocs); 47 | ASSERT_EQ(tlsalloc.active_allocs, allocs); 48 | ASSERT_EQ(tlsalloc.total_deallocs, 0); 49 | ASSERT_EQ(tlsalloc.allocator()->total_allocs, tls_total_alloc + allocs); 50 | ASSERT_EQ(tlsalloc.allocator()->total_deallocs, tls_total_dealloc); 51 | 52 | for(std::size_t i = 0u; i < allocs; ++i) { 53 | tlsalloc.deallocate(chunks[i]); 54 | } 55 | 56 | ASSERT_EQ(tlsalloc.total_allocs, allocs); 57 | ASSERT_EQ(tlsalloc.active_allocs, 0); 58 | ASSERT_EQ(tlsalloc.total_deallocs, allocs); 59 | ASSERT_EQ(tlsalloc.allocator()->total_allocs, tls_total_alloc + allocs); 60 | ASSERT_EQ(tlsalloc.allocator()->total_deallocs, tls_total_dealloc + allocs); 61 | } 62 | 63 | TEST(tls_block_allocator, over_capacity) { 64 | hexi::tls_block_allocator tlsalloc; 65 | std::array mem{}; 66 | mem[0] = tlsalloc.allocate(); 67 | mem[1] = tlsalloc.allocate(); 68 | ASSERT_EQ(tlsalloc.allocator()->storage_active_count, 1); 69 | ASSERT_EQ(tlsalloc.allocator()->new_active_count, 1); 70 | ASSERT_EQ(tlsalloc.total_allocs, 2); 71 | ASSERT_EQ(tlsalloc.total_deallocs, 0); 72 | tlsalloc.deallocate(mem[0]); 73 | ASSERT_EQ(tlsalloc.allocator()->storage_active_count, 0); 74 | ASSERT_EQ(tlsalloc.allocator()->new_active_count, 1); 75 | ASSERT_EQ(tlsalloc.total_allocs, 2); 76 | ASSERT_EQ(tlsalloc.total_deallocs, 1); 77 | tlsalloc.deallocate(mem[1]); 78 | ASSERT_EQ(tlsalloc.allocator()->storage_active_count, 0); 79 | ASSERT_EQ(tlsalloc.allocator()->new_active_count, 0); 80 | ASSERT_EQ(tlsalloc.total_allocs, 2); 81 | ASSERT_EQ(tlsalloc.total_deallocs, 2); 82 | } 83 | 84 | TEST(tls_block_allocator, no_sharing) { 85 | hexi::tls_block_allocator tlsalloc; 86 | const auto tls_total_alloc = tlsalloc.allocator()->total_allocs; 87 | const auto tls_total_dealloc = tlsalloc.allocator()->total_deallocs; 88 | auto chunk = tlsalloc.allocate(); 89 | ASSERT_EQ(tlsalloc.allocator()->storage_active_count, 1); 90 | ASSERT_EQ(tlsalloc.allocator()->total_allocs, tls_total_alloc + 1); 91 | 92 | std::thread thread([&] { 93 | hexi::tls_block_allocator tlsalloc_; 94 | ASSERT_EQ(tlsalloc_.allocator()->total_allocs, 0); 95 | ASSERT_EQ(tlsalloc_.allocator()->storage_active_count, 0); 96 | auto chunk_ = tlsalloc_.allocate(); 97 | ASSERT_EQ(tlsalloc_.allocator()->storage_active_count, 1); 98 | ASSERT_EQ(tlsalloc_.allocator()->total_allocs, 1); 99 | ASSERT_EQ(tlsalloc_.allocator()->total_deallocs, 0); 100 | tlsalloc_.deallocate(chunk_); 101 | ASSERT_EQ(tlsalloc_.allocator()->total_deallocs, 1); 102 | }); 103 | 104 | thread.join(); 105 | 106 | tlsalloc.deallocate(chunk); 107 | ASSERT_EQ(tlsalloc.allocator()->total_deallocs, tls_total_dealloc + 1); 108 | } 109 | 110 | TEST(tls_block_allocator, thread_mismatch) { 111 | hexi::tls_block_allocator tlsalloc; 112 | auto chunk = tlsalloc.allocate(); 113 | 114 | std::jthread thread([&] { 115 | ASSERT_DEATH(tlsalloc.deallocate(chunk), ""); 116 | }); 117 | 118 | // needed to stop further asserts from triggering 119 | tlsalloc.deallocate(chunk); 120 | } -------------------------------------------------------------------------------- /tools/amalgamate/LICENSE.md: -------------------------------------------------------------------------------- 1 | amalgamate.py - Amalgamate C source and header files 2 | Copyright (c) 2012, Erik Edlund 3 | 4 | Redistribution and use in source and binary forms, with or without modification, 5 | are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, 8 | this list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Erik Edlund, nor the names of its contributors may 15 | be used to endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 19 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 22 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 25 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 27 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /tools/amalgamate/README.md: -------------------------------------------------------------------------------- 1 | 2 | # amalgamate.py - Amalgamate C source and header files 3 | 4 | Origin: https://bitbucket.org/erikedlund/amalgamate 5 | 6 | Mirror: https://github.com/edlund/amalgamate 7 | 8 | `amalgamate.py` aims to make it easy to use SQLite-style C source and header 9 | amalgamation in projects. 10 | 11 | For more information, please refer to: http://sqlite.org/amalgamation.html 12 | 13 | ## Here be dragons 14 | 15 | `amalgamate.py` is quite dumb, it only knows the bare minimum about C code 16 | required in order to be able to handle trivial include directives. It can 17 | produce weird results for unexpected code. 18 | 19 | Things to be aware of: 20 | 21 | `amalgamate.py` will not handle complex include directives correctly: 22 | 23 | #define HEADER_PATH "path/to/header.h" 24 | #include HEADER_PATH 25 | 26 | In the above example, `path/to/header.h` will not be included in the 27 | amalgamation (HEADER_PATH is never expanded). 28 | 29 | `amalgamate.py` makes the assumption that each source and header file which 30 | is not empty will end in a new-line character, which is not immediately 31 | preceded by a backslash character (see 5.1.1.2p1.2 of ISO C99). 32 | 33 | `amalgamate.py` should be usable with C++ code, but raw string literals from 34 | C++11 will definitely cause problems: 35 | 36 | R"delimiter(Terrible raw \ data " #include )delimiter" 37 | R"delimiter(Terrible raw \ data " escaping)delimiter" 38 | 39 | In the examples above, `amalgamate.py` will stop parsing the raw string literal 40 | when it encounters the first quotation mark, which will produce unexpected 41 | results. 42 | 43 | ## Installing amalgamate.py 44 | 45 | Python v.2.7.0 or higher is required. 46 | 47 | `amalgamate.py` can be tested and installed using the following commands: 48 | 49 | ./test.sh && sudo -k cp ./amalgamate.py /usr/local/bin/ 50 | 51 | ## Using amalgamate.py 52 | 53 | amalgamate.py [-v] -c path/to/config.json -s path/to/source/dir \ 54 | [-p path/to/prologue.(c|h)] 55 | 56 | * The `-c, --config` option should specify the path to a JSON config file which 57 | lists the source files, include paths and where to write the resulting 58 | amalgamation. Have a look at `test/source.c.json` and `test/include.h.json` 59 | to see two examples. 60 | 61 | * The `-s, --source` option should specify the path to the source directory. 62 | This is useful for supporting separate source and build directories. 63 | 64 | * The `-p, --prologue` option should specify the path to a file which will be 65 | added to the beginning of the amalgamation. It is optional. 66 | 67 | -------------------------------------------------------------------------------- /tools/amalgamate/amalgamate.json: -------------------------------------------------------------------------------- 1 | { 2 | "target": "hexi.h", 3 | "sources": [ 4 | "include/hexi/hexi.h" 5 | ], 6 | "include_paths": [ 7 | "include" 8 | ] 9 | } 10 | 11 | -------------------------------------------------------------------------------- /tools/amalgamate/amalgamate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # amalgamate.py - Amalgamate C source and header files. 4 | # Copyright (c) 2012, Erik Edlund 5 | # 6 | # Redistribution and use in source and binary forms, with or without modification, 7 | # are permitted provided that the following conditions are met: 8 | # 9 | # * Redistributions of source code must retain the above copyright notice, 10 | # this list of conditions and the following disclaimer. 11 | # 12 | # * Redistributions in binary form must reproduce the above copyright notice, 13 | # this list of conditions and the following disclaimer in the documentation 14 | # and/or other materials provided with the distribution. 15 | # 16 | # * Neither the name of Erik Edlund, nor the names of its contributors may 17 | # be used to endorse or promote products derived from this software without 18 | # specific prior written permission. 19 | # 20 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 21 | # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 22 | # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 24 | # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 | # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 26 | # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 27 | # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 29 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | 31 | from __future__ import division 32 | from __future__ import print_function 33 | from __future__ import unicode_literals 34 | 35 | import argparse 36 | import datetime 37 | import json 38 | import os 39 | import re 40 | import sys 41 | 42 | class Amalgamation(object): 43 | 44 | # Prepends self.source_path to file_path if needed. 45 | def actual_path(self, file_path): 46 | if not os.path.isabs(file_path): 47 | file_path = os.path.join(self.source_path, file_path) 48 | return file_path 49 | 50 | # Search included file_path in self.include_paths and 51 | # in source_dir if specified. 52 | def find_included_file(self, file_path, source_dir): 53 | search_dirs = self.include_paths[:] 54 | if source_dir: 55 | search_dirs.insert(0, source_dir) 56 | 57 | for search_dir in search_dirs: 58 | search_path = os.path.join(search_dir, file_path) 59 | if os.path.isfile(self.actual_path(search_path)): 60 | return search_path 61 | return None 62 | 63 | def __init__(self, args): 64 | with open(args.config, 'r') as f: 65 | config = json.loads(f.read()) 66 | for key in config: 67 | setattr(self, key, config[key]) 68 | 69 | self.verbose = args.verbose == "yes" 70 | self.prologue = args.prologue 71 | self.source_path = args.source_path 72 | self.included_files = [] 73 | 74 | # Generate the amalgamation and write it to the target file. 75 | def generate(self): 76 | amalgamation = "" 77 | 78 | if self.prologue: 79 | with open(self.prologue, 'r') as f: 80 | amalgamation += datetime.datetime.now().strftime(f.read()) 81 | 82 | if self.verbose: 83 | print("Config:") 84 | print(" target = {0}".format(self.target)) 85 | print(" working_dir = {0}".format(os.getcwd())) 86 | print(" include_paths = {0}".format(self.include_paths)) 87 | print("Creating amalgamation:") 88 | for file_path in self.sources: 89 | # Do not check the include paths while processing the source 90 | # list, all given source paths must be correct. 91 | actual_path = self.actual_path(file_path) 92 | print(" - processing \"{0}\"".format(file_path)) 93 | t = TranslationUnit(file_path, self, True) 94 | amalgamation += t.content 95 | 96 | with open(self.target, 'w') as f: 97 | f.write(amalgamation) 98 | 99 | print("...done!\n") 100 | if self.verbose: 101 | print("Files processed: {0}".format(self.sources)) 102 | print("Files included: {0}".format(self.included_files)) 103 | print("") 104 | 105 | class TranslationUnit(object): 106 | 107 | # // C++ comment. 108 | cpp_comment_pattern = re.compile(r"//.*?\n") 109 | 110 | # /* C comment. */ 111 | c_comment_pattern = re.compile(r"/\*.*?\*/", re.S) 112 | 113 | # "complex \"stri\\\ng\" value". 114 | string_pattern = re.compile("[^']" r'".*?(?<=[^\\])"', re.S) 115 | 116 | # Handle simple include directives. Support for advanced 117 | # directives where macros and defines needs to expanded is 118 | # not a concern right now. 119 | include_pattern = re.compile( 120 | r'#\s*include\s+(<|")(?P.*?)("|>)', re.S) 121 | 122 | # #pragma once 123 | pragma_once_pattern = re.compile(r'#\s*pragma\s+once', re.S) 124 | 125 | # Search for pattern in self.content, add the match to 126 | # contexts if found and update the index accordingly. 127 | def _search_content(self, index, pattern, contexts): 128 | match = pattern.search(self.content, index) 129 | if match: 130 | contexts.append(match) 131 | return match.end() 132 | return index + 2 133 | 134 | # Return all the skippable contexts, i.e., comments and strings 135 | def _find_skippable_contexts(self): 136 | # Find contexts in the content in which a found include 137 | # directive should not be processed. 138 | skippable_contexts = [] 139 | 140 | # Walk through the content char by char, and try to grab 141 | # skippable contexts using regular expressions when found. 142 | i = 1 143 | content_len = len(self.content) 144 | while i < content_len: 145 | j = i - 1 146 | current = self.content[i] 147 | previous = self.content[j] 148 | 149 | if current == '"': 150 | # String value. 151 | i = self._search_content(j, self.string_pattern, 152 | skippable_contexts) 153 | elif current == '*' and previous == '/': 154 | # C style comment. 155 | i = self._search_content(j, self.c_comment_pattern, 156 | skippable_contexts) 157 | elif current == '/' and previous == '/': 158 | # C++ style comment. 159 | i = self._search_content(j, self.cpp_comment_pattern, 160 | skippable_contexts) 161 | else: 162 | # Skip to the next char. 163 | i += 1 164 | 165 | return skippable_contexts 166 | 167 | # Returns True if the match is within list of other matches 168 | def _is_within(self, match, matches): 169 | for m in matches: 170 | if match.start() > m.start() and \ 171 | match.end() < m.end(): 172 | return True 173 | return False 174 | 175 | # Removes pragma once from content 176 | def _process_pragma_once(self): 177 | content_len = len(self.content) 178 | if content_len < len("#include "): 179 | return 0 180 | 181 | # Find contexts in the content in which a found include 182 | # directive should not be processed. 183 | skippable_contexts = self._find_skippable_contexts() 184 | 185 | pragmas = [] 186 | pragma_once_match = self.pragma_once_pattern.search(self.content) 187 | while pragma_once_match: 188 | if not self._is_within(pragma_once_match, skippable_contexts): 189 | pragmas.append(pragma_once_match) 190 | 191 | pragma_once_match = self.pragma_once_pattern.search(self.content, 192 | pragma_once_match.end()) 193 | 194 | # Handle all collected pragma once directives. 195 | prev_end = 0 196 | tmp_content = '' 197 | for pragma_match in pragmas: 198 | tmp_content += self.content[prev_end:pragma_match.start()] 199 | prev_end = pragma_match.end() 200 | tmp_content += self.content[prev_end:] 201 | self.content = tmp_content 202 | 203 | # Include all trivial #include directives into self.content. 204 | def _process_includes(self): 205 | content_len = len(self.content) 206 | if content_len < len("#include "): 207 | return 0 208 | 209 | # Find contexts in the content in which a found include 210 | # directive should not be processed. 211 | skippable_contexts = self._find_skippable_contexts() 212 | 213 | # Search for include directives in the content, collect those 214 | # which should be included into the content. 215 | includes = [] 216 | include_match = self.include_pattern.search(self.content) 217 | while include_match: 218 | if not self._is_within(include_match, skippable_contexts): 219 | include_path = include_match.group("path") 220 | search_same_dir = include_match.group(1) == '"' 221 | found_included_path = self.amalgamation.find_included_file( 222 | include_path, self.file_dir if search_same_dir else None) 223 | if found_included_path: 224 | includes.append((include_match, found_included_path)) 225 | 226 | include_match = self.include_pattern.search(self.content, 227 | include_match.end()) 228 | 229 | # Handle all collected include directives. 230 | prev_end = 0 231 | tmp_content = '' 232 | for include in includes: 233 | include_match, found_included_path = include 234 | tmp_content += self.content[prev_end:include_match.start()] 235 | tmp_content += "// {0}\n".format(include_match.group(0)) 236 | if not found_included_path in self.amalgamation.included_files: 237 | t = TranslationUnit(found_included_path, self.amalgamation, False) 238 | tmp_content += t.content 239 | prev_end = include_match.end() 240 | tmp_content += self.content[prev_end:] 241 | self.content = tmp_content 242 | 243 | return len(includes) 244 | 245 | # Make all content processing 246 | def _process(self): 247 | if not self.is_root: 248 | self._process_pragma_once() 249 | self._process_includes() 250 | 251 | def __init__(self, file_path, amalgamation, is_root): 252 | self.file_path = file_path 253 | self.file_dir = os.path.dirname(file_path) 254 | self.amalgamation = amalgamation 255 | self.is_root = is_root 256 | 257 | self.amalgamation.included_files.append(self.file_path) 258 | 259 | actual_path = self.amalgamation.actual_path(file_path) 260 | if not os.path.isfile(actual_path): 261 | raise IOError("File not found: \"{0}\"".format(file_path)) 262 | with open(actual_path, 'r') as f: 263 | self.content = f.read() 264 | self._process() 265 | 266 | def main(): 267 | description = "Amalgamate C source and header files." 268 | usage = " ".join([ 269 | "amalgamate.py", 270 | "[-v]", 271 | "-c path/to/config.json", 272 | "-s path/to/source/dir", 273 | "[-p path/to/prologue.(c|h)]" 274 | ]) 275 | argsparser = argparse.ArgumentParser( 276 | description=description, usage=usage) 277 | 278 | argsparser.add_argument("-v", "--verbose", dest="verbose", 279 | choices=["yes", "no"], metavar="", help="be verbose") 280 | 281 | argsparser.add_argument("-c", "--config", dest="config", 282 | required=True, metavar="", help="path to a JSON config file") 283 | 284 | argsparser.add_argument("-s", "--source", dest="source_path", 285 | required=True, metavar="", help="source code path") 286 | 287 | argsparser.add_argument("-p", "--prologue", dest="prologue", 288 | required=False, metavar="", help="path to a C prologue file") 289 | 290 | amalgamation = Amalgamation(argsparser.parse_args()) 291 | amalgamation.generate() 292 | 293 | if __name__ == "__main__": 294 | main() 295 | 296 | --------------------------------------------------------------------------------