├── .github ├── dependabot.yml └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── Dockerfile ├── LICENSE ├── README.md ├── deps └── premake │ ├── gsl.lua │ ├── libtomcrypt.lua │ ├── libtommath.lua │ ├── minizip.lua │ ├── rapidjson.lua │ └── zlib.lua ├── premake5.lua └── src ├── client.hpp ├── client_list.cpp ├── client_list.hpp ├── console.cpp ├── console.hpp ├── crypto_key.cpp ├── crypto_key.hpp ├── game_server.hpp ├── main.cpp ├── network ├── address.cpp ├── address.hpp ├── socket.cpp └── socket.hpp ├── network_list.hpp ├── server.cpp ├── server.hpp ├── server_base.cpp ├── server_base.hpp ├── server_list.cpp ├── server_list.hpp ├── service.hpp ├── services ├── elimination_handler.cpp ├── elimination_handler.hpp ├── getbots_command.cpp ├── getbots_command.hpp ├── getservers_command.cpp ├── getservers_command.hpp ├── heartbeat_command.cpp ├── heartbeat_command.hpp ├── info_response_command.cpp ├── info_response_command.hpp ├── kill_list.cpp ├── kill_list.hpp ├── patch_kill_list_command.cpp ├── patch_kill_list_command.hpp ├── ping_handler.cpp ├── ping_handler.hpp ├── statistics_handler.cpp └── statistics_handler.hpp ├── std_include.cpp ├── std_include.hpp └── utils ├── compression.cpp ├── compression.hpp ├── concurrency.hpp ├── cryptography.cpp ├── cryptography.hpp ├── env.cpp ├── env.hpp ├── info_string.cpp ├── info_string.hpp ├── io.cpp ├── io.hpp ├── memory.cpp ├── memory.hpp ├── parameters.cpp ├── parameters.hpp ├── string.cpp └── string.hpp /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gitsubmodule 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | open-pull-requests-limit: 10 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | tags: 8 | - '[0-9]+.[0-9]+.[0-9]+' 9 | pull_request: 10 | branches: 11 | - "**" 12 | types: [opened, synchronize, reopened] 13 | 14 | concurrency: 15 | group: ${{ github.ref }} 16 | cancel-in-progress: true 17 | 18 | env: 19 | PREMAKE_VERSION: "5.0.0-beta6" 20 | 21 | jobs: 22 | build-windows: 23 | name: Build Windows 24 | runs-on: windows-latest 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | configuration: 29 | - release 30 | compiler: 31 | - msvc 32 | - clang 33 | arch: 34 | - x86 35 | - x64 36 | - arm64 37 | include: 38 | - arch: x86 39 | platform: Win32 40 | - arch: x64 41 | platform: x64 42 | - arch: arm64 43 | platform: arm64 44 | steps: 45 | - name: Check out files 46 | uses: actions/checkout@main 47 | with: 48 | submodules: true 49 | fetch-depth: 0 50 | lfs: false 51 | 52 | - name: Add msbuild to PATH 53 | uses: microsoft/setup-msbuild@main 54 | 55 | - name: Install Premake5 56 | uses: diamante0018/setup-premake@master 57 | with: 58 | version: ${{ env.PREMAKE_VERSION }} 59 | 60 | - name: Generate project files (clang) 61 | if: matrix.compiler == 'clang' 62 | run: premake5 --cc=${{matrix.compiler}} vs2022 63 | 64 | - name: Generate project files (msvc) 65 | if: matrix.compiler == 'msvc' 66 | run: premake5 vs2022 67 | 68 | - name: Set up problem matching 69 | uses: ammaraskar/msvc-problem-matcher@master 70 | 71 | - name: Build ${{matrix.arch}} ${{matrix.configuration}} ${{matrix.compiler}} binaries 72 | run: msbuild /m /p:Configuration=${{matrix.configuration}} /p:Platform=${{matrix.platform}} build/alterware-master.sln 73 | 74 | - name: Upload ${{matrix.arch}} ${{matrix.configuration}} ${{matrix.compiler}} binaries 75 | uses: actions/upload-artifact@main 76 | with: 77 | name: windows-${{matrix.arch}}-${{matrix.configuration}}-${{matrix.compiler}} 78 | path: | 79 | build/bin/${{matrix.arch}}/${{matrix.configuration}}/alterware-master.exe 80 | build/bin/${{matrix.arch}}/${{matrix.configuration}}/alterware-master.pdb 81 | 82 | build-linux: 83 | name: Build Linux 84 | runs-on: ubuntu-latest 85 | container: ubuntu:noble 86 | strategy: 87 | fail-fast: false 88 | matrix: 89 | configuration: 90 | - release 91 | arch: 92 | - x86 93 | - amd64 94 | steps: 95 | - name: Install g++ and multilib 96 | run: | 97 | apt-get update 98 | apt-get install -y wget tar git make gcc-13 g++-13 gcc-13-multilib g++-13-multilib 99 | update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 13 100 | update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-13 13 101 | update-alternatives --set gcc /usr/bin/gcc-13 102 | update-alternatives --set g++ /usr/bin/g++-13 103 | 104 | - name: Check out files 105 | uses: actions/checkout@main 106 | with: 107 | submodules: true 108 | fetch-depth: 0 109 | lfs: false 110 | 111 | - name: Install dependencies (x86) 112 | if: matrix.arch == 'x86' 113 | run: | 114 | dpkg --add-architecture i386 115 | apt-get update 116 | apt-get install -y gcc-13-multilib g++-13-multilib 117 | 118 | - name: Install Premake5 119 | uses: diamante0018/setup-premake@master 120 | with: 121 | version: ${{ env.PREMAKE_VERSION }} 122 | 123 | - name: Install Mold 124 | uses: rui314/setup-mold@staging 125 | 126 | - name: Generate project files 127 | working-directory: ${{ github.workspace }} 128 | run: premake5 gmake 129 | 130 | - name: Set up problem matching 131 | uses: ammaraskar/gcc-problem-matcher@master 132 | 133 | - name: Build ${{matrix.arch}} ${{matrix.configuration}} binaries 134 | working-directory: ${{ github.workspace }} 135 | run: | 136 | make -C build config=${{matrix.configuration}}_${{matrix.arch}} -j$(nproc) 137 | 138 | - name: Upload ${{matrix.arch}} ${{matrix.configuration}} binaries 139 | uses: actions/upload-artifact@main 140 | with: 141 | name: linux-${{matrix.arch}}-${{matrix.configuration}} 142 | path: | 143 | build/bin/${{matrix.arch}}/${{matrix.configuration}}/alterware-master 144 | 145 | prebuild-linux-arm64: 146 | name: Prebuild Linux 147 | runs-on: ubuntu-latest 148 | steps: 149 | - name: Check out files 150 | uses: actions/checkout@main 151 | with: 152 | submodules: true 153 | fetch-depth: 0 154 | lfs: false 155 | 156 | - name: Install Premake5 157 | uses: diamante0018/setup-premake@master 158 | with: 159 | version: ${{ env.PREMAKE_VERSION }} 160 | 161 | - name: Generate project files 162 | run: premake5 --cc=clang gmake 163 | 164 | - name: Upload project files 165 | uses: actions/upload-artifact@main 166 | with: 167 | name: linux-project-files 168 | path: | 169 | build/ 170 | 171 | build-linux-arm64: 172 | name: Build Linux 173 | runs-on: ${{ matrix.os }} 174 | needs: prebuild-linux-arm64 175 | strategy: 176 | fail-fast: false 177 | matrix: 178 | configuration: 179 | - release 180 | arch: 181 | - arm64 182 | include: 183 | - arch: arm64 184 | os: ubuntu-24.04-arm 185 | steps: 186 | - name: Check out files 187 | uses: actions/checkout@main 188 | with: 189 | submodules: true 190 | fetch-depth: 0 191 | lfs: false 192 | 193 | - name: Create the build directory 194 | run: mkdir -p build 195 | 196 | - name: Download project files 197 | uses: actions/download-artifact@main 198 | with: 199 | name: linux-project-files 200 | path: build/ 201 | 202 | - name: Build ${{matrix.arch}} ${{matrix.configuration}} binaries 203 | working-directory: ${{ github.workspace }} 204 | run: | 205 | pushd build 206 | make config=${{matrix.configuration}}_${{matrix.arch}} -j$(nproc) 207 | env: 208 | CC: clang 209 | CXX: clang++ 210 | 211 | - name: Upload ${{matrix.arch}} ${{matrix.configuration}} binaries 212 | uses: actions/upload-artifact@main 213 | with: 214 | name: linux-${{matrix.arch}}-${{matrix.configuration}} 215 | path: | 216 | build/bin/${{matrix.arch}}/${{matrix.configuration}}/alterware-master 217 | 218 | build-macos: 219 | name: Build macOS 220 | runs-on: macos-latest 221 | strategy: 222 | fail-fast: false 223 | matrix: 224 | configuration: 225 | - release 226 | arch: 227 | - amd64 228 | - arm64 229 | steps: 230 | - name: Check out files 231 | uses: actions/checkout@main 232 | with: 233 | submodules: true 234 | fetch-depth: 0 235 | lfs: false 236 | 237 | - name: Install Premake5 238 | uses: diamante0018/setup-premake@master 239 | with: 240 | version: ${{ env.PREMAKE_VERSION }} 241 | 242 | - name: Generate project files 243 | run: premake5 gmake 244 | 245 | - name: Set up problem matching 246 | uses: ammaraskar/gcc-problem-matcher@master 247 | 248 | - name: Build ${{matrix.arch}} ${{matrix.configuration}} binaries 249 | run: | 250 | pushd build 251 | make config=${{matrix.configuration}}_${{matrix.arch}} -j$(sysctl -n hw.logicalcpu) 252 | 253 | - name: Upload ${{matrix.arch}} ${{matrix.configuration}} binaries 254 | uses: actions/upload-artifact@main 255 | with: 256 | name: macos-${{matrix.arch}}-${{matrix.configuration}} 257 | path: | 258 | build/bin/${{matrix.arch}}/${{matrix.configuration}}/alterware-master 259 | 260 | deploy: 261 | name: Deploy artifacts 262 | needs: [build-windows, build-linux, build-linux-arm64, build-macos] 263 | runs-on: ubuntu-latest 264 | if: github.ref_type == 'tag' 265 | steps: 266 | - name: Setup main environment 267 | run: echo "ALTERWARE_MASTER_SERVER_PATH=${{ secrets.ALTERWARE_MASTER_SERVER_SSH_PATH }}" >> $GITHUB_ENV 268 | 269 | - name: Download Release binaries 270 | uses: actions/download-artifact@main 271 | with: 272 | name: linux-amd64-release 273 | 274 | - name: Install SSH key 275 | uses: shimataro/ssh-key-action@v2.7.0 276 | with: 277 | key: ${{ secrets.ALTERWARE_MASTER_SERVER_SSH_PRIVATE_KEY }} 278 | known_hosts: 'just-a-placeholder-so-we-dont-get-errors' 279 | 280 | - name: Add known hosts 281 | run: ssh-keyscan -H ${{ secrets.ALTERWARE_MASTER_SERVER_SSH_ADDRESS }} >> ~/.ssh/known_hosts 282 | 283 | - name: Upload release binary 284 | run: rsync -avz alterware-master ${{ secrets.ALTERWARE_MASTER_SERVER_SSH_USER }}@${{ secrets.ALTERWARE_MASTER_SERVER_SSH_ADDRESS }}:${{ env.ALTERWARE_MASTER_SERVER_PATH }}/ 285 | 286 | - name: Publish changes 287 | run: ssh ${{ secrets.ALTERWARE_MASTER_SERVER_SSH_USER }}@${{ secrets.ALTERWARE_MASTER_SERVER_SSH_ADDRESS }} ${{ secrets.ALTERWARE_SSH_SERVER_PUBLISH_COMMAND }} 288 | 289 | docker: 290 | name: Create Docker Image 291 | needs: [build-windows, build-linux, build-linux-arm64, build-macos] 292 | runs-on: ubuntu-latest 293 | if: github.ref_type == 'tag' 294 | steps: 295 | - name: Check out files 296 | uses: actions/checkout@main 297 | with: 298 | sparse-checkout: | 299 | Dockerfile 300 | README.md 301 | sparse-checkout-cone-mode: false 302 | 303 | - name: Download Release binaries 304 | uses: actions/download-artifact@main 305 | 306 | - name: Compress Binaries 307 | run: | 308 | for dir in */; do 309 | if [[ $dir == *"windows"* ]]; then 310 | cd "$dir" && zip -r "../${dir%/}.zip" . && cd .. 311 | else 312 | tar -czvf "${dir%/}.tar.gz" -C "$dir" . 313 | fi 314 | done 315 | shell: bash 316 | 317 | - name: Setup QEMU 318 | uses: docker/setup-qemu-action@v3.6.0 319 | 320 | - name: Setup Docker Buildx 321 | uses: docker/setup-buildx-action@v3.10.0 322 | 323 | - name: Login to DockerHub 324 | uses: docker/login-action@v3.4.0 325 | with: 326 | username: ${{ secrets.DOCKERHUB_USER }} 327 | password: ${{ secrets.DOCKERHUB_TOKEN }} 328 | 329 | - id: meta 330 | uses: docker/metadata-action@v5.7.0 331 | with: 332 | images: | 333 | alterware/master-server 334 | tags: | 335 | ${{ github.ref_name }} 336 | latest 337 | 338 | - name: Build and Push Docker Image 339 | id: build-and-push 340 | uses: docker/build-push-action@v6.15.0 341 | with: 342 | context: . 343 | platforms: linux/amd64,linux/arm64 344 | push: true 345 | tags: ${{ steps.meta.outputs.tags }} 346 | labels: ${{ steps.meta.outputs.labels }} 347 | cache-from: type=gha 348 | cache-to: type=gha,mode=max 349 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Windows 2 | 3 | # Windows image file caches 4 | Thumbs.db 5 | ehthumbs.db 6 | 7 | # Folder config file 8 | Desktop.ini 9 | 10 | # Recycle Bin used on file shares 11 | $RECYCLE.BIN/ 12 | 13 | # Windows Installer files 14 | *.cab 15 | *.msi 16 | *.msm 17 | *.msp 18 | 19 | # Shortcuts 20 | *.lnk 21 | 22 | ### OSX 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must end with two \r 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | 45 | ### Visual Studio 46 | 47 | # User-specific files 48 | *.suo 49 | *.user 50 | *.userosscache 51 | *.sln.docstates 52 | 53 | # User-specific files (MonoDevelop/Xamarin Studio) 54 | *.userprefs 55 | 56 | # Build results 57 | build 58 | 59 | # Visual Studio 2015 cache/options directory 60 | .vs/ 61 | 62 | # MSTest test Results 63 | [Tt]est[Rr]esult*/ 64 | [Bb]uild[Ll]og.* 65 | 66 | *_i.c 67 | *_p.c 68 | *_i.h 69 | *.ilk 70 | *.meta 71 | *.obj 72 | *.pch 73 | *.pdb 74 | *.pgc 75 | *.pgd 76 | *.rsp 77 | *.sbr 78 | *.tlb 79 | *.tli 80 | *.tlh 81 | *.tmp 82 | *.tmp_proj 83 | *.log 84 | *.vspscc 85 | *.vssscc 86 | .builds 87 | *.pidb 88 | *.svclog 89 | *.scc 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | 100 | # Visual Studio profiler 101 | *.psess 102 | *.vsp 103 | *.vspx 104 | *.sap 105 | 106 | # TFS 2012 Local Workspace 107 | $tf/ 108 | 109 | # Guidance Automation Toolkit 110 | *.gpState 111 | 112 | # Visual Studio cache files 113 | # files ending in .cache can be ignored 114 | *.[Cc]ache 115 | # but keep track of directories ending in .cache 116 | !*.[Cc]ache/ 117 | 118 | # Others 119 | ~$* 120 | *~ 121 | *.dbmdl 122 | *.dbproj.schemaview 123 | *.pfx 124 | *.publishsettings 125 | 126 | # Backup & report files from converting an old project file 127 | # to a newer Visual Studio version. Backup files are not needed, 128 | # because we have git ;-) 129 | _UpgradeReport_Files/ 130 | Backup*/ 131 | UpgradeLog*.XML 132 | UpgradeLog*.htm 133 | 134 | # SQL Server files 135 | *.mdf 136 | *.ldf 137 | 138 | ### IDA 139 | *.id0 140 | *.id1 141 | *.id2 142 | *.nam 143 | *.til 144 | 145 | ### Custom user files 146 | # User scripts 147 | user*.bat 148 | 149 | # Premake binary 150 | #premake5.exe -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/libtommath"] 2 | path = deps/libtommath 3 | url = https://github.com/libtom/libtommath.git 4 | branch = develop 5 | [submodule "deps/zlib"] 6 | path = deps/zlib 7 | url = https://github.com/madler/zlib.git 8 | branch = develop 9 | [submodule "deps/GSL"] 10 | path = deps/GSL 11 | url = https://github.com/Microsoft/GSL.git 12 | [submodule "deps/rapidjson"] 13 | path = deps/rapidjson 14 | url = https://github.com/Tencent/rapidjson.git 15 | [submodule "deps/libtomcrypt"] 16 | path = deps/libtomcrypt 17 | url = https://github.com/libtom/libtomcrypt.git 18 | branch = develop 19 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:noble 2 | ARG TARGETARCH 3 | 4 | WORKDIR /master-server 5 | 6 | RUN groupadd alterware-master && useradd -r -g alterware-master alterware-master 7 | 8 | RUN mkdir -p /master-server && \ 9 | chown alterware-master:alterware-master /master-server && \ 10 | chmod 775 /master-server 11 | 12 | COPY --chown=alterware-master:alterware-master --chmod=755 ./linux-${TARGETARCH}-release/alterware-master /master-server 13 | 14 | USER alterware-master 15 | 16 | EXPOSE 20810/udp 17 | 18 | ENV AW_STATS_LOCATION="" 19 | 20 | ENTRYPOINT ["/master-server/alterware-master"] 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build](https://github.com/alterware/master-server/workflows/Build/badge.svg)](https://github.com/alterware/master-server/actions) 2 | 3 | 4 | # AlterWare: Master Server 5 | This is the master server our clients use. It is based on the DP Master Server (ID Tech) protocol 6 | 7 | ## Usage 8 | Run using [Docker][docker-link] 9 | 10 | ``` 11 | docker run -p 20810:20810/udp -e AW_STATS_LOCATION= alterware/master-server:latest 12 | ``` 13 | 14 | or run as a systemd service 15 | 16 | ``` 17 | [Unit] 18 | Description=AlterWare Master Server 19 | After=network.target 20 | 21 | [Service] 22 | Type=simple 23 | ExecStart=/alterware-master 24 | WorkingDirectory= 25 | Environment="AW_STATS_LOCATION=/var/www//html/stats.json" 26 | StandardOutput=null 27 | Restart=on-failure 28 | RestartSec=10 29 | KillMode=process 30 | User= 31 | Group= 32 | 33 | [Install] 34 | WantedBy=multi-user.target 35 | ``` 36 | 37 | ## Build 38 | - Install [Premake5][premake5-link] and add it to your system PATH 39 | - Clone this repository using [Git][git-link] 40 | - Update the submodules using ``git submodule update --init --recursive`` 41 | - Run Premake with either of these two options ``premake5 vs2022`` (Windows) or ``premake5 gmake2`` (Linux/macOS) 42 | 43 | **IMPORTANT** 44 | Requirements for Unix systems: 45 | - Compilation: Please use Clang as the preferred compiler 46 | - Dependencies: Ensure the LLVM C++ Standard library is installed 47 | - Alternative compilers: If you opt for a different compiler such as GCC, use the [Mold][mold-link] linker 48 | - Customization: Modifications to the Premake5.lua script may be required 49 | - Platform support: Details regarding supported platforms are available in [build.yml](.github/workflows/build.yml). Linux ARM64 should be supported out of the box even if it's not present in the CI scripts 50 | 51 | [docker-link]: https://www.docker.com 52 | [premake5-link]: https://premake.github.io 53 | [git-link]: https://git-scm.com 54 | [mold-link]: https://github.com/rui314/mold 55 | -------------------------------------------------------------------------------- /deps/premake/gsl.lua: -------------------------------------------------------------------------------- 1 | gsl = { 2 | source = path.join(dependencies.basePath, "GSL"), 3 | } 4 | 5 | function gsl.import() 6 | gsl.includes() 7 | end 8 | 9 | function gsl.includes() 10 | includedirs { 11 | path.join(gsl.source, "include") 12 | } 13 | end 14 | 15 | function gsl.project() 16 | 17 | end 18 | 19 | table.insert(dependencies, gsl) 20 | -------------------------------------------------------------------------------- /deps/premake/libtomcrypt.lua: -------------------------------------------------------------------------------- 1 | libtomcrypt = { 2 | source = path.join(dependencies.basePath, "libtomcrypt"), 3 | } 4 | 5 | function libtomcrypt.import() 6 | links { 7 | "libtomcrypt" 8 | } 9 | 10 | libtomcrypt.includes() 11 | end 12 | 13 | function libtomcrypt.includes() 14 | includedirs { 15 | path.join(libtomcrypt.source, "src/headers") 16 | } 17 | 18 | defines { 19 | "LTC_NO_FAST", 20 | "LTC_NO_PROTOTYPES", 21 | "LTC_NO_RSA_BLINDING", 22 | "LTC_NO_FILE", 23 | "ARGTYPE=4", 24 | } 25 | end 26 | 27 | function libtomcrypt.project() 28 | project "libtomcrypt" 29 | language "C" 30 | cdialect "C89" 31 | 32 | libtomcrypt.includes() 33 | libtommath.import() 34 | 35 | files { 36 | path.join(libtomcrypt.source, "src/**.c"), 37 | } 38 | 39 | removefiles { 40 | path.join(libtomcrypt.source, "src/**/*_test.c"), 41 | path.join(libtomcrypt.source, "src/**/*tab.c"), 42 | path.join(libtomcrypt.source, "src/encauth/ocb3/**.c"), 43 | } 44 | 45 | defines { 46 | "_CRT_SECURE_NO_WARNINGS", 47 | "LTC_SOURCE", 48 | "_LIB", 49 | "USE_LTM", 50 | "LTC_NO_TEST", 51 | "LTC_NO_PROTOTYPES" 52 | } 53 | 54 | removedefines { 55 | "_DLL", 56 | "_USRDLL", 57 | } 58 | 59 | filter { "system:macosx", "platforms:arm64" } 60 | buildoptions "-mcpu=apple-m1" 61 | filter {} 62 | 63 | filter { "system:macosx", "platforms:amd64" } 64 | buildoptions "-mcpu=x86-64" 65 | filter {} 66 | 67 | warnings "Off" 68 | kind "StaticLib" 69 | end 70 | 71 | table.insert(dependencies, libtomcrypt) 72 | -------------------------------------------------------------------------------- /deps/premake/libtommath.lua: -------------------------------------------------------------------------------- 1 | libtommath = { 2 | source = path.join(dependencies.basePath, "libtommath"), 3 | } 4 | 5 | function libtommath.import() 6 | links { 7 | "libtommath" 8 | } 9 | 10 | libtommath.includes() 11 | end 12 | 13 | function libtommath.includes() 14 | includedirs { 15 | libtommath.source 16 | } 17 | 18 | if os.istarget("windows") then 19 | defines { 20 | "MP_NO_DEV_URANDOM", 21 | } 22 | end 23 | 24 | defines { 25 | "LTM_DESC", 26 | "__STDC_IEC_559__", 27 | } 28 | end 29 | 30 | function libtommath.project() 31 | project "libtommath" 32 | language "C" 33 | cdialect "C89" 34 | 35 | libtommath.includes() 36 | 37 | files { 38 | path.join(libtommath.source, "*.c"), 39 | } 40 | 41 | defines { 42 | "_LIB" 43 | } 44 | 45 | removedefines { 46 | "_DLL", 47 | "_USRDLL" 48 | } 49 | 50 | warnings "Off" 51 | kind "StaticLib" 52 | end 53 | 54 | table.insert(dependencies, libtommath) 55 | -------------------------------------------------------------------------------- /deps/premake/minizip.lua: -------------------------------------------------------------------------------- 1 | minizip = { 2 | source = path.join(dependencies.basePath, "zlib/contrib/minizip"), 3 | } 4 | 5 | function minizip.import() 6 | links { "minizip" } 7 | zlib.import() 8 | minizip.includes() 9 | end 10 | 11 | function minizip.includes() 12 | includedirs { 13 | minizip.source 14 | } 15 | 16 | zlib.includes() 17 | end 18 | 19 | function minizip.project() 20 | project "minizip" 21 | language "C" 22 | 23 | minizip.includes() 24 | 25 | files { 26 | path.join(minizip.source, "*.h"), 27 | path.join(minizip.source, "*.c"), 28 | } 29 | 30 | filter "system:not windows" 31 | removefiles { 32 | path.join(minizip.source, "iowin32.c"), 33 | } 34 | filter {} 35 | 36 | removefiles { 37 | path.join(minizip.source, "miniunz.c"), 38 | path.join(minizip.source, "minizip.c"), 39 | } 40 | 41 | defines { 42 | "_CRT_SECURE_NO_DEPRECATE", 43 | } 44 | 45 | warnings "Off" 46 | kind "StaticLib" 47 | end 48 | 49 | table.insert(dependencies, minizip) 50 | -------------------------------------------------------------------------------- /deps/premake/rapidjson.lua: -------------------------------------------------------------------------------- 1 | rapidjson = { 2 | source = path.join(dependencies.basePath, "rapidjson"), 3 | } 4 | 5 | function rapidjson.import() 6 | defines{"RAPIDJSON_HAS_STDSTRING"} 7 | rapidjson.includes() 8 | end 9 | 10 | function rapidjson.includes() 11 | includedirs { 12 | path.join(rapidjson.source, "include"), 13 | } 14 | end 15 | 16 | function rapidjson.project() 17 | 18 | end 19 | 20 | table.insert(dependencies, rapidjson) 21 | -------------------------------------------------------------------------------- /deps/premake/zlib.lua: -------------------------------------------------------------------------------- 1 | zlib = { 2 | source = path.join(dependencies.basePath, "zlib"), 3 | } 4 | 5 | function zlib.import() 6 | links { "zlib" } 7 | zlib.includes() 8 | end 9 | 10 | function zlib.includes() 11 | includedirs { 12 | zlib.source 13 | } 14 | 15 | defines { 16 | "ZLIB_CONST", 17 | } 18 | end 19 | 20 | function zlib.project() 21 | project "zlib" 22 | language "C" 23 | cdialect "C89" 24 | 25 | zlib.includes() 26 | 27 | files { 28 | path.join(zlib.source, "*.h"), 29 | path.join(zlib.source, "*.c"), 30 | } 31 | 32 | defines { 33 | "_CRT_SECURE_NO_DEPRECATE", 34 | } 35 | 36 | warnings "Off" 37 | kind "StaticLib" 38 | end 39 | 40 | table.insert(dependencies, zlib) 41 | -------------------------------------------------------------------------------- /premake5.lua: -------------------------------------------------------------------------------- 1 | dependencies = { 2 | basePath = "./deps" 3 | } 4 | 5 | function dependencies.load() 6 | dir = path.join(dependencies.basePath, "premake/*.lua") 7 | deps = os.matchfiles(dir) 8 | 9 | for i, dep in pairs(deps) do 10 | dep = dep:gsub(".lua", "") 11 | require(dep) 12 | end 13 | end 14 | 15 | function dependencies.imports() 16 | for i, proj in pairs(dependencies) do 17 | if type(i) == 'number' then 18 | proj.import() 19 | end 20 | end 21 | end 22 | 23 | function dependencies.projects() 24 | for i, proj in pairs(dependencies) do 25 | if type(i) == 'number' then 26 | proj.project() 27 | end 28 | end 29 | end 30 | 31 | dependencies.load() 32 | 33 | workspace "alterware-master" 34 | startproject "alterware-master" 35 | location "./build" 36 | objdir "%{wks.location}/obj" 37 | targetdir "%{wks.location}/bin/%{cfg.platform}/%{cfg.buildcfg}" 38 | 39 | configurations {"debug", "release"} 40 | 41 | language "C++" 42 | cppdialect "C++20" 43 | 44 | if os.istarget("linux") then 45 | platforms {"x86", "amd64", "arm64"} 46 | elseif os.istarget("macosx") then 47 | platforms {"amd64", "arm64"} 48 | else 49 | platforms {"x86", "x64", "arm64"} 50 | end 51 | 52 | filter "platforms:x86" 53 | architecture "x86" 54 | filter {} 55 | 56 | filter "platforms:x64" 57 | architecture "x86_64" 58 | filter {} 59 | 60 | filter "platforms:amd64" 61 | architecture "x86_64" 62 | filter {} 63 | 64 | filter "platforms:arm64" 65 | architecture "ARM64" 66 | filter {} 67 | 68 | symbols "On" 69 | staticruntime "On" 70 | editandcontinue "Off" 71 | warnings "Extra" 72 | characterset "ASCII" 73 | 74 | filter { "system:linux", "system:macosx" } 75 | buildoptions "-pthread" 76 | linkoptions "-pthread" 77 | filter {} 78 | 79 | if os.istarget("linux") then 80 | filter { "toolset:clang*", "platforms:arm64" } 81 | buildoptions "--target=arm64-linux-gnu" 82 | linkoptions "--target=arm64-linux-gnu" 83 | filter {} 84 | 85 | filter { "toolset:clang*" } 86 | -- always try to use lld. LD or Gold will not work 87 | linkoptions "-fuse-ld=lld" 88 | filter {} 89 | end 90 | 91 | filter { "system:macosx", "platforms:amd64" } 92 | buildoptions "-arch x86_64" 93 | linkoptions "-arch x86_64" 94 | filter {} 95 | 96 | filter { "system:macosx", "platforms:arm64" } 97 | buildoptions "-arch arm64" 98 | linkoptions "-arch arm64" 99 | filter {} 100 | 101 | if os.getenv("CI") then 102 | defines "CI" 103 | end 104 | 105 | flags {"NoIncrementalLink", "NoMinimalRebuild", "MultiProcessorCompile", "No64BitChecks"} 106 | 107 | filter "configurations:release" 108 | optimize "Size" 109 | defines "NDEBUG" 110 | fatalwarnings {"All"} 111 | filter {} 112 | 113 | filter "configurations:debug" 114 | optimize "Debug" 115 | defines {"DEBUG", "_DEBUG"} 116 | filter {} 117 | 118 | project "alterware-master" 119 | kind "ConsoleApp" 120 | language "C++" 121 | 122 | pchheader "std_include.hpp" 123 | pchsource "src/std_include.cpp" 124 | 125 | files {"./src/**.rc", "./src/**.hpp", "./src/**.cpp"} 126 | 127 | includedirs {"./src", "%{prj.location}/src"} 128 | 129 | filter "system:windows" 130 | files { 131 | "./src/**.rc", 132 | } 133 | filter {} 134 | 135 | filter { "system:windows", "toolset:not msc*" } 136 | resincludedirs { 137 | "%{_MAIN_SCRIPT_DIR}/src" 138 | } 139 | filter {} 140 | 141 | filter { "system:windows", "toolset:msc*" } 142 | resincludedirs { 143 | "$(ProjectDir)src" 144 | } 145 | filter {} 146 | 147 | dependencies.imports() 148 | 149 | group "Dependencies" 150 | dependencies.projects() 151 | -------------------------------------------------------------------------------- /src/client.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "utils/cryptography.hpp" 4 | #include "game_server.hpp" 5 | 6 | struct client 7 | { 8 | enum class state 9 | { 10 | can_authenticate = 0, 11 | key_received, 12 | challenge_sent, 13 | }; 14 | 15 | uint64_t guid{0}; 16 | bool registered{false}; 17 | game_type game{game_type::unknown}; 18 | state state{state::can_authenticate}; 19 | utils::cryptography::ecc::key key{}; 20 | std::string challenge{}; 21 | std::string aes_key{}; 22 | std::chrono::high_resolution_clock::time_point heartbeat{}; 23 | }; 24 | -------------------------------------------------------------------------------- /src/client_list.cpp: -------------------------------------------------------------------------------- 1 | #include "std_include.hpp" 2 | #include "client_list.hpp" 3 | 4 | bool client_list::find_client(const uint64_t guid, const access_func& accessor) 5 | { 6 | auto found = false; 7 | 8 | this->iterate([&](iteration_context& context) 9 | { 10 | auto& client = context.get(); 11 | if (client.guid == guid) 12 | { 13 | accessor(client, context.get_address()); 14 | context.stop_iterating(); 15 | found = true; 16 | } 17 | }); 18 | 19 | return found; 20 | } 21 | 22 | bool client_list::find_client(const uint64_t guid, const const_access_func& accessor) const 23 | { 24 | auto found = false; 25 | 26 | this->iterate([&](const iteration_context& context) 27 | { 28 | const auto& client = context.get(); 29 | if (client.guid == guid) 30 | { 31 | accessor(client, context.get_address()); 32 | context.stop_iterating(); 33 | found = true; 34 | } 35 | }); 36 | 37 | return found; 38 | } 39 | -------------------------------------------------------------------------------- /src/client_list.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "client.hpp" 4 | #include "network_list.hpp" 5 | 6 | class client_list : public network_list 7 | { 8 | public: 9 | bool find_client(uint64_t guid, const access_func& accessor); 10 | bool find_client(uint64_t guid, const const_access_func& accessor) const; 11 | 12 | using network_list::insert; 13 | }; -------------------------------------------------------------------------------- /src/console.cpp: -------------------------------------------------------------------------------- 1 | #include "std_include.hpp" 2 | #include "console.hpp" 3 | 4 | #ifdef _WIN32 5 | #define COLOR_LOG_INFO 11//15 6 | #define COLOR_LOG_WARN 14 7 | #define COLOR_LOG_ERROR 12 8 | #define COLOR_LOG_DEBUG 15//7 9 | #else 10 | #define COLOR_LOG_INFO "\033[0;36m" 11 | #define COLOR_LOG_WARN "\033[0;33m" 12 | #define COLOR_LOG_ERROR "\033[0;31m" 13 | #define COLOR_LOG_DEBUG "\033[0m" 14 | #endif 15 | 16 | namespace console 17 | { 18 | namespace 19 | { 20 | std::mutex signal_mutex; 21 | std::function signal_callback; 22 | 23 | #ifdef _WIN32 24 | #define COLOR(win, posix) win 25 | using color_type = WORD; 26 | #else 27 | #define COLOR(win, posix) posix 28 | using color_type = const char*; 29 | #endif 30 | 31 | const color_type color_array[] = 32 | { 33 | COLOR(0x8, "\033[0;90m"), // 0 - black 34 | COLOR(0xC, "\033[0;91m"), // 1 - red 35 | COLOR(0xA, "\033[0;92m"), // 2 - green 36 | COLOR(0xE, "\033[0;93m"), // 3 - yellow 37 | COLOR(0x9, "\033[0;94m"), // 4 - blue 38 | COLOR(0xB, "\033[0;96m"), // 5 - cyan 39 | COLOR(0xD, "\033[0;95m"), // 6 - pink 40 | COLOR(0xF, "\033[0;97m"), // 7 - white 41 | }; 42 | 43 | #ifdef _WIN32 44 | BOOL WINAPI handler(const DWORD signal) 45 | { 46 | if (signal == CTRL_C_EVENT && signal_callback) 47 | { 48 | signal_callback(); 49 | } 50 | 51 | return TRUE; 52 | } 53 | 54 | #else 55 | void handler(int signal) 56 | { 57 | if (signal == SIGINT && signal_callback) 58 | { 59 | signal_callback(); 60 | } 61 | } 62 | #endif 63 | 64 | std::string format(va_list* ap, const char* message) 65 | { 66 | static thread_local char buffer[0x1000]; 67 | 68 | const int count = vsnprintf(buffer, sizeof(buffer), message, *ap); 69 | 70 | if (count < 0) return {}; 71 | return {buffer, static_cast(count)}; 72 | } 73 | 74 | #ifdef _WIN32 75 | HANDLE get_console_handle() 76 | { 77 | return GetStdHandle(STD_OUTPUT_HANDLE); 78 | } 79 | #endif 80 | 81 | void set_color(const color_type color) 82 | { 83 | #ifdef _WIN32 84 | SetConsoleTextAttribute(get_console_handle(), color); 85 | #else 86 | printf("%s", color); 87 | #endif 88 | } 89 | 90 | bool apply_color(const std::string& data, const size_t index, const color_type base_color) 91 | { 92 | if (data[index] != '^' || (index + 1) >= data.size()) 93 | { 94 | return false; 95 | } 96 | 97 | auto code = data[index + 1] - '0'; 98 | if (code < 0 || code > 11) 99 | { 100 | return false; 101 | } 102 | 103 | code = std::min(code, 7); // Everything above white is white 104 | if (code == 7) 105 | { 106 | set_color(base_color); 107 | } 108 | else 109 | { 110 | set_color(color_array[code]); 111 | } 112 | 113 | return true; 114 | } 115 | 116 | void print_colored(const std::string& line, const color_type base_color) 117 | { 118 | lock _{}; 119 | set_color(base_color); 120 | 121 | for (size_t i = 0; i < line.size(); ++i) 122 | { 123 | if (apply_color(line, i, base_color)) 124 | { 125 | ++i; 126 | continue; 127 | } 128 | 129 | putchar(line[i]); 130 | } 131 | 132 | reset_color(); 133 | } 134 | } 135 | 136 | lock::lock() 137 | { 138 | #ifdef _WIN32 139 | _lock_file(stdout); 140 | #else 141 | flockfile(stdout); 142 | #endif 143 | } 144 | 145 | lock::~lock() 146 | { 147 | #ifdef _WIN32 148 | _unlock_file(stdout); 149 | #else 150 | funlockfile(stdout); 151 | #endif 152 | } 153 | 154 | void reset_color() 155 | { 156 | lock _{}; 157 | #ifdef _WIN32 158 | SetConsoleTextAttribute(get_console_handle(), 7); 159 | #else 160 | printf("\033[0m"); 161 | #endif 162 | 163 | fflush(stdout); 164 | } 165 | 166 | void info(const char* message, ...) 167 | { 168 | va_list ap; 169 | va_start(ap, message); 170 | 171 | const auto data = format(&ap, message); 172 | print_colored("[+] " + data + "\n", COLOR_LOG_INFO); 173 | 174 | va_end(ap); 175 | } 176 | 177 | void warn(const char* message, ...) 178 | { 179 | va_list ap; 180 | va_start(ap, message); 181 | 182 | const auto data = format(&ap, message); 183 | print_colored("[!] " + data + "\n", COLOR_LOG_WARN); 184 | 185 | va_end(ap); 186 | } 187 | 188 | void error(const char* message, ...) 189 | { 190 | va_list ap; 191 | va_start(ap, message); 192 | 193 | const auto data = format(&ap, message); 194 | print_colored("[-] " + data + "\n", COLOR_LOG_ERROR); 195 | 196 | va_end(ap); 197 | } 198 | 199 | void log(const char* message, ...) 200 | { 201 | va_list ap; 202 | va_start(ap, message); 203 | 204 | const auto data = format(&ap, message); 205 | print_colored("[*] " + data + "\n", COLOR_LOG_DEBUG); 206 | 207 | va_end(ap); 208 | } 209 | 210 | void set_title(const std::string& title) 211 | { 212 | lock _{}; 213 | 214 | #ifdef _WIN32 215 | SetConsoleTitleA(title.c_str()); 216 | #else 217 | printf("\033]0;%s\007", title.c_str()); 218 | fflush(stdout); 219 | #endif 220 | } 221 | 222 | signal_handler::signal_handler(std::function callback) 223 | : std::lock_guard(signal_mutex) 224 | { 225 | signal_callback = std::move(callback); 226 | 227 | #ifdef _WIN32 228 | SetConsoleCtrlHandler(handler, TRUE); 229 | #else 230 | signal(SIGINT, handler); 231 | #endif 232 | } 233 | 234 | signal_handler::~signal_handler() 235 | { 236 | #ifdef _WIN32 237 | SetConsoleCtrlHandler(handler, FALSE); 238 | #else 239 | signal(SIGINT, SIG_DFL); 240 | #endif 241 | 242 | signal_callback = {}; 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/console.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace console 4 | { 5 | class lock 6 | { 7 | public: 8 | lock(); 9 | ~lock(); 10 | 11 | lock(lock&&) = delete; 12 | lock(const lock&) = delete; 13 | lock& operator=(lock&&) = delete; 14 | lock& operator=(const lock&) = delete; 15 | }; 16 | 17 | void reset_color(); 18 | 19 | void info(const char* message, ...); 20 | void warn(const char* message, ...); 21 | void error(const char* message, ...); 22 | void log(const char* message, ...); 23 | 24 | void set_title(const std::string& title); 25 | 26 | class signal_handler : std::lock_guard 27 | { 28 | public: 29 | signal_handler(std::function callback); 30 | ~signal_handler(); 31 | }; 32 | } 33 | -------------------------------------------------------------------------------- /src/crypto_key.cpp: -------------------------------------------------------------------------------- 1 | #include "std_include.hpp" 2 | #include "crypto_key.hpp" 3 | #include "console.hpp" 4 | 5 | #include "utils/io.hpp" 6 | 7 | namespace crypto_key 8 | { 9 | namespace 10 | { 11 | bool load_key(utils::cryptography::ecc::key& key) 12 | { 13 | std::string data{}; 14 | if (!utils::io::read_file("./private.key", &data)) 15 | { 16 | return false; 17 | } 18 | 19 | key.deserialize(data); 20 | if (!key.is_valid()) 21 | { 22 | console::warn("Loaded key is invalid!"); 23 | return false; 24 | } 25 | 26 | return true; 27 | } 28 | 29 | utils::cryptography::ecc::key generate_key() 30 | { 31 | auto key = utils::cryptography::ecc::generate_key(512); 32 | if (!key.is_valid()) 33 | { 34 | throw std::runtime_error("Failed to generate server key!"); 35 | } 36 | 37 | if (!utils::io::write_file("./private.key", key.serialize())) 38 | { 39 | console::error("Failed to write server key!"); 40 | } 41 | 42 | console::info("Generated cryptographic key: %llX", key.get_hash()); 43 | return key; 44 | } 45 | 46 | utils::cryptography::ecc::key load_or_generate_key() 47 | { 48 | utils::cryptography::ecc::key key{}; 49 | if (load_key(key)) 50 | { 51 | console::log("Loaded cryptographic key: %llX", key.get_hash()); 52 | return key; 53 | } 54 | 55 | return generate_key(); 56 | } 57 | 58 | utils::cryptography::ecc::key get_key_internal() 59 | { 60 | auto key = load_or_generate_key(); 61 | if (!utils::io::write_file("./public.key", key.get_public_key())) 62 | { 63 | console::error("Failed to write public key!"); 64 | } 65 | return key; 66 | } 67 | } 68 | 69 | const utils::cryptography::ecc::key& get() 70 | { 71 | static auto key = get_key_internal(); 72 | return key; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/crypto_key.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace crypto_key 6 | { 7 | const utils::cryptography::ecc::key& get(); 8 | } 9 | -------------------------------------------------------------------------------- /src/game_server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "utils/info_string.hpp" 4 | 5 | enum class game_type 6 | { 7 | unknown = 0, 8 | iw4, 9 | iw5, 10 | iw6, 11 | iw7, 12 | s1, 13 | t7, 14 | }; 15 | 16 | inline const std::string& resolve_game_type_name(const game_type game) 17 | { 18 | static const std::unordered_map names = 19 | { 20 | {game_type::unknown, "Unknown"}, 21 | {game_type::iw4, "IW4"}, 22 | {game_type::iw5, "IW5"}, 23 | {game_type::iw6, "IW6"}, 24 | {game_type::iw7, "IW7"}, 25 | {game_type::s1, "S1"}, 26 | {game_type::t7, "T7"}, 27 | }; 28 | 29 | return names.at(game); 30 | } 31 | 32 | inline game_type resolve_game_type(const std::string& game_name) 33 | { 34 | if (game_name == "IW4") 35 | { 36 | return game_type::iw4; 37 | } 38 | 39 | if (game_name == "IW5") 40 | { 41 | return game_type::iw5; 42 | } 43 | 44 | if (game_name == "IW6") 45 | { 46 | return game_type::iw6; 47 | } 48 | 49 | if (game_name == "IW7") 50 | { 51 | return game_type::iw7; 52 | } 53 | 54 | if (game_name == "S1") 55 | { 56 | return game_type::s1; 57 | } 58 | 59 | if (game_name == "T7") 60 | { 61 | return game_type::t7; 62 | } 63 | 64 | return game_type::unknown; 65 | } 66 | 67 | struct game_server 68 | { 69 | enum class state 70 | { 71 | can_ping = 0, 72 | needs_ping, 73 | pinged, 74 | dead, 75 | }; 76 | 77 | state state{state::can_ping}; 78 | bool registered{false}; 79 | 80 | game_type game{game_type::unknown}; 81 | int protocol{}; 82 | uint32_t clients{}; 83 | std::string name{}; 84 | std::string challenge{}; 85 | utils::info_string info_string{}; 86 | std::chrono::high_resolution_clock::time_point heartbeat{}; 87 | }; 88 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "console.hpp" 4 | #include "server.hpp" 5 | #include "crypto_key.hpp" 6 | 7 | namespace 8 | { 9 | void unsafe_main(const uint16_t port) 10 | { 11 | crypto_key::get(); 12 | 13 | console::log("Creating socket on port %hu", port); 14 | 15 | network::address a{"0.0.0.0"}; 16 | a.set_port(port); 17 | server s{a}; 18 | 19 | console::signal_handler handler([&s]() 20 | { 21 | s.stop(); 22 | }); 23 | 24 | s.run(); 25 | 26 | console::log("Terminating server..."); 27 | } 28 | } 29 | 30 | 31 | int main(const int argc, const char** argv) 32 | { 33 | console::set_title("AlterWare Master"); 34 | console::log("AlterWare Master"); 35 | 36 | try 37 | { 38 | unsafe_main(argc > 1 ? static_cast(std::strtol(argv[1], nullptr, 10)) : 20810); 39 | } 40 | catch (std::exception& e) 41 | { 42 | console::error("Fatal error: %s", e.what()); 43 | return -1; 44 | } 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /src/network/address.cpp: -------------------------------------------------------------------------------- 1 | #include "std_include.hpp" 2 | 3 | #include "network/address.hpp" 4 | 5 | namespace network 6 | { 7 | address::address() 8 | { 9 | ZeroMemory(&this->address_, sizeof(this->address_)); 10 | } 11 | 12 | address::address(const std::string& addr) 13 | : address() 14 | { 15 | this->parse(addr); 16 | } 17 | 18 | address::address(sockaddr_in& addr) 19 | { 20 | this->address_ = addr; 21 | } 22 | 23 | bool address::operator==(const address& obj) const 24 | { 25 | //return !std::memcmp(&this->address_, &obj.address_, sizeof(this->address_)); 26 | return this->address_.sin_family == obj.address_.sin_family // 27 | && this->address_.sin_addr.s_addr == obj.address_.sin_addr.s_addr // 28 | && this->address_.sin_port == obj.address_.sin_port; 29 | } 30 | 31 | void address::set_ipv4(const in_addr addr) 32 | { 33 | this->address_.sin_family = AF_INET; 34 | this->address_.sin_addr = addr; 35 | } 36 | 37 | void address::set_port(const unsigned short port) 38 | { 39 | this->address_.sin_port = htons(port); 40 | } 41 | 42 | unsigned short address::get_port() const 43 | { 44 | return ntohs(this->address_.sin_port); 45 | } 46 | 47 | std::string address::to_string(bool with_port) const 48 | { 49 | char buffer[1000] = {0}; 50 | inet_ntop(this->address_.sin_family, &this->address_.sin_addr, buffer, sizeof(buffer)); 51 | 52 | auto address = std::string(buffer); 53 | if (with_port) 54 | { 55 | address += ":"s + std::to_string(this->get_port()); 56 | } 57 | 58 | return address; 59 | } 60 | 61 | bool address::is_local() const 62 | { 63 | // According to: https://en.wikipedia.org/wiki/Private_network 64 | 65 | uint8_t bytes[4]; 66 | *reinterpret_cast(&bytes) = this->address_.sin_addr.s_addr; 67 | 68 | // 10.X.X.X 69 | if (bytes[0] == 10) 70 | { 71 | return true; 72 | } 73 | 74 | // 192.168.X.X 75 | if (bytes[0] == 192 76 | && bytes[1] == 168) 77 | { 78 | return true; 79 | } 80 | 81 | // 172.16.X.X - 172.31.X.X 82 | if (bytes[0] == 172 83 | && bytes[1] >= 16 84 | && bytes[1] < 32) 85 | { 86 | return true; 87 | } 88 | 89 | // 127.0.0.1 90 | if (this->address_.sin_addr.s_addr == 0x0100007F) 91 | { 92 | return true; 93 | } 94 | 95 | return false; 96 | } 97 | 98 | sockaddr& address::get_addr() 99 | { 100 | return reinterpret_cast(this->get_in_addr()); 101 | } 102 | 103 | const sockaddr& address::get_addr() const 104 | { 105 | return reinterpret_cast(this->get_in_addr()); 106 | } 107 | 108 | sockaddr_in& address::get_in_addr() 109 | { 110 | return this->address_; 111 | } 112 | 113 | const sockaddr_in& address::get_in_addr() const 114 | { 115 | return this->address_; 116 | } 117 | 118 | void address::parse(std::string addr) 119 | { 120 | const auto pos = addr.find_last_of(':'); 121 | if (pos != std::string::npos) 122 | { 123 | auto port = addr.substr(pos + 1); 124 | this->set_port(static_cast(atoi(port.c_str()))); 125 | 126 | addr = addr.substr(0, pos); 127 | } 128 | 129 | this->resolve(addr); 130 | } 131 | 132 | void address::resolve(const std::string& hostname) 133 | { 134 | addrinfo* result = nullptr; 135 | if (!getaddrinfo(hostname.c_str(), nullptr, nullptr, &result)) 136 | { 137 | for (auto* i = result; i; i = i->ai_next) 138 | { 139 | if (i->ai_addr->sa_family == AF_INET) 140 | { 141 | const auto port = this->get_port(); 142 | std::memcpy(&this->address_, i->ai_addr, sizeof(this->address_)); 143 | this->set_port(port); 144 | 145 | break; 146 | } 147 | } 148 | 149 | freeaddrinfo(result); 150 | } 151 | } 152 | } 153 | 154 | std::size_t std::hash::operator()(const network::address& a) const noexcept 155 | { 156 | const auto h1 = std::hash{}(a.get_in_addr().sin_family); 157 | const auto h2 = std::hash{}(a.get_in_addr().sin_addr.s_addr); 158 | const auto h3 = std::hash{}(a.get_in_addr().sin_port); 159 | return h1 ^ (h2 << 1) ^ (h3 << 2); 160 | } 161 | -------------------------------------------------------------------------------- /src/network/address.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace network 4 | { 5 | class address 6 | { 7 | public: 8 | address(); 9 | address(const std::string& addr); 10 | address(sockaddr_in& addr); 11 | 12 | void set_ipv4(in_addr addr); 13 | void set_port(unsigned short port); 14 | [[nodiscard]] unsigned short get_port() const; 15 | 16 | sockaddr& get_addr(); 17 | const sockaddr& get_addr() const; 18 | sockaddr_in& get_in_addr(); 19 | const sockaddr_in& get_in_addr() const; 20 | 21 | [[nodiscard]] bool is_local() const; 22 | [[nodiscard]] std::string to_string(bool with_port = true) const; 23 | 24 | bool operator==(const address& obj) const; 25 | 26 | bool operator!=(const address& obj) const 27 | { 28 | return !(*this == obj); 29 | } 30 | 31 | private: 32 | sockaddr_in address_{}; 33 | 34 | void parse(std::string addr); 35 | void resolve(const std::string& hostname); 36 | }; 37 | } 38 | 39 | namespace std 40 | { 41 | template <> 42 | struct hash 43 | { 44 | std::size_t operator()(const network::address& a) const noexcept; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/network/socket.cpp: -------------------------------------------------------------------------------- 1 | #include "std_include.hpp" 2 | 3 | #include "network/socket.hpp" 4 | 5 | namespace network 6 | { 7 | namespace 8 | { 9 | #ifdef _WIN32 10 | [[maybe_unused]] class wsa_initializer 11 | { 12 | public: 13 | wsa_initializer() 14 | { 15 | WSADATA wsa_data; 16 | if (WSAStartup(MAKEWORD(2, 2), &wsa_data)) 17 | { 18 | throw std::runtime_error("Unable to initialize WSA"); 19 | } 20 | } 21 | 22 | ~wsa_initializer() 23 | { 24 | WSACleanup(); 25 | } 26 | } _; 27 | #endif 28 | } 29 | 30 | socket::socket() 31 | { 32 | this->socket_ = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 33 | } 34 | 35 | socket::~socket() 36 | { 37 | if (this->socket_ != INVALID_SOCKET) 38 | { 39 | #ifdef _WIN32 40 | closesocket(this->socket_); 41 | #else 42 | close(this->socket_); 43 | #endif 44 | } 45 | } 46 | 47 | socket::socket(socket&& obj) noexcept 48 | { 49 | this->operator=(std::move(obj)); 50 | } 51 | 52 | socket& socket::operator=(socket&& obj) noexcept 53 | { 54 | if (this != &obj) 55 | { 56 | this->~socket(); 57 | this->socket_ = obj.socket_; 58 | obj.socket_ = INVALID_SOCKET; 59 | } 60 | 61 | return *this; 62 | } 63 | 64 | bool socket::bind(const address& target) 65 | { 66 | return ::bind(this->socket_, &target.get_addr(), sizeof(target.get_addr())) == 0; 67 | } 68 | 69 | void socket::send(const address& target, const std::string& data) const 70 | { 71 | sendto(this->socket_, data.data(), static_cast(data.size()), 0, &target.get_addr(), 72 | sizeof(target.get_addr())); 73 | } 74 | 75 | bool socket::receive(address& source, std::string& data) const 76 | { 77 | char buffer[0x2000]; 78 | socklen_t len = sizeof(source.get_in_addr()); 79 | 80 | const auto result = recvfrom(this->socket_, buffer, sizeof(buffer), 0, &source.get_addr(), &len); 81 | if (result == SOCKET_ERROR) // Probably WSAEWOULDBLOCK 82 | { 83 | return false; 84 | } 85 | 86 | data.assign(buffer, buffer + result); 87 | return true; 88 | } 89 | 90 | bool socket::set_blocking(const bool blocking) 91 | { 92 | #ifdef _WIN32 93 | unsigned long mode = blocking ? 0 : 1; 94 | return ioctlsocket(this->socket_, FIONBIO, &mode) == 0; 95 | #else 96 | int flags = fcntl(this->socket_, F_GETFL, 0); 97 | if (flags == -1) return false; 98 | flags = blocking ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK); 99 | return fcntl(this->socket_, F_SETFL, flags) == 0; 100 | #endif 101 | } 102 | 103 | bool socket::sleep(const std::chrono::milliseconds timeout) const 104 | { 105 | fd_set fdr; 106 | FD_ZERO(&fdr); 107 | FD_SET(this->socket_, &fdr); 108 | 109 | const auto msec = timeout.count(); 110 | 111 | timeval tv{}; 112 | tv.tv_sec = static_cast(msec / 1000ll); 113 | tv.tv_usec = static_cast((msec % 1000) * 1000); 114 | 115 | const auto retval = select(static_cast(this->socket_) + 1, &fdr, nullptr, nullptr, &tv); 116 | if (retval == SOCKET_ERROR) 117 | { 118 | std::this_thread::sleep_for(1ms); 119 | return socket_is_ready; 120 | } 121 | 122 | if (retval > 0) 123 | { 124 | return socket_is_ready; 125 | } 126 | 127 | return !socket_is_ready; 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/network/socket.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "network/address.hpp" 4 | 5 | namespace network 6 | { 7 | class socket 8 | { 9 | public: 10 | socket(); 11 | ~socket(); 12 | 13 | socket(const socket& obj) = delete; 14 | socket& operator=(const socket& obj) = delete; 15 | 16 | socket(socket&& obj) noexcept; 17 | socket& operator=(socket&& obj) noexcept; 18 | 19 | bool bind(const address& target); 20 | 21 | void send(const address& target, const std::string& data) const; 22 | bool receive(address& source, std::string& data) const; 23 | 24 | bool set_blocking(bool blocking); 25 | 26 | static const bool socket_is_ready = true; 27 | bool sleep(std::chrono::milliseconds timeout) const; 28 | 29 | private: 30 | #ifdef _WIN32 31 | using socklen_t = int; 32 | #else 33 | using SOCKET = int; 34 | #define INVALID_SOCKET (SOCKET)(~0) 35 | #define SOCKET_ERROR (-1) 36 | #endif 37 | 38 | SOCKET socket_ = INVALID_SOCKET; 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/network_list.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "console.hpp" 6 | #include "network/address.hpp" 7 | #include "utils/concurrency.hpp" 8 | 9 | template 10 | class network_list 11 | { 12 | public: 13 | class iteration_context 14 | { 15 | public: 16 | friend network_list; 17 | 18 | const network::address& get_address() const { return *this->address_; } 19 | T& get() { return *this->element_; } 20 | const T& get() const { return *this->element_; } 21 | void remove() { this->remove_ = true; } 22 | void stop_iterating() const { this->stop_iterating_ = true; } 23 | 24 | private: 25 | T* element_{}; 26 | const network::address* address_{}; 27 | bool remove_{false}; 28 | mutable bool stop_iterating_{false}; 29 | }; 30 | 31 | using iterate_func = std::function; 32 | using const_iterate_func = std::function; 33 | 34 | using access_func = std::function; 35 | using const_access_func = std::function; 36 | 37 | using insert_func = std::function; 38 | 39 | bool find(const network::address& address, const access_func& accessor) 40 | { 41 | return this->elements_.template access([&](list_type& list) 42 | { 43 | const auto i = list.find(address); 44 | if (i == list.end()) 45 | { 46 | return false; 47 | } 48 | 49 | accessor(i->second, i->first); 50 | return true; 51 | }); 52 | } 53 | 54 | bool find(const network::address& address, const const_access_func& accessor) const 55 | { 56 | return this->elements_.template access([&](const list_type& list) 57 | { 58 | const auto i = list.find(address); 59 | if (i == list.end()) 60 | { 61 | return false; 62 | } 63 | 64 | accessor(i->second, i->first); 65 | return true; 66 | }); 67 | } 68 | 69 | void iterate(const iterate_func& iterator) 70 | { 71 | this->elements_.access([&](list_type& list) 72 | { 73 | iteration_context context{}; 74 | 75 | for (auto i = list.begin(); i != list.end();) 76 | { 77 | context.element_ = &i->second; 78 | context.address_ = &i->first; 79 | context.remove_ = false; 80 | 81 | iterator(context); 82 | 83 | if (context.remove_) 84 | { 85 | i = list.erase(i); 86 | } 87 | else 88 | { 89 | ++i; 90 | } 91 | 92 | if (context.stop_iterating_) 93 | { 94 | break; 95 | } 96 | } 97 | }); 98 | } 99 | 100 | void iterate(const const_iterate_func& iterator) const 101 | { 102 | this->elements_.access([&](const list_type& list) 103 | { 104 | iteration_context context{}; 105 | 106 | for (const auto& server : list) 107 | { 108 | // const_cast is ok here 109 | context.element_ = const_cast(&server.second); 110 | context.address_ = &server.first; 111 | 112 | iterator(context); 113 | 114 | if (context.stop_iterating_) 115 | { 116 | break; 117 | } 118 | } 119 | }); 120 | } 121 | 122 | protected: 123 | void insert(const network::address& address, const insert_func& callback) 124 | { 125 | this->elements_.access([&](list_type& list) 126 | { 127 | auto entry = list.find(address); 128 | if (entry != list.end()) 129 | { 130 | callback(entry->second); 131 | return; 132 | } 133 | 134 | if (!is_insertion_allowed(list, address)) 135 | { 136 | console::log("Insertion rejected for target:\t%s", address.to_string().c_str()); 137 | return; 138 | } 139 | 140 | callback(list[address]); 141 | }); 142 | } 143 | 144 | private: 145 | using list_type = std::unordered_map; 146 | utils::concurrency::container elements_; 147 | 148 | static bool is_insertion_allowed(const list_type& list, const network::address& address) 149 | { 150 | if constexpr (IPLimit == 0) 151 | { 152 | return true; 153 | } 154 | 155 | const auto target_ip = address.get_in_addr().sin_addr.s_addr; 156 | 157 | size_t occurrences = 0; 158 | for (const auto& entry : list) 159 | { 160 | const auto entry_ip = entry.first.get_in_addr().sin_addr.s_addr; 161 | if (entry_ip == target_ip) 162 | { 163 | if (++occurrences >= IPLimit) 164 | { 165 | return false; 166 | } 167 | } 168 | } 169 | 170 | return true; 171 | } 172 | }; 173 | -------------------------------------------------------------------------------- /src/server.cpp: -------------------------------------------------------------------------------- 1 | #include "std_include.hpp" 2 | #include "server.hpp" 3 | #include "service.hpp" 4 | #include "console.hpp" 5 | 6 | #include "services/getbots_command.hpp" 7 | #include "services/getservers_command.hpp" 8 | #include "services/heartbeat_command.hpp" 9 | #include "services/info_response_command.hpp" 10 | #include "services/patch_kill_list_command.hpp" 11 | #include "services/ping_handler.hpp" 12 | #include "services/elimination_handler.hpp" 13 | #include "services/statistics_handler.hpp" 14 | #include "services/kill_list.hpp" 15 | 16 | server::server(const network::address& bind_addr) 17 | : server_base(bind_addr) 18 | { 19 | this->register_service(); 20 | this->register_service(); 21 | this->register_service(); 22 | this->register_service(); 23 | this->register_service(); 24 | this->register_service(); 25 | this->register_service(); 26 | this->register_service(); 27 | this->register_service(); 28 | } 29 | 30 | server_list& server::get_server_list() 31 | { 32 | return this->server_list_; 33 | } 34 | 35 | const server_list& server::get_server_list() const 36 | { 37 | return this->server_list_; 38 | } 39 | 40 | client_list& server::get_client_list() 41 | { 42 | return this->client_list_; 43 | } 44 | 45 | const client_list& server::get_client_list() const 46 | { 47 | return this->client_list_; 48 | } 49 | 50 | void server::run_frame() 51 | { 52 | for (const auto& service : services_) 53 | { 54 | try 55 | { 56 | service->run_frame(); 57 | } 58 | catch (const service::execution_exception& e) 59 | { 60 | console::warn("Exception in service: %s", e.what()); 61 | } 62 | catch (const std::exception& e) 63 | { 64 | console::error("Fatal exception in service: %s", e.what()); 65 | } 66 | } 67 | } 68 | 69 | void server::handle_command(const network::address& target, const std::string_view& command, 70 | const std::string_view& data) 71 | { 72 | const auto handler = this->command_services_.find(std::string{command}); 73 | if (handler == this->command_services_.end()) 74 | { 75 | console::warn("Unhandled command (%s): %.*s", target.to_string().c_str(), command.size(), command.data()); 76 | return; 77 | } 78 | 79 | std::string ban_reason; 80 | if (this->get_service()->contains(target, ban_reason)) 81 | { 82 | console::log("Refused command from server %s - target is on the kill list (%s)", target.to_string().c_str(), ban_reason.c_str()); 83 | return; 84 | } 85 | 86 | #ifdef DEBUG 87 | console::log("Handling command (%s): %.*s - %.*s", target.to_string().c_str(), command.size(), command.data(), data.size(), data.data()); 88 | #endif 89 | 90 | try 91 | { 92 | handler->second->handle_command(target, data); 93 | } 94 | catch (const service::execution_exception& e) 95 | { 96 | console::warn("Exception in command %.*s (%s): %s", command.size(), command.data(), target.to_string().c_str(), e.what()); 97 | } 98 | catch (const std::exception& e) 99 | { 100 | console::error("Fatal exception in command %.*s (%s): %s", command.size(), command.data(), target.to_string().c_str(), e.what()); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/server.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "server_base.hpp" 4 | #include "server_list.hpp" 5 | #include "client_list.hpp" 6 | #include "service.hpp" 7 | 8 | class server : public server_base 9 | { 10 | public: 11 | server(const network::address& bind_addr); 12 | 13 | server_list& get_server_list(); 14 | const server_list& get_server_list() const; 15 | 16 | client_list& get_client_list(); 17 | const client_list& get_client_list() const; 18 | 19 | template 20 | T* get_service() 21 | { 22 | static_assert(std::is_base_of_v, "Type must be a service!"); 23 | 24 | for (auto& service : this->services_) 25 | { 26 | const auto& service_ref = *service; 27 | if (typeid(service_ref) == typeid(T)) 28 | { 29 | return reinterpret_cast(service.get()); 30 | } 31 | } 32 | 33 | return nullptr; 34 | } 35 | 36 | private: 37 | server_list server_list_; 38 | client_list client_list_; 39 | 40 | std::vector> services_; 41 | std::unordered_map command_services_; 42 | 43 | template 44 | void register_service(Args&&... args) 45 | { 46 | static_assert(std::is_base_of_v, "Type must be a service!"); 47 | 48 | auto service = std::make_unique(*this, std::forward(args)...); 49 | auto* const command = service->get_command(); 50 | if (command) 51 | { 52 | command_services_[command] = service.get(); 53 | } 54 | 55 | services_.emplace_back(std::move(service)); 56 | } 57 | 58 | void run_frame() override; 59 | void handle_command(const network::address& target, const std::string_view& command, 60 | const std::string_view& data) override; 61 | }; 62 | -------------------------------------------------------------------------------- /src/server_base.cpp: -------------------------------------------------------------------------------- 1 | #include "std_include.hpp" 2 | #include "server_base.hpp" 3 | #include "console.hpp" 4 | 5 | namespace 6 | { 7 | bool is_command(const std::string_view& data) 8 | { 9 | return data.size() > 4 && *reinterpret_cast(data.data()) == -1; 10 | } 11 | 12 | int find_separator(const std::string_view& data) 13 | { 14 | for (size_t i = 4; i < data.size(); ++i) 15 | { 16 | const auto& chr = data[i]; 17 | 18 | if (chr == ' ' || chr == '\n' || chr == '\0') 19 | { 20 | return static_cast(i); 21 | } 22 | } 23 | 24 | return -1; 25 | } 26 | } 27 | 28 | server_base::server_base(const network::address& bind_addr) 29 | { 30 | if (!this->socket_.bind(bind_addr)) 31 | { 32 | throw std::runtime_error("Failed to bind socket!"); 33 | } 34 | 35 | this->socket_.set_blocking(false); 36 | } 37 | 38 | void server_base::run() 39 | { 40 | this->stopped_ = false; 41 | std::thread thread = std::thread([this] 42 | { 43 | std::this_thread::sleep_for(30ms); 44 | this->run_socket(); 45 | }); 46 | 47 | while (!this->stopped_) 48 | { 49 | this->run_frame(); 50 | std::this_thread::sleep_for(100ms); 51 | } 52 | 53 | if (thread.joinable()) 54 | { 55 | thread.join(); 56 | } 57 | } 58 | 59 | void server_base::stop() 60 | { 61 | stopped_ = true; 62 | } 63 | 64 | void server_base::send(const network::address& target, const std::string& command, const std::string& data, 65 | const std::string& separator) const 66 | { 67 | this->socket_.send(target, "\xFF\xFF\xFF\xFF" + command + separator + data); 68 | } 69 | 70 | void server_base::run_socket() 71 | { 72 | while (!this->stopped_) 73 | { 74 | if (!this->receive_data()) 75 | { 76 | (void)this->socket_.sleep(100ms); 77 | } 78 | } 79 | } 80 | 81 | bool server_base::receive_data() 82 | { 83 | std::string data{}; 84 | network::address address{}; 85 | 86 | if (!this->socket_.receive(address, data)) 87 | { 88 | return false; 89 | } 90 | 91 | if (!is_command(data)) 92 | { 93 | console::warn("Received invalid data from: %s", address.to_string().c_str()); 94 | return false; 95 | } 96 | 97 | this->parse_data(address, std::string_view{data.data() + 4, data.size() - 4}); 98 | 99 | return true; 100 | } 101 | 102 | void server_base::parse_data(const network::address& target, const std::string_view& data) 103 | { 104 | const auto separator = find_separator(data); 105 | if (separator <= 0) 106 | { 107 | this->handle_command(target, data, {}); 108 | } 109 | else 110 | { 111 | this->handle_command(target, std::string_view{data.data(), static_cast(separator)}, 112 | std::string_view{data.data() + (separator + 1), data.size() - (separator + 1)}); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/server_base.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "network/socket.hpp" 4 | 5 | 6 | class server_base 7 | { 8 | public: 9 | server_base(const network::address& bind_addr); 10 | virtual ~server_base() = default; 11 | 12 | void run(); 13 | void stop(); 14 | 15 | void send(const network::address& target, const std::string& command, const std::string& data, 16 | const std::string& separator = " ") const; 17 | 18 | protected: 19 | virtual void run_frame() = 0; 20 | virtual void handle_command(const network::address& target, const std::string_view& command, 21 | const std::string_view& data) = 0; 22 | 23 | private: 24 | network::socket socket_{}; 25 | volatile bool stopped_ = false; 26 | 27 | void run_socket(); 28 | [[nodiscard]] bool receive_data(); 29 | void parse_data(const network::address& target, const std::string_view& data); 30 | }; 31 | -------------------------------------------------------------------------------- /src/server_list.cpp: -------------------------------------------------------------------------------- 1 | #include "std_include.hpp" 2 | #include "server_list.hpp" 3 | 4 | void server_list::find_registered_servers(const game_type game, const int protocol, const access_func& accessor) 5 | { 6 | this->iterate([&](iteration_context& context) 7 | { 8 | auto& server = context.get(); 9 | if (server.registered && server.game == game && server.protocol == protocol) 10 | { 11 | accessor(server, context.get_address()); 12 | } 13 | }); 14 | } 15 | 16 | void server_list::find_registered_servers(const game_type game, const int protocol, 17 | const const_access_func& accessor) const 18 | { 19 | this->iterate([&](const iteration_context& context) 20 | { 21 | const auto& server = context.get(); 22 | if (server.registered && server.game == game && server.protocol == protocol) 23 | { 24 | accessor(server, context.get_address()); 25 | } 26 | }); 27 | } 28 | 29 | void server_list::heartbeat(const network::address& address) 30 | { 31 | this->insert(address, [](game_server& server) 32 | { 33 | if (server.state == game_server::state::can_ping) 34 | { 35 | server.heartbeat = std::chrono::high_resolution_clock::now(); 36 | server.state = game_server::state::needs_ping; 37 | } 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/server_list.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "game_server.hpp" 4 | #include "network_list.hpp" 5 | 6 | class server_list : public network_list 7 | { 8 | public: 9 | void find_registered_servers(game_type game, int protocol, const access_func& accessor); 10 | void find_registered_servers(game_type game, int protocol, const const_access_func& accessor) const; 11 | 12 | void heartbeat(const network::address& address); 13 | }; 14 | -------------------------------------------------------------------------------- /src/service.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "network/address.hpp" 4 | 5 | class server; 6 | 7 | class service 8 | { 9 | public: 10 | class execution_exception : public std::runtime_error 11 | { 12 | public: 13 | using std::runtime_error::runtime_error; 14 | }; 15 | 16 | service(server& server) 17 | : server_(server) 18 | { 19 | } 20 | 21 | virtual ~service() = default; 22 | 23 | service(service&&) = delete; 24 | service(const service&) = delete; 25 | service& operator=(service&&) = delete; 26 | service& operator=(const service&) = delete; 27 | 28 | [[nodiscard]] virtual const char* get_command() const { return nullptr; } 29 | 30 | virtual void handle_command([[maybe_unused]] const network::address& target, 31 | [[maybe_unused]] const std::string_view& data) 32 | { 33 | } 34 | 35 | virtual void run_frame() 36 | { 37 | } 38 | 39 | protected: 40 | [[nodiscard]] const server& get_server() const 41 | { 42 | return server_; 43 | } 44 | 45 | server& get_server() 46 | { 47 | return server_; 48 | } 49 | 50 | private: 51 | server& server_; 52 | }; 53 | 54 | // server and service have a cycle, but fuck, it's easier that way 55 | // include guards should handle that 56 | #include "server.hpp" 57 | -------------------------------------------------------------------------------- /src/services/elimination_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "elimination_handler.hpp" 3 | 4 | #include 5 | 6 | namespace 7 | { 8 | constexpr auto T7_PROTOCOL = 7; 9 | 10 | constexpr size_t MAX_SERVERS_PER_GAME = 10; 11 | 12 | std::array bad_names = 13 | { 14 | "http", 15 | "discord.gg", 16 | }; 17 | } 18 | 19 | void elimination_handler::run_frame() 20 | { 21 | std::unordered_map> server_count; 22 | 23 | auto now = std::chrono::high_resolution_clock::now(); 24 | this->get_server().get_server_list().iterate([&](server_list::iteration_context& context) 25 | { 26 | auto& server = context.get(); 27 | const auto diff = now - server.heartbeat; 28 | 29 | if ((server.state == game_server::state::pinged && diff > 2min) || 30 | (server.state == game_server::state::can_ping && diff > 15min)) 31 | { 32 | context.remove(); 33 | return; 34 | } 35 | 36 | if (server.game == game_type::unknown) 37 | { 38 | return; 39 | } 40 | 41 | if (server.game == game_type::t7 && server.protocol < T7_PROTOCOL) 42 | { 43 | #ifdef _DEBUG 44 | console::log("Removing T7 server '%s' because they are using an outdated protocol (%i)", context.get_address().to_string().c_str(), server.protocol); 45 | #endif 46 | context.remove(); 47 | return; 48 | } 49 | 50 | ++server_count[server.game][context.get_address().to_string(false)]; 51 | if (server_count[server.game][context.get_address().to_string(false)] > MAX_SERVERS_PER_GAME) 52 | { 53 | console::log("Removing server '%s' because it exceeds MAX_SERVERS_PER_GAME", context.get_address().to_string().c_str()); 54 | context.remove(); 55 | return; 56 | } 57 | 58 | const auto name = utils::string::to_lower(server.name); 59 | for (const auto& entry : bad_names) 60 | { 61 | if (const auto pos = name.find(entry); pos != std::string::npos) 62 | { 63 | console::log("Removing server '%s' (%s) because it contains a bad name", server.name.c_str(), context.get_address().to_string().c_str()); 64 | context.remove(); 65 | return; 66 | } 67 | } 68 | }); 69 | 70 | now = std::chrono::high_resolution_clock::now(); 71 | this->get_server().get_client_list().iterate([&](client_list::iteration_context& context) 72 | { 73 | auto& client = context.get(); 74 | const auto diff = now - client.heartbeat; 75 | 76 | if (diff > 5min || (!client.registered && diff > 20s)) 77 | { 78 | context.remove(); 79 | } 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /src/services/elimination_handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../service.hpp" 4 | 5 | class elimination_handler : public service 6 | { 7 | public: 8 | using service::service; 9 | 10 | void run_frame() override; 11 | }; 12 | -------------------------------------------------------------------------------- /src/services/getbots_command.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "getbots_command.hpp" 3 | 4 | #include "../console.hpp" 5 | 6 | const char* getbots_command::get_command() const 7 | { 8 | return "getbots"; 9 | } 10 | 11 | void getbots_command::handle_command(const network::address& target, const std::string_view&) 12 | { 13 | static const std::array bot_names 14 | { 15 | "6arelyhuman", 16 | "aerosoul", 17 | "All Night", 18 | "Ash", 19 | "avail", 20 | "barbie slut", 21 | "Barbie Whore", 22 | "Bimbo Suicide", 23 | "bimbo", 24 | "Blow Up!", 25 | "Boston", 26 | "Bubbles", 27 | "Caliber", 28 | "CAPTAINJERKFACE", 29 | "Catalyst", 30 | "Cenote", 31 | "Chicago", 32 | "clang", 33 | "Cressi", 34 | "DANCE! Till We Die", 35 | "David Guetta", 36 | "Death city", 37 | "Debian", 38 | "Devil's Spring", 39 | "Diamante", 40 | "Diavolo", 41 | "Disgusting", 42 | "DKo5", 43 | "Do U Luv Me", 44 | "Do U", 45 | "Don't Like Anybody", 46 | "don't touch my hair", 47 | "drama queenz", 48 | "Dsso", 49 | "Eagle's Nest", 50 | "Eat Me", 51 | "Evan", 52 | "Eyelashes on", 53 | "feeling nothing", 54 | "Flex190", 55 | "FOURZEROTWO", 56 | "FragsAreUs", 57 | "FryTechTip", 58 | "Furry", 59 | "FutureRave", 60 | "Ginnie Springs", 61 | "Girl", 62 | "Go Crazy", 63 | "gsc-tool", 64 | "H3X1C", 65 | "Hands up!", 66 | "Hey Dj", 67 | "Higher", 68 | "homura", 69 | "Horizon - SCR", 70 | "Hypocrite", 71 | "INeedGames", 72 | "Infamous", 73 | "Inferno", 74 | "InfinityScript", 75 | "Jack Back", 76 | "JetFins", 77 | "JezuzLizard", 78 | "Joe Biden", 79 | "Joel", 80 | "King Crimson", 81 | "KiraFoxAF", 82 | "KonaFuzzButt", 83 | "Laupetin", 84 | "Level Up!", 85 | "Lifeline", 86 | "Like a G6", 87 | "llvm-project", 88 | "Loba", 89 | "Loona", 90 | "Loudward", 91 | "Louvenarde", 92 | "Mares", 93 | "momo5502", 94 | "Montreal", 95 | "MSVC", 96 | "MUPP", 97 | "mxve", 98 | "My Flaws", 99 | "Neapolis", 100 | "New York", 101 | "Nightzy", 102 | "Non-binary Jesus", 103 | "OneFourOne", 104 | "OpenAssetTools", 105 | "pgriffin69x", 106 | "Poker Face", 107 | "Portofino", 108 | "PottyMouth", 109 | "RaidMax", 110 | "ramzi1337hak0r", 111 | "RektInator", 112 | "Resxt", 113 | "rEvoRebreather", 114 | "RezTech", 115 | "Roxie", 116 | "Santi Diving", 117 | "saysupshelly", 118 | "Scarlxrd", 119 | "scene high", 120 | "SCUBAPRO", 121 | "Slay Everyday", 122 | "Sloth", 123 | "SLUT SZN", 124 | "Snake", 125 | "Sparky", 126 | "st0rm", 127 | "Stalk Me", 128 | "StarBerry", 129 | "Titanium", 130 | "Trimix", 131 | "turn it up", 132 | "TwinLeaf", 133 | "ur my heroin", 134 | "Ur Vampire", 135 | "Valkyrie", 136 | "Vore", 137 | "Wanted", 138 | "Wattson", 139 | "Wereric", 140 | "X Party", 141 | "xensik", 142 | "XOXO (Kisses Hugs)", 143 | "XXXTENSIONS", 144 | "You & Me", 145 | "Yummy", 146 | "ZoneBuilder", 147 | "ZoneTool", 148 | }; 149 | 150 | std::stringstream stream{}; 151 | for (const auto& bot : bot_names) 152 | { 153 | stream << bot << std::endl; 154 | } 155 | 156 | this->get_server().send(target, "getbotsResponse", stream.str()); 157 | console::log("Sent bot names to: %s", target.to_string().c_str()); 158 | } 159 | -------------------------------------------------------------------------------- /src/services/getbots_command.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../service.hpp" 4 | 5 | class getbots_command : public service 6 | { 7 | public: 8 | using service::service; 9 | 10 | const char* get_command() const override; 11 | void handle_command(const network::address& target, const std::string_view& data) override; 12 | }; 13 | -------------------------------------------------------------------------------- /src/services/getservers_command.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "getservers_command.hpp" 3 | 4 | #include "../console.hpp" 5 | 6 | #include 7 | 8 | namespace 9 | { 10 | struct prepared_server 11 | { 12 | uint32_t address; 13 | uint16_t port; 14 | }; 15 | } 16 | 17 | constexpr auto MTU = 1400; // Real UDP MTU is more like 1500 bytes, but we keep a little wiggle room just in case 18 | constexpr auto DPM_PROTOCOL_ADDRESS_LENGTH = sizeof prepared_server::address; 19 | constexpr auto DPM_PROTOCOL_PORT_LENGTH = sizeof prepared_server::port; 20 | 21 | static_assert(DPM_PROTOCOL_ADDRESS_LENGTH == 4); 22 | static_assert(DPM_PROTOCOL_PORT_LENGTH == 2); 23 | 24 | const char* getservers_command::get_command() const 25 | { 26 | return "getservers"; 27 | } 28 | 29 | void getservers_command::handle_command(const network::address& target, const std::string_view& data) 30 | { 31 | const utils::parameters params(data); 32 | if (params.size() < 2) 33 | { 34 | throw execution_exception("Invalid parameter count"); 35 | } 36 | 37 | const auto& game = params[0]; 38 | 39 | const auto* p = params[1].c_str(); 40 | char* end; 41 | const auto protocol = strtol(params[1].c_str(), &end, 10); 42 | if (p == end) 43 | { 44 | throw execution_exception("Invalid protocol"); 45 | } 46 | 47 | const auto game_type = resolve_game_type(game); 48 | if (game_type == game_type::unknown) 49 | { 50 | throw execution_exception("Invalid game type: " + game); 51 | } 52 | 53 | std::queue prepared_servers{}; 54 | 55 | this->get_server().get_server_list() // 56 | .find_registered_servers(game_type, protocol, 57 | [&prepared_servers](const game_server&, const network::address& address) 58 | { 59 | const auto addr = address.get_in_addr().sin_addr.s_addr; 60 | const auto port = htons(address.get_port()); 61 | 62 | prepared_servers.push({ addr, port }); 63 | }); 64 | 65 | size_t packet_count = 0; 66 | std::string response{}; 67 | 68 | while (!prepared_servers.empty()) 69 | { 70 | const auto& server = prepared_servers.front(); 71 | response.push_back('\\'); 72 | response.append(reinterpret_cast(&server.address), DPM_PROTOCOL_ADDRESS_LENGTH); 73 | response.append(reinterpret_cast(&server.port), DPM_PROTOCOL_PORT_LENGTH); 74 | prepared_servers.pop(); 75 | 76 | if (response.size() >= MTU || prepared_servers.empty()) 77 | { 78 | // Only send EOT if the queue is empty (last packet) 79 | if (prepared_servers.empty()) 80 | { 81 | response.push_back('\\'); 82 | response.append("EOT"); 83 | response.push_back('\0'); 84 | response.push_back('\0'); 85 | response.push_back('\0'); 86 | } 87 | 88 | this->get_server().send(target, "getserversResponse", response); 89 | packet_count++; 90 | 91 | response.clear(); 92 | } 93 | } 94 | 95 | console::log("Sent %zu servers in %zu parts for game %s:\t%s", prepared_servers.size(), packet_count, game.c_str(), target.to_string().c_str()); 96 | } 97 | -------------------------------------------------------------------------------- /src/services/getservers_command.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../service.hpp" 4 | 5 | class getservers_command : public service 6 | { 7 | public: 8 | using service::service; 9 | 10 | const char* get_command() const override; 11 | void handle_command(const network::address& target, const std::string_view& data) override; 12 | }; 13 | -------------------------------------------------------------------------------- /src/services/heartbeat_command.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "heartbeat_command.hpp" 3 | 4 | const char* heartbeat_command::get_command() const 5 | { 6 | return "heartbeat"; 7 | } 8 | 9 | void heartbeat_command::handle_command(const network::address& target, [[maybe_unused]] const std::string_view& data) 10 | { 11 | this->get_server().get_server_list().heartbeat(target); 12 | } 13 | -------------------------------------------------------------------------------- /src/services/heartbeat_command.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../service.hpp" 4 | 5 | class heartbeat_command : public service 6 | { 7 | public: 8 | using service::service; 9 | 10 | const char* get_command() const override; 11 | void handle_command(const network::address& target, const std::string_view& data) override; 12 | }; 13 | -------------------------------------------------------------------------------- /src/services/info_response_command.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "info_response_command.hpp" 3 | 4 | #include "../console.hpp" 5 | 6 | const char* info_response_command::get_command() const 7 | { 8 | return "infoResponse"; 9 | } 10 | 11 | void info_response_command::handle_command(const network::address& target, const std::string_view& data) 12 | { 13 | const auto found = this->get_server().get_server_list().find( 14 | target, [&data](game_server& server, const network::address& address) 15 | { 16 | utils::info_string info{data}; 17 | const auto game = info.get("gamename"); 18 | const auto challenge = info.get("challenge"); 19 | 20 | const auto game_type = resolve_game_type(game); 21 | 22 | if (game_type == game_type::unknown) 23 | { 24 | server.state = game_server::state::dead; 25 | throw execution_exception{"Invalid game type: " + game}; 26 | } 27 | 28 | if (server.state != game_server::state::pinged) 29 | { 30 | throw execution_exception{"Stray info response"}; 31 | } 32 | 33 | if (challenge != server.challenge) 34 | { 35 | throw execution_exception{"Invalid challenge"}; 36 | } 37 | 38 | const auto player_count = atoi(info.get("clients").c_str()); 39 | const auto bot_count = atoi(info.get("bots").c_str()); 40 | auto real_player_count = player_count - bot_count; 41 | real_player_count = std::clamp(real_player_count, 0, 18); 42 | 43 | server.registered = true; 44 | server.game = game_type; 45 | server.state = game_server::state::can_ping; 46 | server.protocol = atoi(info.get("protocol").c_str()); 47 | server.clients = static_cast(real_player_count); 48 | server.name = info.get("hostname"); 49 | server.heartbeat = std::chrono::high_resolution_clock::now(); 50 | server.info_string = std::move(info); 51 | 52 | console::log("Server registered for game %s (%i):\t%s\t- %s", game.c_str(), server.protocol, 53 | address.to_string().c_str(), server.name.c_str()); 54 | }); 55 | 56 | if (!found) 57 | { 58 | throw execution_exception{"infoResponse without server!"}; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/services/info_response_command.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../service.hpp" 4 | 5 | class info_response_command : public service 6 | { 7 | public: 8 | using service::service; 9 | 10 | const char* get_command() const override; 11 | void handle_command(const network::address& target, const std::string_view& data) override; 12 | }; 13 | -------------------------------------------------------------------------------- /src/services/kill_list.cpp: -------------------------------------------------------------------------------- 1 | #include "std_include.hpp" 2 | #include "kill_list.hpp" 3 | 4 | #include 5 | 6 | constexpr auto* kill_file = "./kill.txt"; 7 | 8 | kill_list::kill_list_entry::kill_list_entry(std::string ip_address, std::string reason) 9 | : ip_address_(std::move(ip_address)), reason_(std::move(reason)) 10 | { 11 | } 12 | 13 | bool kill_list::contains(const network::address& address, std::string& reason) 14 | { 15 | auto str_address = address.to_string(false); 16 | 17 | return this->entries_container_.access([&str_address, &reason](const kill_list_entries& entries) 18 | { 19 | if (const auto itr = entries.find(str_address); itr != entries.end()) 20 | { 21 | reason = itr->second.reason_; 22 | return true; 23 | } 24 | 25 | return false; 26 | }); 27 | } 28 | 29 | void kill_list::add_to_kill_list(const kill_list_entry& add) 30 | { 31 | const auto any_change = this->entries_container_.access([&add](kill_list_entries& entries) 32 | { 33 | const auto existing_entry = entries.find(add.ip_address_); 34 | if (existing_entry == entries.end() || existing_entry->second.reason_ != add.reason_) 35 | { 36 | console::info("Added %s to kill list (reason: %s)", add.ip_address_.c_str(), add.reason_.c_str()); 37 | entries[add.ip_address_] = add; 38 | return true; 39 | } 40 | 41 | return false; 42 | }); 43 | 44 | if (!any_change) 45 | { 46 | console::info("%s already in kill list, doing nothing", add.ip_address_.c_str()); 47 | return; 48 | } 49 | 50 | this->write_to_disk(); 51 | } 52 | 53 | void kill_list::remove_from_kill_list(const network::address& remove) 54 | { 55 | this->remove_from_kill_list(remove.to_string()); 56 | } 57 | 58 | void kill_list::remove_from_kill_list(const std::string& remove) 59 | { 60 | const auto any_change = this->entries_container_.access([&remove](kill_list_entries& entries) 61 | { 62 | if (entries.erase(remove)) 63 | { 64 | console::info("Removed %s from kill list", remove.c_str()); 65 | return true; 66 | } 67 | 68 | return false; 69 | }); 70 | 71 | if (!any_change) 72 | { 73 | console::info("%s not in kill list, doing nothing", remove.c_str()); 74 | return; 75 | } 76 | 77 | this->write_to_disk(); 78 | } 79 | 80 | void kill_list::reload_from_disk() 81 | { 82 | std::string contents; 83 | if (!utils::io::read_file(kill_file, &contents)) 84 | { 85 | console::info("Could not find %s, kill list will not be loaded.", kill_file); 86 | return; 87 | } 88 | 89 | std::istringstream string_stream(contents); 90 | std::string line; 91 | 92 | this->entries_container_.access([&string_stream, &line](kill_list_entries& entries) 93 | { 94 | entries.clear(); 95 | while (std::getline(string_stream, line)) 96 | { 97 | if (line[0] == '#') 98 | { 99 | // comments or ignored line 100 | continue; 101 | } 102 | 103 | std::string ip; 104 | std::string comment; 105 | 106 | const auto index = line.find(' '); 107 | if (index != std::string::npos) 108 | { 109 | ip = line.substr(0, index); 110 | comment = line.substr(index + 1); 111 | } 112 | else 113 | { 114 | ip = line; 115 | } 116 | 117 | if (ip.empty()) 118 | { 119 | continue; 120 | } 121 | 122 | // Double line breaks from windows' \r\n 123 | if (ip[ip.size() - 1] == '\r') 124 | { 125 | ip.pop_back(); 126 | } 127 | 128 | entries.emplace(ip, kill_list_entry(ip, comment)); 129 | } 130 | 131 | console::info("Loaded %zu kill list entries from %s", entries.size(), kill_file); 132 | }); 133 | } 134 | 135 | void kill_list::write_to_disk() 136 | { 137 | std::ostringstream stream; 138 | this->entries_container_.access([&stream](const kill_list_entries& entries) 139 | { 140 | for (const auto& [ip, entry] : entries) 141 | { 142 | stream << entry.ip_address_ << " " << entry.reason_ << "\n"; 143 | } 144 | 145 | if (utils::io::write_file(kill_file, stream.str(), false)) 146 | { 147 | console::info("Wrote %s to disk (%zu entries)", kill_file, entries.size()); 148 | } 149 | else 150 | { 151 | console::error("Failed to write %s!", kill_file); 152 | } 153 | }); 154 | } 155 | 156 | kill_list::kill_list(server& server) : service(server) 157 | { 158 | this->reload_from_disk(); 159 | } 160 | -------------------------------------------------------------------------------- /src/services/kill_list.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include "../service.hpp" 5 | 6 | class kill_list : public service 7 | { 8 | public: 9 | class kill_list_entry 10 | { 11 | public: 12 | kill_list_entry() = default; 13 | kill_list_entry(std::string ip_address, std::string reason); 14 | 15 | std::string ip_address_; 16 | std::string reason_; 17 | }; 18 | 19 | kill_list(server& server); 20 | 21 | bool contains(const network::address& address, std::string& reason); 22 | void add_to_kill_list(const kill_list_entry& add); 23 | void remove_from_kill_list(const network::address& remove); 24 | void remove_from_kill_list(const std::string& remove); 25 | 26 | private: 27 | using kill_list_entries = std::unordered_map; 28 | utils::concurrency::container entries_container_; 29 | 30 | void reload_from_disk(); 31 | void write_to_disk(); 32 | }; 33 | -------------------------------------------------------------------------------- /src/services/patch_kill_list_command.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "patch_kill_list_command.hpp" 3 | 4 | #include "crypto_key.hpp" 5 | #include "services/kill_list.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | const char* patch_kill_list_command::get_command() const 12 | { 13 | return "patchkill"; 14 | } 15 | 16 | // patchkill timestamp signature add/remove target_ip (ban_reason) 17 | void patch_kill_list_command::handle_command([[maybe_unused]] const network::address& target, const std::string_view& data) 18 | { 19 | const utils::parameters params(data); 20 | if (params.size() < 3) 21 | { 22 | throw execution_exception("Invalid parameter count"); 23 | } 24 | 25 | const auto supplied_timestamp = std::chrono::seconds(std::stoul(params[0])); 26 | const auto current_timestamp = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()); 27 | 28 | // Abs the duration so that the client can be ahead or behind 29 | const auto time_stretch = std::chrono::abs(current_timestamp - supplied_timestamp); 30 | 31 | // not offset by more than 5 minutes in either direction 32 | if (time_stretch > 5min) 33 | { 34 | throw execution_exception(utils::string::va("Invalid timestamp supplied - expected %llu, got %llu, which is more than 5 minutes apart", current_timestamp.count(), supplied_timestamp.count())); 35 | } 36 | 37 | const auto& signature = utils::cryptography::base64::decode(params[1]); 38 | const auto should_remove = params[2] == "remove"s; 39 | 40 | if (!should_remove && params[2] != "add"s) 41 | { 42 | throw execution_exception("Invalid parameter #2: should be 'add' or 'remove'"); 43 | } 44 | 45 | const auto supplied_reason = params.join(4); 46 | const auto& crypto_key = crypto_key::get(); 47 | const auto signature_candidate = std::to_string(supplied_timestamp.count()); 48 | 49 | if (!utils::cryptography::ecc::verify_message(crypto_key, signature_candidate, signature)) 50 | { 51 | throw execution_exception("Signature verification of the kill list patch key failed"); 52 | } 53 | 54 | const auto kill_list_service = this->get_server().get_service(); 55 | const auto& supplied_address = params[3]; 56 | 57 | if (should_remove) 58 | { 59 | kill_list_service->remove_from_kill_list(supplied_address); 60 | } 61 | else 62 | { 63 | kill_list_service->add_to_kill_list(kill_list::kill_list_entry(supplied_address, supplied_reason)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/services/patch_kill_list_command.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../service.hpp" 4 | 5 | class patch_kill_list_command : public service 6 | { 7 | public: 8 | using service::service; 9 | 10 | const char* get_command() const override; 11 | void handle_command(const network::address& target, const std::string_view& data) override; 12 | }; 13 | -------------------------------------------------------------------------------- /src/services/ping_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "ping_handler.hpp" 3 | 4 | #include 5 | 6 | void ping_handler::run_frame() 7 | { 8 | auto count = 0; 9 | this->get_server().get_server_list().iterate([&](server_list::iteration_context& context) 10 | { 11 | auto& server = context.get(); 12 | if (server.state == game_server::state::needs_ping) 13 | { 14 | server.state = game_server::state::pinged; 15 | server.challenge = utils::cryptography::random::get_challenge(); 16 | server.heartbeat = std::chrono::high_resolution_clock::now(); 17 | 18 | this->get_server().send(context.get_address(), "getinfo", server.challenge); 19 | 20 | if (20 >= ++count) 21 | { 22 | context.stop_iterating(); 23 | } 24 | } 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/services/ping_handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../service.hpp" 4 | 5 | class ping_handler : public service 6 | { 7 | public: 8 | using service::service; 9 | 10 | void run_frame() override; 11 | }; 12 | -------------------------------------------------------------------------------- /src/services/statistics_handler.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "statistics_handler.hpp" 3 | #include "../console.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | namespace 10 | { 11 | void write_stats(const std::map>>& servers, 12 | std::map& players) 13 | { 14 | rapidjson::Document root{}; 15 | root.SetObject(); 16 | 17 | for (const auto& game_servers : servers) 18 | { 19 | const auto server_count = static_cast(game_servers.second.size()); 20 | const auto player_count = players[game_servers.first]; 21 | 22 | rapidjson::Value game{}; 23 | game.SetObject(); 24 | 25 | game.AddMember("servers", server_count, root.GetAllocator()); 26 | game.AddMember("players", player_count, root.GetAllocator()); 27 | 28 | auto game_name = resolve_game_type_name(game_servers.first); 29 | game_name = utils::string::to_lower(game_name); 30 | 31 | rapidjson::Value game_name_object(game_name, root.GetAllocator()); 32 | root.AddMember(game_name_object, game, root.GetAllocator()); 33 | } 34 | 35 | rapidjson::StringBuffer root_buffer{}; 36 | rapidjson::Writer> 37 | root_writer(root_buffer); 38 | root.Accept(root_writer); 39 | 40 | std::string root_data(root_buffer.GetString(), root_buffer.GetLength()); 41 | const auto location = utils::env::get_value("AW_STATS_LOCATION"); 42 | if (location.empty()) 43 | { 44 | console::error("The environment variable 'AW_STATS_LOCATION' is not set. Please set 'AW_STATS_LOCATION' to specify the save location for 'stats.json'"); 45 | return; 46 | } 47 | 48 | console::info("Writing stats to %s", location.c_str()); 49 | 50 | utils::io::write_file(location, root_data); 51 | } 52 | } 53 | 54 | statistics_handler::statistics_handler(server& server) 55 | : service(server) 56 | , last_print(std::chrono::high_resolution_clock::now()) 57 | { 58 | } 59 | 60 | void statistics_handler::run_frame() 61 | { 62 | const auto now = std::chrono::high_resolution_clock::now(); 63 | if (now - this->last_print < 5min) 64 | { 65 | return; 66 | } 67 | 68 | std::map players{}; 69 | std::map>> servers; 70 | 71 | this->last_print = std::chrono::high_resolution_clock::now(); 72 | this->get_server().get_server_list().iterate([&servers, &players](const server_list::iteration_context& context) 73 | { 74 | const auto& server = context.get(); 75 | if (server.registered) 76 | { 77 | servers[server.game].emplace_back(server.name, context.get_address()); 78 | players[server.game] += server.clients; 79 | } 80 | }); 81 | 82 | console::lock _{}; 83 | 84 | for (const auto& game_servers : servers) 85 | { 86 | console::log("%s (%d):", resolve_game_type_name(game_servers.first).c_str(), 87 | static_cast(game_servers.second.size())); 88 | 89 | for (const auto& server : game_servers.second) 90 | { 91 | console::log("\t%s\t%s", server.second.to_string().c_str(), server.first.c_str()); 92 | } 93 | } 94 | 95 | write_stats(servers, players); 96 | } 97 | -------------------------------------------------------------------------------- /src/services/statistics_handler.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../service.hpp" 4 | 5 | class statistics_handler : public service 6 | { 7 | public: 8 | statistics_handler(server& server); 9 | 10 | void run_frame() override; 11 | 12 | private: 13 | std::chrono::high_resolution_clock::time_point last_print; 14 | }; 15 | -------------------------------------------------------------------------------- /src/std_include.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | extern "C" 4 | { 5 | int s_read_arc4random(void*, size_t) 6 | { 7 | return -1; 8 | } 9 | 10 | #if !defined(__linux__) 11 | int s_read_getrandom(void*, size_t) 12 | { 13 | return -1; 14 | } 15 | #endif 16 | 17 | #ifdef _WIN32 18 | int s_read_urandom(void*, size_t) 19 | { 20 | return -1; 21 | } 22 | 23 | int s_read_ltm_rng(void*, size_t) 24 | { 25 | return -1; 26 | } 27 | #else 28 | int s_read_wincsp(void*, size_t) 29 | { 30 | return -1; 31 | } 32 | #endif 33 | } 34 | -------------------------------------------------------------------------------- /src/std_include.hpp: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #pragma once 3 | 4 | #define WIN32_LEAN_AND_MEAN 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #else 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #define ZeroMemory(x, y) std::memset(x, 0, y) 21 | 22 | #endif 23 | 24 | // min and max is required by gdi, therefore NOMINMAX won't work 25 | #ifdef max 26 | #undef max 27 | #endif 28 | 29 | #ifdef min 30 | #undef min 31 | #endif 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | #include 51 | #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | 60 | #include 61 | #include 62 | #include 63 | #include 64 | 65 | #ifdef _WIN32 66 | 67 | #pragma comment(lib, "ntdll.lib") 68 | #pragma comment(lib, "ws2_32.lib") 69 | #pragma comment(lib, "urlmon.lib" ) 70 | #pragma comment(lib, "iphlpapi.lib") 71 | 72 | #endif 73 | 74 | using namespace std::literals; 75 | -------------------------------------------------------------------------------- /src/utils/compression.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "compression.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "io.hpp" 11 | 12 | namespace utils::compression 13 | { 14 | namespace zlib 15 | { 16 | namespace 17 | { 18 | class zlib_stream 19 | { 20 | public: 21 | zlib_stream() 22 | { 23 | memset(&stream_, 0, sizeof(stream_)); 24 | valid_ = inflateInit(&stream_) == Z_OK; 25 | } 26 | 27 | zlib_stream(zlib_stream&&) = delete; 28 | zlib_stream(const zlib_stream&) = delete; 29 | zlib_stream& operator=(zlib_stream&&) = delete; 30 | zlib_stream& operator=(const zlib_stream&) = delete; 31 | 32 | ~zlib_stream() 33 | { 34 | if (valid_) 35 | { 36 | inflateEnd(&stream_); 37 | } 38 | } 39 | 40 | z_stream& get() 41 | { 42 | return stream_; // 43 | } 44 | 45 | bool is_valid() const 46 | { 47 | return valid_; 48 | } 49 | 50 | private: 51 | bool valid_{false}; 52 | z_stream stream_{}; 53 | }; 54 | } 55 | 56 | std::string decompress(const std::string& data) 57 | { 58 | std::string buffer{}; 59 | zlib_stream stream_container{}; 60 | if (!stream_container.is_valid()) 61 | { 62 | return {}; 63 | } 64 | 65 | int ret{}; 66 | size_t offset = 0; 67 | static thread_local uint8_t dest[CHUNK] = {0}; 68 | auto& stream = stream_container.get(); 69 | 70 | do 71 | { 72 | const auto input_size = std::min(sizeof(dest), data.size() - offset); 73 | stream.avail_in = static_cast(input_size); 74 | stream.next_in = reinterpret_cast(data.data()) + offset; 75 | offset += stream.avail_in; 76 | 77 | do 78 | { 79 | stream.avail_out = sizeof(dest); 80 | stream.next_out = dest; 81 | 82 | ret = inflate(&stream, Z_NO_FLUSH); 83 | if (ret != Z_OK && ret != Z_STREAM_END) 84 | { 85 | return {}; 86 | } 87 | 88 | buffer.insert(buffer.end(), dest, dest + sizeof(dest) - stream.avail_out); 89 | } 90 | while (stream.avail_out == 0); 91 | } 92 | while (ret != Z_STREAM_END); 93 | 94 | return buffer; 95 | } 96 | 97 | std::string compress(const std::string& data) 98 | { 99 | std::string result{}; 100 | auto length = compressBound(static_cast(data.size())); 101 | result.resize(length); 102 | 103 | if (compress2(reinterpret_cast(result.data()), &length, 104 | reinterpret_cast(data.data()), static_cast(data.size()), 105 | Z_BEST_COMPRESSION) != Z_OK) 106 | { 107 | return {}; 108 | } 109 | 110 | result.resize(length); 111 | return result; 112 | } 113 | } 114 | 115 | namespace zip 116 | { 117 | namespace 118 | { 119 | bool add_file(zipFile& zip_file, const std::string& filename, const std::string& data) 120 | { 121 | const auto zip_64 = data.size() > 0xffffffff ? 1 : 0; 122 | if (ZIP_OK != zipOpenNewFileInZip64(zip_file, filename.c_str(), nullptr, nullptr, 0, nullptr, 0, nullptr, 123 | Z_DEFLATED, Z_BEST_COMPRESSION, zip_64)) 124 | { 125 | return false; 126 | } 127 | 128 | const auto _ = gsl::finally([&zip_file]() 129 | { 130 | zipCloseFileInZip(zip_file); 131 | }); 132 | 133 | return ZIP_OK == zipWriteInFileInZip(zip_file, data.data(), static_cast(data.size())); 134 | } 135 | } 136 | 137 | void archive::add(const std::string& filename, const std::string& data) 138 | { 139 | this->files_[filename] = data; 140 | } 141 | 142 | bool archive::write(const std::string& filename, const std::string& comment) 143 | { 144 | // Hack to create the directory :3 145 | io::write_file(filename, {}); 146 | io::remove_file(filename); 147 | 148 | auto* zip_file = zipOpen64(filename.c_str(), 0); 149 | if (!zip_file) 150 | { 151 | return false; 152 | } 153 | 154 | const auto _ = gsl::finally([&zip_file, &comment]() 155 | { 156 | zipClose(zip_file, comment.empty() ? nullptr : comment.c_str()); 157 | }); 158 | 159 | for (const auto& file : this->files_) 160 | { 161 | if (!add_file(zip_file, file.first, file.second)) 162 | { 163 | return false; 164 | } 165 | } 166 | 167 | return true; 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/utils/compression.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #define CHUNK 16384u 7 | 8 | namespace utils::compression 9 | { 10 | namespace zlib 11 | { 12 | std::string compress(const std::string& data); 13 | std::string decompress(const std::string& data); 14 | } 15 | 16 | namespace zip 17 | { 18 | class archive 19 | { 20 | public: 21 | void add(const std::string& filename, const std::string& data); 22 | bool write(const std::string& filename, const std::string& comment = {}); 23 | 24 | private: 25 | std::unordered_map files_; 26 | }; 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /src/utils/concurrency.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace utils::concurrency 6 | { 7 | template 8 | class container 9 | { 10 | public: 11 | template 12 | R access(F&& accessor) const 13 | { 14 | std::lock_guard _{mutex_}; 15 | return accessor(object_); 16 | } 17 | 18 | template 19 | R access(F&& accessor) 20 | { 21 | std::lock_guard _{mutex_}; 22 | return accessor(object_); 23 | } 24 | 25 | template 26 | R access_with_lock(F&& accessor) const 27 | { 28 | std::unique_lock lock{mutex_}; 29 | return accessor(object_, lock); 30 | } 31 | 32 | template 33 | R access_with_lock(F&& accessor) 34 | { 35 | std::unique_lock lock{mutex_}; 36 | return accessor(object_, lock); 37 | } 38 | 39 | T& get_raw() { return object_; } 40 | const T& get_raw() const { return object_; } 41 | 42 | private: 43 | mutable MutexType mutex_{}; 44 | T object_{}; 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/cryptography.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "string.hpp" 5 | #include "cryptography.hpp" 6 | 7 | /// http://www.opensource.apple.com/source/CommonCrypto/CommonCrypto-55010/Source/libtomcrypt/doc/libTomCryptDoc.pdf 8 | 9 | namespace utils::cryptography 10 | { 11 | namespace 12 | { 13 | struct __ 14 | { 15 | __() 16 | { 17 | ltc_mp = ltm_desc; 18 | 19 | register_cipher(&aes_desc); 20 | register_cipher(&des3_desc); 21 | 22 | register_prng(&sprng_desc); 23 | register_prng(&fortuna_desc); 24 | register_prng(&yarrow_desc); 25 | 26 | register_hash(&sha1_desc); 27 | register_hash(&sha256_desc); 28 | register_hash(&sha512_desc); 29 | } 30 | } ___; 31 | 32 | [[maybe_unused]] const char* cs(const uint8_t* data) 33 | { 34 | return reinterpret_cast(data); 35 | } 36 | 37 | [[maybe_unused]] char* cs(uint8_t* data) 38 | { 39 | return reinterpret_cast(data); 40 | } 41 | 42 | [[maybe_unused]] const uint8_t* cs(const char* data) 43 | { 44 | return reinterpret_cast(data); 45 | } 46 | 47 | [[maybe_unused]] uint8_t* cs(char* data) 48 | { 49 | return reinterpret_cast(data); 50 | } 51 | 52 | [[maybe_unused]] unsigned long ul(const size_t value) 53 | { 54 | return static_cast(value); 55 | } 56 | 57 | class prng 58 | { 59 | public: 60 | prng(const ltc_prng_descriptor& descriptor, const bool autoseed = true) 61 | : state_(std::make_unique()) 62 | , descriptor_(descriptor) 63 | { 64 | this->id_ = register_prng(&descriptor); 65 | if (this->id_ == -1) 66 | { 67 | throw std::runtime_error("PRNG "s + this->descriptor_.name + " could not be registered!"); 68 | } 69 | 70 | if (autoseed) 71 | { 72 | this->auto_seed(); 73 | } 74 | else 75 | { 76 | this->descriptor_.start(this->state_.get()); 77 | } 78 | } 79 | 80 | ~prng() 81 | { 82 | this->descriptor_.done(this->state_.get()); 83 | } 84 | 85 | prng_state* get_state() const 86 | { 87 | this->descriptor_.ready(this->state_.get()); 88 | return this->state_.get(); 89 | } 90 | 91 | int get_id() const 92 | { 93 | return this->id_; 94 | } 95 | 96 | void add_entropy(const void* data, const size_t length) const 97 | { 98 | this->descriptor_.add_entropy(static_cast(data), ul(length), this->state_.get()); 99 | } 100 | 101 | void read(void* data, const size_t length) const 102 | { 103 | this->descriptor_.read(static_cast(data), ul(length), this->get_state()); 104 | } 105 | 106 | private: 107 | int id_; 108 | std::unique_ptr state_; 109 | const ltc_prng_descriptor& descriptor_; 110 | 111 | void auto_seed() const 112 | { 113 | rng_make_prng(128, this->id_, this->state_.get(), nullptr); 114 | 115 | int i[4]; // uninitialized data 116 | auto* i_ptr = &i; 117 | this->add_entropy(&i, sizeof(i)); 118 | this->add_entropy(&i_ptr, sizeof(i_ptr)); 119 | 120 | auto t = std::time(nullptr); 121 | this->add_entropy(&t, sizeof(t)); 122 | 123 | std::random_device rd{}; 124 | for (auto j = 0; j < 4; ++j) 125 | { 126 | const auto x = rd(); 127 | this->add_entropy(&x, sizeof(x)); 128 | } 129 | } 130 | }; 131 | 132 | const prng prng_(fortuna_desc); 133 | } 134 | 135 | ecc::key::key() 136 | { 137 | ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); 138 | } 139 | 140 | ecc::key::~key() 141 | { 142 | this->free(); 143 | } 144 | 145 | ecc::key::key(key&& obj) noexcept 146 | : key() 147 | { 148 | this->operator=(std::move(obj)); 149 | } 150 | 151 | ecc::key::key(const key& obj) 152 | : key() 153 | { 154 | this->operator=(obj); 155 | } 156 | 157 | ecc::key& ecc::key::operator=(key&& obj) noexcept 158 | { 159 | if (this != &obj) 160 | { 161 | std::memmove(&this->key_storage_, &obj.key_storage_, sizeof(this->key_storage_)); 162 | ZeroMemory(&obj.key_storage_, sizeof(obj.key_storage_)); 163 | } 164 | 165 | return *this; 166 | } 167 | 168 | ecc::key& ecc::key::operator=(const key& obj) 169 | { 170 | if (this != &obj && obj.is_valid()) 171 | { 172 | this->deserialize(obj.serialize(obj.key_storage_.type)); 173 | } 174 | 175 | return *this; 176 | } 177 | 178 | bool ecc::key::is_valid() const 179 | { 180 | return (!memory::is_set(&this->key_storage_, 0, sizeof(this->key_storage_))); 181 | } 182 | 183 | ecc_key& ecc::key::get() 184 | { 185 | return this->key_storage_; 186 | } 187 | 188 | const ecc_key& ecc::key::get() const 189 | { 190 | return this->key_storage_; 191 | } 192 | 193 | std::string ecc::key::get_public_key() const 194 | { 195 | uint8_t buffer[512] = {0}; 196 | unsigned long length = sizeof(buffer); 197 | 198 | if (ecc_ansi_x963_export(&this->key_storage_, buffer, &length) == CRYPT_OK) 199 | { 200 | return std::string(cs(buffer), length); 201 | } 202 | 203 | return {}; 204 | } 205 | 206 | void ecc::key::set(const std::string& pub_key_buffer) 207 | { 208 | this->free(); 209 | 210 | if (ecc_ansi_x963_import(cs(pub_key_buffer.data()), 211 | ul(pub_key_buffer.size()), 212 | &this->key_storage_) != CRYPT_OK) 213 | { 214 | ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); 215 | } 216 | } 217 | 218 | void ecc::key::deserialize(const std::string& key) 219 | { 220 | this->free(); 221 | 222 | if (ecc_import(cs(key.data()), ul(key.size()), 223 | &this->key_storage_) != CRYPT_OK 224 | ) 225 | { 226 | ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); 227 | } 228 | } 229 | 230 | std::string ecc::key::serialize(const int type) const 231 | { 232 | uint8_t buffer[4096] = {0}; 233 | unsigned long length = sizeof(buffer); 234 | 235 | if (ecc_export(buffer, &length, type, &this->key_storage_) == CRYPT_OK) 236 | { 237 | return std::string(cs(buffer), length); 238 | } 239 | 240 | return {}; 241 | } 242 | 243 | void ecc::key::free() 244 | { 245 | if (this->is_valid()) 246 | { 247 | ecc_free(&this->key_storage_); 248 | } 249 | 250 | ZeroMemory(&this->key_storage_, sizeof(this->key_storage_)); 251 | } 252 | 253 | bool ecc::key::operator==(key& key) const 254 | { 255 | return (this->is_valid() && key.is_valid() && this->serialize(PK_PUBLIC) == key.serialize(PK_PUBLIC)); 256 | } 257 | 258 | uint64_t ecc::key::get_hash() const 259 | { 260 | const auto hash = sha1::compute(this->get_public_key()); 261 | if (hash.size() >= 8) 262 | { 263 | return *reinterpret_cast(hash.data()); 264 | } 265 | 266 | return 0; 267 | } 268 | 269 | ecc::key ecc::generate_key(const int bits) 270 | { 271 | key key; 272 | ecc_make_key(prng_.get_state(), prng_.get_id(), bits / 8, &key.get()); 273 | 274 | return key; 275 | } 276 | 277 | ecc::key ecc::generate_key(const int bits, const std::string& entropy) 278 | { 279 | key key{}; 280 | const prng yarrow(yarrow_desc, false); 281 | yarrow.add_entropy(entropy.data(), entropy.size()); 282 | 283 | ecc_make_key(yarrow.get_state(), yarrow.get_id(), bits / 8, &key.get()); 284 | 285 | return key; 286 | } 287 | 288 | std::string ecc::sign_message(const key& key, const std::string& message) 289 | { 290 | if (!key.is_valid()) return {}; 291 | 292 | uint8_t buffer[512]; 293 | unsigned long length = sizeof(buffer); 294 | 295 | const auto hash = sha512::compute(message); 296 | 297 | ecc_sign_hash(cs(hash.data()), ul(hash.size()), buffer, &length, prng_.get_state(), prng_.get_id(), 298 | &key.get()); 299 | 300 | return std::string(cs(buffer), length); 301 | } 302 | 303 | bool ecc::verify_message(const key& key, const std::string& message, const std::string& signature) 304 | { 305 | if (!key.is_valid()) return false; 306 | 307 | const auto hash = sha512::compute(message); 308 | 309 | auto result = 0; 310 | return (ecc_verify_hash(cs(signature.data()), 311 | ul(signature.size()), 312 | cs(hash.data()), 313 | ul(hash.size()), &result, 314 | &key.get()) == CRYPT_OK && result != 0); 315 | } 316 | 317 | bool ecc::encrypt(const key& key, std::string& data) 318 | { 319 | std::string out_data{}; 320 | out_data.resize(std::max(ul(data.size() * 3), ul(0x100))); 321 | 322 | auto out_len = ul(out_data.size()); 323 | auto crypt = [&]() 324 | { 325 | return ecc_encrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, 326 | prng_.get_state(), prng_.get_id(), find_hash("sha512"), &key.get()); 327 | }; 328 | 329 | auto res = crypt(); 330 | 331 | if (res == CRYPT_BUFFER_OVERFLOW) 332 | { 333 | out_data.resize(out_len); 334 | res = crypt(); 335 | } 336 | 337 | if (res != CRYPT_OK) 338 | { 339 | return false; 340 | } 341 | 342 | out_data.resize(out_len); 343 | data = std::move(out_data); 344 | return true; 345 | } 346 | 347 | bool ecc::decrypt(const key& key, std::string& data) 348 | { 349 | std::string out_data{}; 350 | out_data.resize(std::max(ul(data.size() * 3), ul(0x100))); 351 | 352 | auto out_len = ul(out_data.size()); 353 | auto crypt = [&]() 354 | { 355 | return ecc_decrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, &key.get()); 356 | }; 357 | 358 | auto res = crypt(); 359 | 360 | if (res == CRYPT_BUFFER_OVERFLOW) 361 | { 362 | out_data.resize(out_len); 363 | res = crypt(); 364 | } 365 | 366 | if (res != CRYPT_OK) 367 | { 368 | return false; 369 | } 370 | 371 | out_data.resize(out_len); 372 | data = std::move(out_data); 373 | return true; 374 | } 375 | 376 | std::string rsa::encrypt(const std::string& data, const std::string& hash, const std::string& key) 377 | { 378 | rsa_key new_key; 379 | rsa_import(cs(key.data()), ul(key.size()), &new_key); 380 | const auto _ = gsl::finally([&]() 381 | { 382 | rsa_free(&new_key); 383 | }); 384 | 385 | 386 | std::string out_data{}; 387 | out_data.resize(std::max(ul(data.size() * 3), ul(0x100))); 388 | 389 | auto out_len = ul(out_data.size()); 390 | auto crypt = [&]() 391 | { 392 | return rsa_encrypt_key(cs(data.data()), ul(data.size()), cs(out_data.data()), &out_len, cs(hash.data()), 393 | ul(hash.size()), prng_.get_state(), prng_.get_id(), find_hash("sha512"), &new_key); 394 | }; 395 | 396 | auto res = crypt(); 397 | 398 | if (res == CRYPT_BUFFER_OVERFLOW) 399 | { 400 | out_data.resize(out_len); 401 | res = crypt(); 402 | } 403 | 404 | if (res == CRYPT_OK) 405 | { 406 | out_data.resize(out_len); 407 | return out_data; 408 | } 409 | 410 | return {}; 411 | } 412 | 413 | std::string des3::encrypt(const std::string& data, const std::string& iv, const std::string& key) 414 | { 415 | std::string enc_data; 416 | enc_data.resize(data.size()); 417 | 418 | symmetric_CBC cbc; 419 | const auto des3 = find_cipher("3des"); 420 | 421 | cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast(key.size()), 0, &cbc); 422 | cbc_encrypt(cs(data.data()), cs(enc_data.data()), ul(data.size()), &cbc); 423 | cbc_done(&cbc); 424 | 425 | return enc_data; 426 | } 427 | 428 | std::string des3::decrypt(const std::string& data, const std::string& iv, const std::string& key) 429 | { 430 | std::string dec_data; 431 | dec_data.resize(data.size()); 432 | 433 | symmetric_CBC cbc; 434 | const auto des3 = find_cipher("3des"); 435 | 436 | cbc_start(des3, cs(iv.data()), cs(key.data()), static_cast(key.size()), 0, &cbc); 437 | cbc_decrypt(cs(data.data()), cs(dec_data.data()), ul(data.size()), &cbc); 438 | cbc_done(&cbc); 439 | 440 | return dec_data; 441 | } 442 | 443 | std::string tiger::compute(const std::string& data, const bool hex) 444 | { 445 | return compute(cs(data.data()), data.size(), hex); 446 | } 447 | 448 | std::string tiger::compute(const uint8_t* data, const size_t length, const bool hex) 449 | { 450 | uint8_t buffer[24] = {0}; 451 | 452 | hash_state state; 453 | tiger_init(&state); 454 | tiger_process(&state, data, ul(length)); 455 | tiger_done(&state, buffer); 456 | 457 | std::string hash(cs(buffer), sizeof(buffer)); 458 | if (!hex) return hash; 459 | 460 | return string::dump_hex(hash, {}); 461 | } 462 | 463 | std::string aes::encrypt(const std::string& data, const std::string& iv, const std::string& key) 464 | { 465 | std::string enc_data; 466 | enc_data.resize(data.size()); 467 | 468 | symmetric_CBC cbc; 469 | const auto aes = find_cipher("aes"); 470 | 471 | cbc_start(aes, cs(iv.data()), cs(key.data()), 472 | static_cast(key.size()), 0, &cbc); 473 | cbc_encrypt(cs(data.data()), 474 | cs(enc_data.data()), 475 | ul(data.size()), &cbc); 476 | cbc_done(&cbc); 477 | 478 | return enc_data; 479 | } 480 | 481 | std::string aes::decrypt(const std::string& data, const std::string& iv, const std::string& key) 482 | { 483 | std::string dec_data; 484 | dec_data.resize(data.size()); 485 | 486 | symmetric_CBC cbc; 487 | const auto aes = find_cipher("aes"); 488 | 489 | cbc_start(aes, cs(iv.data()), cs(key.data()), 490 | static_cast(key.size()), 0, &cbc); 491 | cbc_decrypt(cs(data.data()), 492 | cs(dec_data.data()), 493 | ul(data.size()), &cbc); 494 | cbc_done(&cbc); 495 | 496 | return dec_data; 497 | } 498 | 499 | std::string hmac_sha1::compute(const std::string& data, const std::string& key) 500 | { 501 | std::string buffer; 502 | buffer.resize(20); 503 | 504 | hmac_state state; 505 | hmac_init(&state, find_hash("sha1"), cs(key.data()), ul(key.size())); 506 | hmac_process(&state, cs(data.data()), static_cast(data.size())); 507 | 508 | auto out_len = ul(buffer.size()); 509 | hmac_done(&state, cs(buffer.data()), &out_len); 510 | 511 | buffer.resize(out_len); 512 | return buffer; 513 | } 514 | 515 | std::string sha1::compute(const std::string& data, const bool hex) 516 | { 517 | return compute(cs(data.data()), data.size(), hex); 518 | } 519 | 520 | std::string sha1::compute(const uint8_t* data, const size_t length, const bool hex) 521 | { 522 | uint8_t buffer[20] = {0}; 523 | 524 | hash_state state; 525 | sha1_init(&state); 526 | sha1_process(&state, data, ul(length)); 527 | sha1_done(&state, buffer); 528 | 529 | std::string hash(cs(buffer), sizeof(buffer)); 530 | if (!hex) return hash; 531 | 532 | return string::dump_hex(hash, {}); 533 | } 534 | 535 | std::string sha256::compute(const std::string& data, const bool hex) 536 | { 537 | return compute(cs(data.data()), data.size(), hex); 538 | } 539 | 540 | std::string sha256::compute(const uint8_t* data, const size_t length, const bool hex) 541 | { 542 | uint8_t buffer[32] = {0}; 543 | 544 | hash_state state; 545 | sha256_init(&state); 546 | sha256_process(&state, data, ul(length)); 547 | sha256_done(&state, buffer); 548 | 549 | std::string hash(cs(buffer), sizeof(buffer)); 550 | if (!hex) return hash; 551 | 552 | return string::dump_hex(hash, {}); 553 | } 554 | 555 | std::string sha512::compute(const std::string& data, const bool hex) 556 | { 557 | return compute(cs(data.data()), data.size(), hex); 558 | } 559 | 560 | std::string sha512::compute(const uint8_t* data, const size_t length, const bool hex) 561 | { 562 | uint8_t buffer[64] = {0}; 563 | 564 | hash_state state; 565 | sha512_init(&state); 566 | sha512_process(&state, data, ul(length)); 567 | sha512_done(&state, buffer); 568 | 569 | std::string hash(cs(buffer), sizeof(buffer)); 570 | if (!hex) return hash; 571 | 572 | return string::dump_hex(hash, {}); 573 | } 574 | 575 | std::string base64::encode(const uint8_t* data, const size_t len) 576 | { 577 | std::string result; 578 | result.resize((len + 2) * 2); 579 | 580 | auto out_len = ul(result.size()); 581 | if (base64_encode(data, ul(len), result.data(), &out_len) != CRYPT_OK) 582 | { 583 | return {}; 584 | } 585 | 586 | result.resize(out_len); 587 | return result; 588 | } 589 | 590 | std::string base64::encode(const std::string& data) 591 | { 592 | return base64::encode(cs(data.data()), static_cast(data.size())); 593 | } 594 | 595 | std::string base64::decode(const std::string& data) 596 | { 597 | std::string result; 598 | result.resize((data.size() + 2) * 2); 599 | 600 | auto out_len = ul(result.size()); 601 | if (base64_decode(data.data(), ul(data.size()), cs(result.data()), &out_len) != CRYPT_OK) 602 | { 603 | return {}; 604 | } 605 | 606 | result.resize(out_len); 607 | return result; 608 | } 609 | 610 | unsigned int jenkins_one_at_a_time::compute(const std::string& data) 611 | { 612 | return compute(data.data(), data.size()); 613 | } 614 | 615 | unsigned int jenkins_one_at_a_time::compute(const char* key, const size_t len) 616 | { 617 | unsigned int hash, i; 618 | for (hash = i = 0; i < len; ++i) 619 | { 620 | hash += key[i]; 621 | hash += (hash << 10); 622 | hash ^= (hash >> 6); 623 | } 624 | hash += (hash << 3); 625 | hash ^= (hash >> 11); 626 | hash += (hash << 15); 627 | return hash; 628 | } 629 | 630 | uint32_t random::get_integer() 631 | { 632 | uint32_t result; 633 | random::get_data(&result, sizeof(result)); 634 | return result; 635 | } 636 | 637 | std::string random::get_challenge() 638 | { 639 | std::string result; 640 | result.resize(sizeof(uint32_t)); 641 | random::get_data(result.data(), result.size()); 642 | return string::dump_hex(result, {}); 643 | } 644 | 645 | void random::get_data(void* data, const size_t size) 646 | { 647 | prng_.read(data, size); 648 | } 649 | } 650 | -------------------------------------------------------------------------------- /src/utils/cryptography.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace utils::cryptography 7 | { 8 | namespace ecc 9 | { 10 | class key final 11 | { 12 | public: 13 | key(); 14 | ~key(); 15 | 16 | key(key&& obj) noexcept; 17 | key(const key& obj); 18 | key& operator=(key&& obj) noexcept; 19 | key& operator=(const key& obj); 20 | 21 | bool is_valid() const; 22 | 23 | ecc_key& get(); 24 | const ecc_key& get() const; 25 | 26 | std::string get_public_key() const; 27 | 28 | void set(const std::string& pub_key_buffer); 29 | 30 | void deserialize(const std::string& key); 31 | 32 | std::string serialize(int type = PK_PRIVATE) const; 33 | 34 | void free(); 35 | 36 | bool operator==(key& key) const; 37 | 38 | uint64_t get_hash() const; 39 | 40 | private: 41 | ecc_key key_storage_{}; 42 | }; 43 | 44 | key generate_key(int bits); 45 | key generate_key(int bits, const std::string& entropy); 46 | std::string sign_message(const key& key, const std::string& message); 47 | bool verify_message(const key& key, const std::string& message, const std::string& signature); 48 | 49 | bool encrypt(const key& key, std::string& data); 50 | bool decrypt(const key& key, std::string& data); 51 | } 52 | 53 | namespace rsa 54 | { 55 | std::string encrypt(const std::string& data, const std::string& hash, const std::string& key); 56 | } 57 | 58 | namespace des3 59 | { 60 | std::string encrypt(const std::string& data, const std::string& iv, const std::string& key); 61 | std::string decrypt(const std::string& data, const std::string& iv, const std::string& key); 62 | } 63 | 64 | namespace tiger 65 | { 66 | std::string compute(const std::string& data, bool hex = false); 67 | std::string compute(const uint8_t* data, size_t length, bool hex = false); 68 | } 69 | 70 | namespace aes 71 | { 72 | std::string encrypt(const std::string& data, const std::string& iv, const std::string& key); 73 | std::string decrypt(const std::string& data, const std::string& iv, const std::string& key); 74 | } 75 | 76 | namespace hmac_sha1 77 | { 78 | std::string compute(const std::string& data, const std::string& key); 79 | } 80 | 81 | namespace sha1 82 | { 83 | std::string compute(const std::string& data, bool hex = false); 84 | std::string compute(const uint8_t* data, size_t length, bool hex = false); 85 | } 86 | 87 | namespace sha256 88 | { 89 | std::string compute(const std::string& data, bool hex = false); 90 | std::string compute(const uint8_t* data, size_t length, bool hex = false); 91 | } 92 | 93 | namespace sha512 94 | { 95 | std::string compute(const std::string& data, bool hex = false); 96 | std::string compute(const uint8_t* data, size_t length, bool hex = false); 97 | } 98 | 99 | namespace base64 100 | { 101 | std::string encode(const uint8_t* data, size_t len); 102 | std::string encode(const std::string& data); 103 | std::string decode(const std::string& data); 104 | } 105 | 106 | namespace jenkins_one_at_a_time 107 | { 108 | unsigned int compute(const std::string& data); 109 | unsigned int compute(const char* key, size_t len); 110 | }; 111 | 112 | namespace random 113 | { 114 | uint32_t get_integer(); 115 | std::string get_challenge(); 116 | void get_data(void* data, size_t size); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/utils/env.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "env.hpp" 3 | 4 | namespace utils::env 5 | { 6 | std::string get_value(const std::string& name) 7 | { 8 | const char* var_value = nullptr; 9 | #ifdef _WIN32 10 | 11 | char* buffer = nullptr; 12 | size_t len = 0; 13 | if (_dupenv_s(&buffer, &len, name.c_str()) == 0 && buffer != nullptr) 14 | { 15 | var_value = buffer; 16 | } 17 | 18 | const auto _ = gsl::finally([&] 19 | { 20 | std::free(buffer); 21 | }); 22 | 23 | if (var_value) 24 | { 25 | return var_value; 26 | } 27 | 28 | return {}; 29 | #else 30 | var_value = std::getenv(name.c_str()); 31 | return var_value ? std::string{ var_value } : std::string{}; 32 | #endif 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/env.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace utils::env 5 | { 6 | std::string get_value(const std::string& name); 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/info_string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "info_string.hpp" 4 | #include "string.hpp" 5 | 6 | namespace utils 7 | { 8 | info_string::info_string(const std::string& buffer) 9 | { 10 | this->parse(buffer); 11 | } 12 | 13 | info_string::info_string(const std::string_view& buffer) 14 | : info_string(std::string{buffer}) 15 | { 16 | } 17 | 18 | void info_string::set(const std::string& key, const std::string& value) 19 | { 20 | this->key_value_pairs_[key] = value; 21 | } 22 | 23 | std::string info_string::get(const std::string& key) const 24 | { 25 | const auto value = this->key_value_pairs_.find(key); 26 | if (value != this->key_value_pairs_.end()) 27 | { 28 | return value->second; 29 | } 30 | 31 | return {}; 32 | } 33 | 34 | void info_string::parse(std::string buffer) 35 | { 36 | if (buffer[0] == '\\') 37 | { 38 | buffer = buffer.substr(1); 39 | } 40 | 41 | auto key_values = string::split(buffer, '\\'); 42 | for (size_t i = 0; !key_values.empty() && i < (key_values.size() - 1); i += 2) 43 | { 44 | const auto& key = key_values[i]; 45 | const auto& value = key_values[i + 1]; 46 | 47 | if (!this->key_value_pairs_.contains(key)) 48 | { 49 | this->key_value_pairs_[key] = value; 50 | } 51 | } 52 | } 53 | 54 | std::string info_string::build() const 55 | { 56 | std::string info_string; 57 | for (const auto& [key, val] : this->key_value_pairs_) 58 | { 59 | info_string.push_back('\\'); 60 | 61 | info_string.append(key); 62 | info_string.push_back('\\'); 63 | info_string.append(val); 64 | } 65 | 66 | return info_string; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/utils/info_string.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace utils 7 | { 8 | class info_string 9 | { 10 | public: 11 | info_string() = default; 12 | info_string(const std::string& buffer); 13 | info_string(const std::string_view& buffer); 14 | 15 | void set(const std::string& key, const std::string& value); 16 | std::string get(const std::string& key) const; 17 | std::string build() const; 18 | 19 | private: 20 | std::unordered_map key_value_pairs_{}; 21 | 22 | void parse(std::string buffer); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/io.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "io.hpp" 4 | #include 5 | #include 6 | 7 | namespace utils::io 8 | { 9 | bool remove_file(const std::string& file) 10 | { 11 | return remove(file.c_str()) == 0; 12 | } 13 | 14 | bool move_file(const std::string& src, const std::string& target) 15 | { 16 | return rename(src.c_str(), target.c_str()) == 0; 17 | } 18 | 19 | bool file_exists(const std::string& file) 20 | { 21 | return std::ifstream(file).good(); 22 | } 23 | 24 | bool write_file(const std::string& file, const std::string& data, const bool append) 25 | { 26 | const auto pos = file.find_last_of("/\\"); 27 | if (pos != std::string::npos) 28 | { 29 | create_directory(file.substr(0, pos)); 30 | } 31 | 32 | auto mode = std::ios::binary | std::ofstream::out; 33 | if (append) 34 | { 35 | mode |= std::ofstream::app; 36 | } 37 | 38 | std::ofstream stream(file, mode); 39 | 40 | if (stream.is_open()) 41 | { 42 | stream.write(data.data(), data.size()); 43 | stream.close(); 44 | return true; 45 | } 46 | 47 | return false; 48 | } 49 | 50 | std::string read_file(const std::string& file) 51 | { 52 | std::string data; 53 | read_file(file, &data); 54 | return data; 55 | } 56 | 57 | bool read_file(const std::string& file, std::string* data) 58 | { 59 | if (!data) return false; 60 | data->clear(); 61 | 62 | if (file_exists(file)) 63 | { 64 | std::ifstream stream(file, std::ios::binary); 65 | if (!stream.is_open()) return false; 66 | 67 | stream.seekg(0, std::ios::end); 68 | const std::streamsize size = stream.tellg(); 69 | stream.seekg(0, std::ios::beg); 70 | 71 | if (size > -1) 72 | { 73 | data->resize(static_cast(size)); 74 | stream.read(const_cast(data->data()), size); 75 | stream.close(); 76 | return true; 77 | } 78 | } 79 | 80 | return false; 81 | } 82 | 83 | size_t file_size(const std::string& file) 84 | { 85 | if (file_exists(file)) 86 | { 87 | std::ifstream stream(file, std::ios::binary); 88 | 89 | if (stream.good()) 90 | { 91 | stream.seekg(0, std::ios::end); 92 | return static_cast(stream.tellg()); 93 | } 94 | } 95 | 96 | return 0; 97 | } 98 | 99 | bool create_directory(const std::string& directory) 100 | { 101 | return std::filesystem::create_directories(directory); 102 | } 103 | 104 | bool directory_exists(const std::string& directory) 105 | { 106 | return std::filesystem::is_directory(directory); 107 | } 108 | 109 | bool directory_is_empty(const std::string& directory) 110 | { 111 | return std::filesystem::is_empty(directory); 112 | } 113 | 114 | std::vector list_files(const std::string& directory) 115 | { 116 | std::vector files; 117 | 118 | for (auto& file : std::filesystem::directory_iterator(directory)) 119 | { 120 | files.push_back(file.path().generic_string()); 121 | } 122 | 123 | return files; 124 | } 125 | 126 | void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target) 127 | { 128 | std::filesystem::copy(src, target, 129 | std::filesystem::copy_options::overwrite_existing | 130 | std::filesystem::copy_options::recursive); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/utils/io.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace utils::io 8 | { 9 | bool remove_file(const std::string& file); 10 | bool move_file(const std::string& src, const std::string& target); 11 | bool file_exists(const std::string& file); 12 | bool write_file(const std::string& file, const std::string& data, bool append = false); 13 | bool read_file(const std::string& file, std::string* data); 14 | std::string read_file(const std::string& file); 15 | size_t file_size(const std::string& file); 16 | bool create_directory(const std::string& directory); 17 | bool directory_exists(const std::string& directory); 18 | bool directory_is_empty(const std::string& directory); 19 | std::vector list_files(const std::string& directory); 20 | void copy_folder(const std::filesystem::path& src, const std::filesystem::path& target); 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/memory.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "memory.hpp" 4 | 5 | namespace utils 6 | { 7 | memory::allocator memory::mem_allocator_; 8 | 9 | memory::allocator::~allocator() 10 | { 11 | this->clear(); 12 | } 13 | 14 | void memory::allocator::clear() 15 | { 16 | std::lock_guard _(this->mutex_); 17 | 18 | for (auto& data : this->pool_) 19 | { 20 | memory::free(data); 21 | } 22 | 23 | this->pool_.clear(); 24 | } 25 | 26 | void memory::allocator::free(void* data) 27 | { 28 | std::lock_guard _(this->mutex_); 29 | 30 | const auto j = std::find(this->pool_.begin(), this->pool_.end(), data); 31 | if (j != this->pool_.end()) 32 | { 33 | memory::free(data); 34 | this->pool_.erase(j); 35 | } 36 | } 37 | 38 | void memory::allocator::free(const void* data) 39 | { 40 | this->free(const_cast(data)); 41 | } 42 | 43 | void* memory::allocator::allocate(const size_t length) 44 | { 45 | std::lock_guard _(this->mutex_); 46 | 47 | const auto data = memory::allocate(length); 48 | this->pool_.push_back(data); 49 | return data; 50 | } 51 | 52 | bool memory::allocator::empty() const 53 | { 54 | return this->pool_.empty(); 55 | } 56 | 57 | char* memory::allocator::duplicate_string(const std::string& string) 58 | { 59 | std::lock_guard _(this->mutex_); 60 | 61 | const auto data = memory::duplicate_string(string); 62 | this->pool_.push_back(data); 63 | return data; 64 | } 65 | 66 | void* memory::allocate(const size_t length) 67 | { 68 | return ::calloc(length, 1); 69 | } 70 | 71 | char* memory::duplicate_string(const std::string& string) 72 | { 73 | const auto new_string = allocate_array(string.size() + 1); 74 | std::memcpy(new_string, string.c_str(), string.size()); 75 | return new_string; 76 | } 77 | 78 | void memory::free(void* data) 79 | { 80 | ::free(data); 81 | } 82 | 83 | void memory::free(const void* data) 84 | { 85 | free(const_cast(data)); 86 | } 87 | 88 | bool memory::is_set(const void* mem, const char chr, const size_t length) 89 | { 90 | auto* const mem_arr = static_cast(mem); 91 | 92 | for (size_t i = 0; i < length; ++i) 93 | { 94 | if (mem_arr[i] != chr) 95 | { 96 | return false; 97 | } 98 | } 99 | 100 | return true; 101 | } 102 | 103 | memory::allocator* memory::get_allocator() 104 | { 105 | return &memory::mem_allocator_; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/utils/memory.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace utils 7 | { 8 | class memory final 9 | { 10 | public: 11 | class allocator final 12 | { 13 | public: 14 | ~allocator(); 15 | 16 | void clear(); 17 | 18 | void free(void* data); 19 | 20 | void free(const void* data); 21 | 22 | void* allocate(size_t length); 23 | 24 | template 25 | inline T* allocate() 26 | { 27 | return this->allocate_array(1); 28 | } 29 | 30 | template 31 | inline T* allocate_array(const size_t count = 1) 32 | { 33 | return static_cast(this->allocate(count * sizeof(T))); 34 | } 35 | 36 | bool empty() const; 37 | 38 | char* duplicate_string(const std::string& string); 39 | 40 | private: 41 | std::mutex mutex_; 42 | std::vector pool_; 43 | }; 44 | 45 | static void* allocate(size_t length); 46 | 47 | template 48 | static inline T* allocate() 49 | { 50 | return allocate_array(1); 51 | } 52 | 53 | template 54 | static inline T* allocate_array(const size_t count = 1) 55 | { 56 | return static_cast(allocate(count * sizeof(T))); 57 | } 58 | 59 | static char* duplicate_string(const std::string& string); 60 | 61 | static void free(void* data); 62 | static void free(const void* data); 63 | 64 | static bool is_set(const void* mem, char chr, size_t length); 65 | 66 | static allocator* get_allocator(); 67 | 68 | private: 69 | static allocator mem_allocator_; 70 | }; 71 | } 72 | -------------------------------------------------------------------------------- /src/utils/parameters.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "parameters.hpp" 4 | #include "string.hpp" 5 | 6 | namespace utils 7 | { 8 | parameters::parameters(std::string buffer) 9 | { 10 | while (!buffer.empty() && (buffer.back() == '\0' || isspace(buffer.back()))) 11 | { 12 | buffer.pop_back(); 13 | } 14 | 15 | this->arguments_ = string::split(buffer, ' '); 16 | } 17 | 18 | parameters::parameters(const std::string_view& buffer) 19 | : parameters(std::string{buffer.data(), buffer.size()}) 20 | { 21 | } 22 | 23 | void parameters::add(std::string value) 24 | { 25 | this->arguments_.emplace_back(std::move(value)); 26 | } 27 | 28 | size_t parameters::size() const 29 | { 30 | return this->arguments_.size(); 31 | } 32 | 33 | const std::string& parameters::get(const size_t index) const 34 | { 35 | return this->arguments_.at(index); 36 | } 37 | 38 | std::string parameters::join(const size_t index, const std::string& separator) const 39 | { 40 | std::string buffer{}; 41 | 42 | for (auto i = index; i < this->size(); ++i) 43 | { 44 | if (i != 0) 45 | { 46 | buffer.append(separator); 47 | } 48 | 49 | buffer.append(this->get(i)); 50 | } 51 | 52 | return buffer; 53 | } 54 | 55 | const std::string& parameters::operator[](const size_t index) const 56 | { 57 | return this->get(index); 58 | } 59 | 60 | parameters::list_type::iterator parameters::begin() 61 | { 62 | return this->arguments_.begin(); 63 | } 64 | 65 | parameters::list_type::const_iterator parameters::begin() const 66 | { 67 | return this->arguments_.begin(); 68 | } 69 | 70 | parameters::list_type::iterator parameters::end() 71 | { 72 | return this->arguments_.end(); 73 | } 74 | 75 | parameters::list_type::const_iterator parameters::end() const 76 | { 77 | return this->arguments_.end(); 78 | } 79 | 80 | bool parameters::has(const std::string& value) const 81 | { 82 | for (const auto& val : this->arguments_) 83 | { 84 | if (val == value) 85 | { 86 | return true; 87 | } 88 | } 89 | 90 | return false; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/utils/parameters.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace utils 7 | { 8 | class parameters 9 | { 10 | public: 11 | using list_type = std::vector; 12 | 13 | parameters() = default; 14 | parameters(std::string buffer); 15 | parameters(const std::string_view& buffer); 16 | 17 | void add(std::string value); 18 | 19 | size_t size() const; 20 | std::string join(size_t index = 0, const std::string& separator = " ") const; 21 | 22 | const std::string& operator [](size_t index) const; 23 | const std::string& get(size_t index) const; 24 | 25 | list_type::iterator begin(); 26 | list_type::const_iterator begin() const; 27 | list_type::iterator end(); 28 | list_type::const_iterator end() const; 29 | 30 | bool has(const std::string& value) const; 31 | 32 | private: 33 | list_type arguments_{}; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/string.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "string.hpp" 4 | #include 5 | #include 6 | #include 7 | 8 | namespace utils::string 9 | { 10 | const char* va(const char* fmt, ...) 11 | { 12 | static thread_local va_provider<8, 256> provider; 13 | 14 | va_list ap; 15 | va_start(ap, fmt); 16 | 17 | const char* result = provider.get(fmt, ap); 18 | 19 | va_end(ap); 20 | return result; 21 | } 22 | 23 | std::vector split(const std::string& s, const char delim) 24 | { 25 | std::stringstream ss(s); 26 | std::string item; 27 | std::vector elems; 28 | 29 | while (std::getline(ss, item, delim)) 30 | { 31 | elems.push_back(std::move(item)); 32 | item = std::string{}; 33 | } 34 | 35 | return elems; 36 | } 37 | 38 | std::string to_lower(std::string text) 39 | { 40 | std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input) 41 | { 42 | return static_cast(tolower(input)); 43 | }); 44 | 45 | return text; 46 | } 47 | 48 | std::string to_upper(std::string text) 49 | { 50 | std::transform(text.begin(), text.end(), text.begin(), [](const unsigned char input) 51 | { 52 | return static_cast(toupper(input)); 53 | }); 54 | 55 | return text; 56 | } 57 | 58 | bool starts_with(const std::string& text, const std::string& substring) 59 | { 60 | return text.find(substring) == 0; 61 | } 62 | 63 | bool ends_with(const std::string& text, const std::string& substring) 64 | { 65 | if (substring.size() > text.size()) return false; 66 | return std::equal(substring.rbegin(), substring.rend(), text.rbegin()); 67 | } 68 | 69 | std::string dump_hex(const std::string& data, const std::string& separator) 70 | { 71 | std::string result; 72 | 73 | for (unsigned int i = 0; i < data.size(); ++i) 74 | { 75 | if (i > 0) 76 | { 77 | result.append(separator); 78 | } 79 | 80 | result.append(va("%02X", data[i] & 0xFF)); 81 | } 82 | 83 | return result; 84 | } 85 | 86 | std::string replace(std::string str, const std::string& from, const std::string& to) 87 | { 88 | if (from.empty()) 89 | { 90 | return str; 91 | } 92 | 93 | size_t start_pos = 0; 94 | while ((start_pos = str.find(from, start_pos)) != std::string::npos) 95 | { 96 | str.replace(start_pos, from.length(), to); 97 | start_pos += to.length(); 98 | } 99 | 100 | return str; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/utils/string.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "memory.hpp" 3 | #include 4 | 5 | template 6 | constexpr std::size_t ARRAY_COUNT(Type(&)[n]) { return n; } 7 | 8 | namespace utils::string 9 | { 10 | template 11 | class va_provider final 12 | { 13 | public: 14 | static_assert(buffers != 0 && min_buffer_size != 0, "buffers and min_buffer_size mustn't be 0"); 15 | 16 | va_provider() : current_buffer_(0) 17 | { 18 | } 19 | 20 | char* get(const char* format, va_list ap) 21 | { 22 | ++this->current_buffer_ %= ARRAY_COUNT(this->string_pool_); 23 | auto entry = &this->string_pool_[this->current_buffer_]; 24 | 25 | if (!entry->size_ || !entry->buffer_) 26 | { 27 | throw std::runtime_error("String pool not initialized"); 28 | } 29 | 30 | while (true) 31 | { 32 | const auto res = vsnprintf(entry->buffer_, entry->size_, format, ap); 33 | 34 | if (res > 0) break; // Success 35 | if (res == 0) return nullptr; // Error 36 | 37 | entry->double_size(); 38 | } 39 | 40 | return entry->buffer_; 41 | } 42 | 43 | private: 44 | class entry final 45 | { 46 | public: 47 | explicit entry(const std::size_t size = min_buffer_size) : size_(size), buffer_(nullptr) 48 | { 49 | if (this->size_ < min_buffer_size) this->size_ = min_buffer_size; 50 | this->allocate(); 51 | } 52 | 53 | ~entry() 54 | { 55 | if (this->buffer_) memory::get_allocator()->free(this->buffer_); 56 | this->size_ = 0; 57 | this->buffer_ = nullptr; 58 | } 59 | 60 | void allocate() 61 | { 62 | if (this->buffer_) memory::get_allocator()->free(this->buffer_); 63 | this->buffer_ = memory::get_allocator()->allocate_array(this->size_ + 1); 64 | } 65 | 66 | void double_size() 67 | { 68 | this->size_ *= 2; 69 | this->allocate(); 70 | } 71 | 72 | std::size_t size_; 73 | char* buffer_; 74 | }; 75 | 76 | std::size_t current_buffer_; 77 | entry string_pool_[buffers]; 78 | }; 79 | 80 | const char* va(const char* fmt, ...); 81 | 82 | std::vector split(const std::string& s, char delim); 83 | std::vector split(const std::string_view& s, char delim); 84 | 85 | std::string to_lower(std::string text); 86 | std::string to_upper(std::string text); 87 | 88 | bool starts_with(const std::string& text, const std::string& substring); 89 | bool ends_with(const std::string& text, const std::string& substring); 90 | 91 | std::string dump_hex(const std::string& data, const std::string& separator = " "); 92 | 93 | std::string replace(std::string str, const std::string& from, const std::string& to); 94 | } 95 | --------------------------------------------------------------------------------