mirror of
				https://github.com/OpenFusionProject/OpenFusion.git
				synced 2025-10-31 08:40:12 +00:00 
			
		
		
		
	Compare commits
	
		
			40 Commits
		
	
	
		
			575754f727
			...
			2.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 43a2504357 | |||
| ca196bf620 | |||
| 6b9ae4c325 | |||
| d06c324aa3 | |||
| 052196d1cd | |||
| e84f6505b8 | |||
| b483bf7190 | |||
| 6ff51685a8 | |||
| b4ed31d4fb | |||
| 36e0667ed2 | |||
| ed9fe61faf | |||
| 55cf3f7102 | |||
| 1543dac4e0 | |||
| ae327cc104 | |||
| 6ffde9bb44 | |||
| 8568fd1c46 | |||
| 05a5303522 | |||
| 3365cb53b7 | |||
| 5e92a58134 | |||
| 94064e1865 | |||
| 5e73ff272d | |||
| 197ccad0eb | |||
|   | 68b56e7c25 | ||
|   | cada1bcfd8 | ||
|   | ca43a2996a | ||
| 7c66041a6f | |||
| 2c822e210b | |||
|   | 352fa8a133 | ||
|   | c116794c83 | ||
|   | 4ebda6066c | ||
| 6de21277d6 | |||
|   | 397700e909 | ||
| d9b6aedd5b | |||
|   | 145113062b | ||
| d717c5d74d | |||
| a6eb0e2349 | |||
| 52833f7fb3 | |||
|   | 3aed24de26 | ||
|   | 17362b2ea6 | ||
|   | 2096c3c3cc | 
							
								
								
									
										10
									
								
								.github/workflows/check-builds.yaml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/check-builds.yaml
									
									
									
									
										vendored
									
									
								
							| @@ -29,7 +29,7 @@ jobs: | |||||||
|           submodules: recursive |           submodules: recursive | ||||||
|           fetch-depth: 0 |           fetch-depth: 0 | ||||||
|       - name: Install dependencies |       - name: Install dependencies | ||||||
|         run: sudo apt install clang cmake snap -y && sudo snap install powershell --classic |         run: sudo apt install clang cmake snap libsqlite3-dev -y && sudo snap install powershell --classic | ||||||
|       - name: Check compilation |       - name: Check compilation | ||||||
|         run: | |         run: | | ||||||
|           $versions = "104", "728", "1013" |           $versions = "104", "728", "1013" | ||||||
| @@ -52,7 +52,7 @@ jobs: | |||||||
|           Copy-Item -Path "config.ini" -Destination "bin" |           Copy-Item -Path "config.ini" -Destination "bin" | ||||||
|         shell: pwsh |         shell: pwsh | ||||||
|       - name: Upload build artifact |       - name: Upload build artifact | ||||||
|         uses: actions/upload-artifact@v2 |         uses: actions/upload-artifact@v4 | ||||||
|         with: |         with: | ||||||
|           name: 'ubuntu22_04-bin-x64-${{ env.SHORT_SHA }}' |           name: 'ubuntu22_04-bin-x64-${{ env.SHORT_SHA }}' | ||||||
|           path: bin |           path: bin | ||||||
| @@ -106,14 +106,14 @@ jobs: | |||||||
|           } |           } | ||||||
|         shell: pwsh |         shell: pwsh | ||||||
|       - name: Upload build artifact |       - name: Upload build artifact | ||||||
|         uses: actions/upload-artifact@v2 |         uses: actions/upload-artifact@v4 | ||||||
|         with: |         with: | ||||||
|           name: 'windows-vs2019-bin-x64-${{ env.SHORT_SHA }}' |           name: 'windows-vs2019-bin-x64-${{ env.SHORT_SHA }}' | ||||||
|           path: bin |           path: bin | ||||||
|  |  | ||||||
|   copy-artifacts: |   copy-artifacts: | ||||||
|     if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' |     if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master' | ||||||
|     runs-on: ubuntu-22.04 |     runs-on: ubuntu-latest | ||||||
|     needs: [windows-build, ubuntu-build] |     needs: [windows-build, ubuntu-build] | ||||||
|     env: |     env: | ||||||
|       BOT_SSH_KEY: ${{ secrets.BOT_SSH_KEY }} |       BOT_SSH_KEY: ${{ secrets.BOT_SSH_KEY }} | ||||||
| @@ -127,7 +127,7 @@ jobs: | |||||||
|           GITDESC=$(git describe --tags) |           GITDESC=$(git describe --tags) | ||||||
|           mkdir $GITDESC |           mkdir $GITDESC | ||||||
|           echo "ARTDIR=$GITDESC" >> $GITHUB_ENV |           echo "ARTDIR=$GITDESC" >> $GITHUB_ENV | ||||||
|       - uses: actions/download-artifact@v3 |       - uses: actions/download-artifact@v4 | ||||||
|         with: |         with: | ||||||
|           path: ${{ env.ARTDIR }} |           path: ${{ env.ARTDIR }} | ||||||
|       - name: Upload artifacts |       - name: Upload artifacts | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								.github/workflows/push-docker-image.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										14
									
								
								.github/workflows/push-docker-image.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,6 +3,7 @@ name: Push Docker Image | |||||||
| on: | on: | ||||||
|   release: |   release: | ||||||
|     types: [published] |     types: [published] | ||||||
|  |   workflow_dispatch: | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   push-docker-image: |   push-docker-image: | ||||||
| @@ -10,11 +11,6 @@ jobs: | |||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|     permissions: |     permissions: | ||||||
|       contents: read |       contents: read | ||||||
|     strategy: |  | ||||||
|       matrix: |  | ||||||
|         platforms: |  | ||||||
|           - linux/amd64 |  | ||||||
|           - linux/arm64 |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|       - name: Retrieve major version |       - name: Retrieve major version | ||||||
| @@ -28,11 +24,15 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           password: ${{ secrets.DOCKERHUB_TOKEN }} |           password: ${{ secrets.DOCKERHUB_TOKEN }} | ||||||
|           username: ${{ secrets.DOCKERHUB_USERNAME }} |           username: ${{ secrets.DOCKERHUB_USERNAME }} | ||||||
|  |       - name: Set up QEMU | ||||||
|  |         uses: docker/setup-qemu-action@v3 | ||||||
|  |       - name: Set up Docker Buildx | ||||||
|  |         uses: docker/setup-buildx-action@v3 | ||||||
|       - name: Build and push the Docker image |       - name: Build and push the Docker image | ||||||
|         uses: docker/build-push-action@v5 |         uses: docker/build-push-action@v6 | ||||||
|         with: |         with: | ||||||
|           context: . |           context: . | ||||||
|           file: ./Dockerfile |           file: ./Dockerfile | ||||||
|           platforms: ${{ matrix.platforms }} |           platforms: linux/amd64,linux/arm64 | ||||||
|           push: true |           push: true | ||||||
|           tags: ${{ secrets.DOCKERHUB_REPOSITORY }}:${{ github.ref_name }},${{ secrets.DOCKERHUB_REPOSITORY }}:${{ steps.split.outputs._0 }},${{ secrets.DOCKERHUB_REPOSITORY }}:latest |           tags: ${{ secrets.DOCKERHUB_REPOSITORY }}:${{ github.ref_name }},${{ secrets.DOCKERHUB_REPOSITORY }}:${{ steps.split.outputs._0 }},${{ secrets.DOCKERHUB_REPOSITORY }}:latest | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,32 +1,41 @@ | |||||||
| # build | # build | ||||||
| FROM debian:stable-slim as build | FROM alpine:3 as build | ||||||
|  |  | ||||||
| WORKDIR /usr/src/app | WORKDIR /usr/src/app | ||||||
|  |  | ||||||
| RUN apt-get -y update && apt-get install -y \ | RUN apk update && apk upgrade && apk add \ | ||||||
|  | linux-headers \ | ||||||
| git \ | git \ | ||||||
| clang \ | clang18 \ | ||||||
| make \ | make \ | ||||||
| libsqlite3-dev | sqlite-dev | ||||||
|  |  | ||||||
| COPY src ./src | COPY src ./src | ||||||
| COPY vendor ./vendor | COPY vendor ./vendor | ||||||
| COPY .git ./.git | COPY .git ./.git | ||||||
| COPY Makefile CMakeLists.txt version.h.in ./ | COPY Makefile CMakeLists.txt version.h.in ./ | ||||||
|  |  | ||||||
| RUN make -j8 | RUN sed -i 's/^CC=clang$/&-18/' Makefile | ||||||
|  | RUN sed -i 's/^CXX=clang++$/&-18/' Makefile | ||||||
|  |  | ||||||
|  | RUN make nosandbox -j$(nproc) | ||||||
|  |  | ||||||
| # prod | # prod | ||||||
| FROM debian:stable-slim | FROM alpine:3 | ||||||
|  |  | ||||||
| WORKDIR /usr/src/app | WORKDIR /usr/src/app | ||||||
|  |  | ||||||
| RUN apt-get -y update && apt-get install -y \ | RUN apk update && apk upgrade && apk add \ | ||||||
| libsqlite3-dev | libstdc++ \ | ||||||
|  | sqlite-dev | ||||||
|  |  | ||||||
| COPY --from=build /usr/src/app/bin/fusion /bin/fusion | COPY --from=build /usr/src/app/bin/fusion /bin/fusion | ||||||
| COPY sql ./sql | COPY sql ./sql | ||||||
|  |  | ||||||
| CMD ["/bin/fusion"] | CMD ["/bin/fusion"] | ||||||
|  |  | ||||||
| LABEL Name=openfusion Version=0.0.2 | EXPOSE 23000/tcp | ||||||
|  | EXPOSE 23001/tcp | ||||||
|  | EXPOSE 8003/tcp | ||||||
|  |  | ||||||
|  | LABEL Name=openfusion Version=2.0.0 | ||||||
|   | |||||||
| @@ -1,6 +1,6 @@ | |||||||
| MIT License | MIT License | ||||||
|  |  | ||||||
| Copyright (c) 2020-2024 OpenFusion Contributors | Copyright (c) 2020-2025 OpenFusion Contributors | ||||||
|  |  | ||||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||||
| of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||||
|   | |||||||
							
								
								
									
										8
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								Makefile
									
									
									
									
									
								
							| @@ -115,6 +115,7 @@ CXXHDR=\ | |||||||
| 	src/settings.hpp\ | 	src/settings.hpp\ | ||||||
| 	src/Transport.hpp\ | 	src/Transport.hpp\ | ||||||
| 	src/TableData.hpp\ | 	src/TableData.hpp\ | ||||||
|  | 	src/Bucket.hpp\ | ||||||
| 	src/Chunking.hpp\ | 	src/Chunking.hpp\ | ||||||
| 	src/Buddies.hpp\ | 	src/Buddies.hpp\ | ||||||
| 	src/Groups.hpp\ | 	src/Groups.hpp\ | ||||||
| @@ -133,6 +134,8 @@ HDR=$(CHDR) $(CXXHDR) | |||||||
| all: $(SERVER) | all: $(SERVER) | ||||||
|  |  | ||||||
| windows: $(SERVER) | windows: $(SERVER) | ||||||
|  | nosandbox: $(SERVER) | ||||||
|  | nolandlock: $(SERVER) | ||||||
|  |  | ||||||
| # assign Windows-specific values if targeting Windows | # assign Windows-specific values if targeting Windows | ||||||
| windows : CC=$(WIN_CC) | windows : CC=$(WIN_CC) | ||||||
| @@ -142,6 +145,9 @@ windows : CXXFLAGS=$(WIN_CXXFLAGS) | |||||||
| windows : LDFLAGS=$(WIN_LDFLAGS) | windows : LDFLAGS=$(WIN_LDFLAGS) | ||||||
| windows : SERVER=$(WIN_SERVER) | windows : SERVER=$(WIN_SERVER) | ||||||
|  |  | ||||||
|  | nosandbox : CFLAGS+=-DCONFIG_NOSANDBOX=1 | ||||||
|  | nolandlock : CFLAGS+=-DCONFIG_NOLANDLOCK=1 | ||||||
|  |  | ||||||
| .SUFFIXES: .o .c .cpp .h .hpp | .SUFFIXES: .o .c .cpp .h .hpp | ||||||
|  |  | ||||||
| .c.o: | .c.o: | ||||||
| @@ -163,7 +169,7 @@ version.h: | |||||||
|  |  | ||||||
| src/main.o: version.h | src/main.o: version.h | ||||||
|  |  | ||||||
| .PHONY: all windows clean nuke | .PHONY: all windows nosandbox nolandlock clean nuke | ||||||
|  |  | ||||||
| # only gets rid of OpenFusion objects, so we don't need to | # only gets rid of OpenFusion objects, so we don't need to | ||||||
| # recompile the libs every time | # recompile the libs every time | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								README.md
									
									
									
									
									
								
							| @@ -1,34 +1,35 @@ | |||||||
| <p align="center"><img width="640" src="res/openfusion-hero.png" alt=""></p> | <p align="center"><img width="640" src="res/openfusion-hero.png" alt="OpenFusion Logo"></p> | ||||||
|  |  | ||||||
| <p align="center"> | <p align="center"> | ||||||
|     <a href="https://github.com/OpenFusionProject/OpenFusion/releases/latest"><img src="https://img.shields.io/github/v/release/OpenFusionProject/OpenFusion" alt="Current Release"></a> |     <a href="https://github.com/OpenFusionProject/OpenFusion/releases/latest"><img src="https://img.shields.io/github/v/release/OpenFusionProject/OpenFusion" alt="Current Release"></a> | ||||||
|     <a href="https://github.com/OpenFusionProject/OpenFusion/actions/workflows/check-builds.yaml"><img src="https://github.com/OpenFusionProject/OpenFusion/actions/workflows/check-builds.yaml/badge.svg" alt="Workflow"></a> |     <a href="https://github.com/OpenFusionProject/OpenFusion/actions/workflows/check-builds.yaml"><img src="https://github.com/OpenFusionProject/OpenFusion/actions/workflows/check-builds.yaml/badge.svg" alt="Workflow"></a> | ||||||
|  |     <a href="https://hub.docker.com/repository/docker/openfusion/openfusion/"><img src="https://badgen.net/docker/pulls/openfusion/openfusion?icon=docker&label=pulls"></a> | ||||||
|     <a href="https://discord.gg/DYavckB"><img src="https://img.shields.io/badge/chat-on%20discord-7289da.svg?logo=discord" alt="Discord"></a> |     <a href="https://discord.gg/DYavckB"><img src="https://img.shields.io/badge/chat-on%20discord-7289da.svg?logo=discord" alt="Discord"></a> | ||||||
|     <a href="https://github.com/OpenFusionProject/OpenFusion/blob/master/LICENSE.md"><img src="https://img.shields.io/github/license/OpenFusionProject/OpenFusion" alt="License"></a> |     <a href="https://github.com/OpenFusionProject/OpenFusion/blob/master/LICENSE.md"><img src="https://img.shields.io/github/license/OpenFusionProject/OpenFusion" alt="License"></a> | ||||||
| </p> | </p> | ||||||
|  |  | ||||||
| OpenFusion is a reverse-engineered server for FusionFall. It primarily targets versions `beta-20100104` and `beta-20111013` of the original game, with [limited support](https://github.com/OpenFusionProject/OpenFusion/wiki/FusionFall-Version-Support) for others. | OpenFusion is a reverse-engineered server for FusionFall. It primarily targets versions `beta-20100104` and `beta-20111013` of the original game, with [limited support](https://openfusion.dev/docs/reference/fusionfall-version-support/) for others. | ||||||
|  |  | ||||||
| ## Usage | ## Usage | ||||||
|  |  | ||||||
| ### Getting Started | ### Getting Started | ||||||
| #### Method A: Installer (Easiest) | #### Method A: Installer (Easiest) | ||||||
| 1. Download the client installer by clicking [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.5/OpenFusionClient-1.5-Installer.exe) - choose to run the file. | 1. Download the launcher installer by clicking [here](https://github.com/OpenFusionProject/OpenFusionLauncher/releases/latest/download/OpenFusionLauncher-Windows-Installer.exe) - choose to run the file. | ||||||
| 2. After a few moments, the client should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect. | 2. After a few moments, the launcher should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect. | ||||||
| 3. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen. | 3. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen. | ||||||
| 4. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 3. | 4. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 3. | ||||||
|  |  | ||||||
| #### Method B: Standalone .zip file | #### Method B: Standalone .zip file | ||||||
| 1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.5/OpenFusionClient-1.5.zip). | 1. Download the launcher from [here](https://github.com/OpenFusionProject/OpenFusionLauncher/releases/latest/download/OpenFusionLauncher-Windows-Portable.zip). | ||||||
| 2. Extract it to a folder of your choice. Note: if you are upgrading from an older version, it is preferable to start with a fresh folder rather than overwriting a previous install. | 2. Extract it to a folder of your choice. Note: if you are upgrading from an older version, it is preferable to start with a fresh folder rather than overwriting a previous install. | ||||||
| 3. Run OpenFusionClient.exe - you will be given a choice between two public servers by default. Select the one you wish to play and click connect. | 3. Run OpenFusionLauncher.exe - you will be given a choice between two public servers by default. Select the one you wish to play and click connect. | ||||||
| 4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen. | 4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen. | ||||||
| 5. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 4. | 5. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 4. | ||||||
|  |  | ||||||
| Instructions for getting the client to run on Linux through Wine can be found [here](https://github.com/OpenFusionProject/OpenFusion/wiki/Running-the-game-client-on-Linux). | Instructions for getting the client to run on Linux through Wine can be found [here](https://openfusion.dev/docs/guides/running-on-linux/). | ||||||
|  |  | ||||||
| ### Hosting a server | ### Hosting a server | ||||||
| 1. Grab `OpenFusionServer-1.5-Original.zip` or `OpenFusionServer-1.5-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.5). | 1. Grab `OpenFusionServer-Windows-Original.zip` or `OpenFusionServer-Windows-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/latest). | ||||||
| 2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server. | 2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server. | ||||||
| 3. Add a new server to the client's list: | 3. Add a new server to the client's list: | ||||||
|     1. For Description, enter anything you want. This is what will show up in the server list. |     1. For Description, enter anything you want. This is what will show up in the server list. | ||||||
| @@ -79,17 +80,17 @@ This just works if you're all under the same LAN, but if you want to play over t | |||||||
|  |  | ||||||
| ## Compiling  | ## Compiling  | ||||||
|  |  | ||||||
| OpenFusion has one external dependency: SQLite. The oldest compatible version is `3.33.0`. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Installing-SQLite-on-Windows-using-vcpkg). | OpenFusion has one external dependency: SQLite. The oldest compatible version is `3.33.0`. You can install it on Windows using `vcpkg`, and on Unix/Linux using your distribution's package manager. For a more indepth guide on how to set up vcpkg, [read this guide](https://openfusion.dev/docs/development/installing-sqlite-on-windows-using-vcpkg/). | ||||||
|  |  | ||||||
| You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file. | You have two choices for compiling OpenFusion: the included Makefile and the included CMakeLists file. | ||||||
|  |  | ||||||
| ### Makefile | ### Makefile | ||||||
|  |  | ||||||
| A detailed compilation guide is available for Windows users in the wiki [using MinGW-w64 and MSYS2](https://github.com/OpenFusionProject/OpenFusion/wiki/Compilation-on-Windows). Otherwise, to compile it for the current platform you're on, just run `make` with the correct build tools installed (currently make and clang). | A detailed compilation guide is available for Windows users on the website [using MinGW-w64 and MSYS2](https://openfusion.dev/docs/development/compilation-on-windows-msys2-mingw/). Otherwise, to compile it for the current platform you're on, just run `make` with the correct build tools installed (currently make and clang). | ||||||
|  |  | ||||||
| ### CMake | ### CMake | ||||||
|  |  | ||||||
| A detailed guide is available [on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/Compilation-with-CMake-or-Visual-Studio) for people using regular old CMake or the version of CMake that comes with Visual Studio. tl;dr: `cmake -B build` | A detailed guide is available [in our documentation](https://openfusion.dev/docs/development/compilation-with-cmake-or-visual-studio/) for people using regular old CMake or the version of CMake that comes with Visual Studio. TL;DR: `cmake -B build` | ||||||
|  |  | ||||||
| ## Contributing | ## Contributing | ||||||
|  |  | ||||||
| @@ -107,4 +108,4 @@ Meanwhile the Academy server is more meant for legitimate playthroughs (default | |||||||
|  |  | ||||||
| When hosting a local server, you will have access to all commands by default (account level 1). | When hosting a local server, you will have access to all commands by default (account level 1). | ||||||
|  |  | ||||||
| For a list of available commands, see [this wiki page](https://github.com/OpenFusionProject/OpenFusion/wiki/Ingame-Command-list). | For a list of available commands, see [this page](https://openfusion.dev/docs/reference/ingame-command-list/). | ||||||
|   | |||||||
| @@ -12,11 +12,17 @@ sandbox=true | |||||||
| [login] | [login] | ||||||
| # must be kept in sync with loginInfo.php | # must be kept in sync with loginInfo.php | ||||||
| port=23000 | port=23000 | ||||||
|  | # will all name wheel names be approved instantly? | ||||||
|  | acceptallwheelnames=true | ||||||
| # will all custom names be approved instantly? | # will all custom names be approved instantly? | ||||||
| acceptallcustomnames=true | acceptallcustomnames=true | ||||||
| # should attempts to log into non-existent accounts | # should attempts to log into non-existent accounts | ||||||
| # automatically create them? | # automatically create them? | ||||||
| autocreateaccounts=true | autocreateaccounts=true | ||||||
|  | # list of supported authentication methods (comma-separated) | ||||||
|  | # password = allow logging in with plaintext passwords | ||||||
|  | # cookie = allow logging in with one-shot auth cookies | ||||||
|  | authmethods=password | ||||||
| # how often should everything be flushed to the database? | # how often should everything be flushed to the database? | ||||||
| # the default is 4 minutes | # the default is 4 minutes | ||||||
| dbsaveinterval=240 | dbsaveinterval=240 | ||||||
| @@ -98,5 +104,8 @@ eventmode=0 | |||||||
| enabled=false | enabled=false | ||||||
| # the port to listen for connections on | # the port to listen for connections on | ||||||
| port=8003 | port=8003 | ||||||
|  | # The local IP to listen on. | ||||||
|  | # Do not change this unless you know what you're doing. | ||||||
|  | listenip=127.0.0.1 | ||||||
| # how often the listeners should be updated (in milliseconds) | # how often the listeners should be updated (in milliseconds) | ||||||
| interval=5000 | interval=5000 | ||||||
|   | |||||||
| @@ -1,11 +1,9 @@ | |||||||
| version: '3.4' |  | ||||||
|  |  | ||||||
| services: | services: | ||||||
|   openfusion: |   openfusion: | ||||||
|     image: openfusion |  | ||||||
|     build: |     build: | ||||||
|       context: . |       context: . | ||||||
|       dockerfile: ./Dockerfile |       dockerfile: ./Dockerfile | ||||||
|  |     image: openfusion/openfusion:latest | ||||||
|     volumes: |     volumes: | ||||||
|       - ./config.ini:/usr/src/app/config.ini |       - ./config.ini:/usr/src/app/config.ini | ||||||
|       - ./database.db:/usr/src/app/database.db |       - ./database.db:/usr/src/app/database.db | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								sql/migration4.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								sql/migration4.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | /* | ||||||
|  |     It is recommended in the SQLite manual to turn off | ||||||
|  |     foreign keys when making schema changes that involve them | ||||||
|  | */ | ||||||
|  | PRAGMA foreign_keys=OFF; | ||||||
|  | BEGIN TRANSACTION; | ||||||
|  | -- New table to store auth cookies | ||||||
|  | CREATE TABLE Auth ( | ||||||
|  |     AccountID   INTEGER NOT NULL, | ||||||
|  |     Cookie      TEXT NOT NULL, | ||||||
|  |     Expires     INTEGER DEFAULT 0 NOT NULL, | ||||||
|  |     FOREIGN KEY(AccountID) REFERENCES Accounts(AccountID) ON DELETE CASCADE, | ||||||
|  |     UNIQUE (AccountID) | ||||||
|  | ); | ||||||
|  | -- Update DB Version | ||||||
|  | UPDATE Meta SET Value = 5 WHERE Key = 'DatabaseVersion'; | ||||||
|  | UPDATE Meta SET Value = strftime('%s', 'now') WHERE Key = 'LastMigration'; | ||||||
|  | COMMIT; | ||||||
|  | PRAGMA foreign_keys=ON; | ||||||
							
								
								
									
										8
									
								
								sql/migration5.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								sql/migration5.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | |||||||
|  | BEGIN TRANSACTION; | ||||||
|  | -- New Columns | ||||||
|  | ALTER TABLE Accounts ADD Email TEXT DEFAULT '' NOT NULL; | ||||||
|  | ALTER TABLE Accounts ADD LastPasswordReset INTEGER DEFAULT 0 NOT NULL; | ||||||
|  | -- Update DB Version | ||||||
|  | UPDATE Meta SET Value = 6 WHERE Key = 'DatabaseVersion'; | ||||||
|  | UPDATE Meta SET Value = strftime('%s', 'now') WHERE Key = 'LastMigration'; | ||||||
|  | COMMIT; | ||||||
| @@ -9,6 +9,8 @@ CREATE TABLE IF NOT EXISTS Accounts ( | |||||||
|     BannedUntil       INTEGER DEFAULT 0 NOT NULL, |     BannedUntil       INTEGER DEFAULT 0 NOT NULL, | ||||||
|     BannedSince       INTEGER DEFAULT 0 NOT NULL, |     BannedSince       INTEGER DEFAULT 0 NOT NULL, | ||||||
|     BanReason         TEXT    DEFAULT '' NOT NULL, |     BanReason         TEXT    DEFAULT '' NOT NULL, | ||||||
|  |     Email             TEXT    DEFAULT '' NOT NULL, | ||||||
|  |     LastPasswordReset INTEGER DEFAULT 0 NOT NULL, | ||||||
|     PRIMARY KEY(AccountID AUTOINCREMENT) |     PRIMARY KEY(AccountID AUTOINCREMENT) | ||||||
| ); | ); | ||||||
|  |  | ||||||
| @@ -158,4 +160,12 @@ CREATE TABLE IF NOT EXISTS RedeemedCodes( | |||||||
|     Code        TEXT NOT NULL, |     Code        TEXT NOT NULL, | ||||||
|     FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE, |     FOREIGN KEY(PlayerID) REFERENCES Players(PlayerID) ON DELETE CASCADE, | ||||||
|     UNIQUE (PlayerID, Code) |     UNIQUE (PlayerID, Code) | ||||||
| ) | ); | ||||||
|  |  | ||||||
|  | CREATE TABLE IF NOT EXISTS Auth ( | ||||||
|  |     AccountID   INTEGER NOT NULL, | ||||||
|  |     Cookie      TEXT NOT NULL, | ||||||
|  |     Expires     INTEGER DEFAULT 0 NOT NULL, | ||||||
|  |     FOREIGN KEY(AccountID) REFERENCES Accounts(AccountID) ON DELETE CASCADE, | ||||||
|  |     UNIQUE (AccountID) | ||||||
|  | ); | ||||||
|   | |||||||
| @@ -334,8 +334,8 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std: | |||||||
|     size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC); |     size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC); | ||||||
|     for(SkillResult& sr : results) |     for(SkillResult& sr : results) | ||||||
|         resplen += sr.size; |         resplen += sr.size; | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     sP_FE2CL_NANO_SKILL_USE_SUCC* pkt = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf; |     sP_FE2CL_NANO_SKILL_USE_SUCC* pkt = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf; | ||||||
|     pkt->iPC_ID = plr->iID; |     pkt->iPC_ID = plr->iID; | ||||||
| @@ -368,7 +368,6 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*> | |||||||
|     SkillData* skill = &SkillTable[skillID]; |     SkillData* skill = &SkillTable[skillID]; | ||||||
|  |  | ||||||
|     std::vector<SkillResult> results = handleSkill(skill, 0, src, affected); |     std::vector<SkillResult> results = handleSkill(skill, 0, src, affected); | ||||||
|     if(results.empty()) return; // no effect; no need for confirmation packets |  | ||||||
|  |  | ||||||
|     // lazy validation since skill results might be different sizes |     // lazy validation since skill results might be different sizes | ||||||
|     if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), MAX_SKILLRESULT_SIZE)) { |     if (!validOutVarPacket(sizeof(sP_FE2CL_NPC_SKILL_HIT), results.size(), MAX_SKILLRESULT_SIZE)) { | ||||||
| @@ -380,8 +379,8 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*> | |||||||
|     size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT); |     size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT); | ||||||
|     for(SkillResult& sr : results) |     for(SkillResult& sr : results) | ||||||
|         resplen += sr.size; |         resplen += sr.size; | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; |     sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; | ||||||
|     pkt->iNPC_ID = npc.id; |     pkt->iNPC_ID = npc.id; | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								src/Bucket.hpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/Bucket.hpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,40 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  | #include <optional> | ||||||
|  |  | ||||||
|  | #include <assert.h> | ||||||
|  |  | ||||||
|  | template<class T, size_t N> | ||||||
|  | class Bucket { | ||||||
|  |     std::array<T, N> buf; | ||||||
|  |     size_t sz; | ||||||
|  | public: | ||||||
|  |     Bucket() { | ||||||
|  |         sz = 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void add(const T& item) { | ||||||
|  |         assert(sz < N); | ||||||
|  |         buf[sz++] = item; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     std::optional<T> get(size_t idx) const { | ||||||
|  |         if (idx < sz) { | ||||||
|  |             return buf[idx]; | ||||||
|  |         } | ||||||
|  |         return std::nullopt; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     size_t size() const { | ||||||
|  |         return sz; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bool isFull() const { | ||||||
|  |         return sz == N; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void clear() { | ||||||
|  |         sz = 0; | ||||||
|  |     } | ||||||
|  | }; | ||||||
| @@ -41,9 +41,9 @@ void Buddies::sendBuddyList(CNSocket* sock) { | |||||||
|  |  | ||||||
|     // initialize response struct |     // initialize response struct | ||||||
|     size_t resplen = sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC) + buddyCnt * sizeof(sBuddyBaseInfo); |     size_t resplen = sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC) + buddyCnt * sizeof(sBuddyBaseInfo); | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|  |  | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC* resp = (sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC*)respbuf; |     sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC* resp = (sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC*)respbuf; | ||||||
|     sBuddyBaseInfo* respdata = (sBuddyBaseInfo*)(respbuf + sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC)); |     sBuddyBaseInfo* respdata = (sBuddyBaseInfo*)(respbuf + sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC)); | ||||||
|   | |||||||
| @@ -178,9 +178,9 @@ void Buffs::tickDrain(EntityRef self, Buff* buff, int mult) { | |||||||
|     int dealt = combatant->takeDamage(buff->getLastSource(), damage); |     int dealt = combatant->takeDamage(buff->getLastSource(), damage); | ||||||
|  |  | ||||||
|     size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); |     size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); | ||||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE - 8); |     assert(resplen < CN_PACKET_BODY_SIZE); | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; |     sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; | ||||||
|     pkt->iID = self.id; |     pkt->iID = self.id; | ||||||
|   | |||||||
							
								
								
									
										32
									
								
								src/Chat.cpp
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/Chat.cpp
									
									
									
									
									
								
							| @@ -1,6 +1,7 @@ | |||||||
| #include "Chat.hpp" | #include "Chat.hpp" | ||||||
|  |  | ||||||
| #include "servers/CNShardServer.hpp" | #include "servers/CNShardServer.hpp" | ||||||
|  | #include "servers/Monitor.hpp" | ||||||
|  |  | ||||||
| #include "Player.hpp" | #include "Player.hpp" | ||||||
| #include "PlayerManager.hpp" | #include "PlayerManager.hpp" | ||||||
| @@ -8,8 +9,6 @@ | |||||||
|  |  | ||||||
| #include <assert.h> | #include <assert.h> | ||||||
|  |  | ||||||
| std::vector<std::string> Chat::dump; |  | ||||||
|  |  | ||||||
| using namespace Chat; | using namespace Chat; | ||||||
|  |  | ||||||
| static void chatHandler(CNSocket* sock, CNPacketData* data) { | static void chatHandler(CNSocket* sock, CNPacketData* data) { | ||||||
| @@ -28,7 +27,7 @@ static void chatHandler(CNSocket* sock, CNPacketData* data) { | |||||||
|     std::string logLine = "[FreeChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; |     std::string logLine = "[FreeChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; | ||||||
|  |  | ||||||
|     std::cout << logLine << std::endl; |     std::cout << logLine << std::endl; | ||||||
|     dump.push_back(logLine); |     Monitor::chats.push_back(logLine); | ||||||
|  |  | ||||||
|     // send to client |     // send to client | ||||||
|     INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp); |     INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp); | ||||||
| @@ -51,7 +50,7 @@ static void menuChatHandler(CNSocket* sock, CNPacketData* data) { | |||||||
|     std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; |     std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; | ||||||
|  |  | ||||||
|     std::cout << logLine << std::endl; |     std::cout << logLine << std::endl; | ||||||
|     dump.push_back(logLine); |     Monitor::chats.push_back(logLine); | ||||||
|  |  | ||||||
|     // send to client |     // send to client | ||||||
|     INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp); |     INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp); | ||||||
| @@ -103,14 +102,14 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) { | |||||||
|     memcpy(msg.szAnnounceMsg, announcement->szAnnounceMsg, sizeof(msg.szAnnounceMsg)); |     memcpy(msg.szAnnounceMsg, announcement->szAnnounceMsg, sizeof(msg.szAnnounceMsg)); | ||||||
|     std::map<CNSocket*, Player*>::iterator it; |     std::map<CNSocket*, Player*>::iterator it; | ||||||
|  |  | ||||||
|  |     // This value is completely arbitrary, but these make the most sense when you consider the architecture of the game | ||||||
|     switch (announcement->iAreaType) { |     switch (announcement->iAreaType) { | ||||||
|     case 0: // area (all players in viewable chunks) |     case 0: // area (all players in viewable chunks) | ||||||
|         sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); |         sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); | ||||||
|         PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE); |         PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE); | ||||||
|         break; |         break; | ||||||
|     case 1: // shard |     case 1: // channel | ||||||
|     case 2: // world |     case 2: // shard | ||||||
|         break; // not applicable to OpenFusion |  | ||||||
|     case 3: // global (all players) |     case 3: // global (all players) | ||||||
|         for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) { |         for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) { | ||||||
|             CNSocket* allSock = it->first; |             CNSocket* allSock = it->first; | ||||||
| @@ -120,9 +119,12 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) { | |||||||
|         break; |         break; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     std::string logLine = "[Bcast " + std::to_string(announcement->iAreaType) + "] " + PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg); |     std::string logLine = std::to_string(announcement->iAreaType) + " " | ||||||
|     std::cout << logLine << std::endl; |                         + std::to_string(announcement->iAnnounceType) + " " | ||||||
|     dump.push_back("**" + logLine + "**"); |                         + std::to_string(announcement->iDuringTime) + " " | ||||||
|  |                         + PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg); | ||||||
|  |     std::cout << "Broadcast " << logLine << std::endl; | ||||||
|  |     Monitor::bcasts.push_back(logLine); | ||||||
| } | } | ||||||
|  |  | ||||||
| // Buddy freechatting | // Buddy freechatting | ||||||
| @@ -155,7 +157,7 @@ static void buddyChatHandler(CNSocket* sock, CNPacketData* data) { | |||||||
|  |  | ||||||
|     std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; |     std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; | ||||||
|     std::cout << logLine << std::endl; |     std::cout << logLine << std::endl; | ||||||
|     dump.push_back(logLine); |     Monitor::chats.push_back(logLine); | ||||||
|  |  | ||||||
|     U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat)); |     U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat)); | ||||||
|  |  | ||||||
| @@ -185,7 +187,7 @@ static void buddyMenuChatHandler(CNSocket* sock, CNPacketData* data) { | |||||||
|     std::string logLine = "[BuddyMenuChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; |     std::string logLine = "[BuddyMenuChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; | ||||||
|  |  | ||||||
|     std::cout << logLine << std::endl; |     std::cout << logLine << std::endl; | ||||||
|     dump.push_back(logLine); |     Monitor::chats.push_back(logLine); | ||||||
|  |  | ||||||
|     U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat)); |     U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat)); | ||||||
|  |  | ||||||
| @@ -218,7 +220,7 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) { | |||||||
|     std::string logLine = "[TradeChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; |     std::string logLine = "[TradeChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; | ||||||
|  |  | ||||||
|     std::cout << logLine << std::endl; |     std::cout << logLine << std::endl; | ||||||
|     dump.push_back(logLine); |     Monitor::chats.push_back(logLine); | ||||||
|  |  | ||||||
|     resp.iEmoteCode = pacdat->iEmoteCode; |     resp.iEmoteCode = pacdat->iEmoteCode; | ||||||
|     sock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT); |     sock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT); | ||||||
| @@ -241,7 +243,7 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) { | |||||||
|  |  | ||||||
|     std::string logLine = "[GroupChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; |     std::string logLine = "[GroupChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; | ||||||
|     std::cout << logLine << std::endl; |     std::cout << logLine << std::endl; | ||||||
|     dump.push_back(logLine); |     Monitor::chats.push_back(logLine); | ||||||
|  |  | ||||||
|     // send to client |     // send to client | ||||||
|     INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp); |     INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp); | ||||||
| @@ -264,7 +266,7 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) { | |||||||
|     std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; |     std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; | ||||||
|  |  | ||||||
|     std::cout << logLine << std::endl; |     std::cout << logLine << std::endl; | ||||||
|     dump.push_back(logLine); |     Monitor::chats.push_back(logLine); | ||||||
|  |  | ||||||
|     // send to client |     // send to client | ||||||
|     INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp); |     INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp); | ||||||
|   | |||||||
| @@ -8,7 +8,6 @@ | |||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| namespace Chat { | namespace Chat { | ||||||
|     extern std::vector<std::string> dump; |  | ||||||
|     void init(); |     void init(); | ||||||
|  |  | ||||||
|     void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD |     void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD | ||||||
|   | |||||||
							
								
								
									
										163
									
								
								src/Chunking.cpp
									
									
									
									
									
								
							
							
						
						
									
										163
									
								
								src/Chunking.cpp
									
									
									
									
									
								
							| @@ -1,7 +1,9 @@ | |||||||
| #include "Chunking.hpp" | #include "Chunking.hpp" | ||||||
|  |  | ||||||
|  | #include "Player.hpp" | ||||||
| #include "MobAI.hpp" | #include "MobAI.hpp" | ||||||
| #include "NPCManager.hpp" | #include "NPCManager.hpp" | ||||||
|  | #include "Bucket.hpp" | ||||||
|  |  | ||||||
| #include <assert.h> | #include <assert.h> | ||||||
|  |  | ||||||
| @@ -11,6 +13,12 @@ using namespace Chunking; | |||||||
|  * The initial chunkPos value before a player is placed into the world. |  * The initial chunkPos value before a player is placed into the world. | ||||||
|  */ |  */ | ||||||
| const ChunkPos Chunking::INVALID_CHUNK = {}; | const ChunkPos Chunking::INVALID_CHUNK = {}; | ||||||
|  | constexpr size_t MAX_PC_PER_AROUND = (CN_PACKET_BODY_SIZE - sizeof(int32_t)) / sizeof(sPCAppearanceData); | ||||||
|  | constexpr size_t MAX_NPC_PER_AROUND = (CN_PACKET_BODY_SIZE - sizeof(int32_t)) / sizeof(sNPCAppearanceData); | ||||||
|  | constexpr size_t MAX_SHINY_PER_AROUND = (CN_PACKET_BODY_SIZE - sizeof(int32_t)) / sizeof(sShinyAppearanceData); | ||||||
|  | constexpr size_t MAX_TRANSPORTATION_PER_AROUND = (CN_PACKET_BODY_SIZE - sizeof(int32_t)) / sizeof(sTransportationAppearanceData); | ||||||
|  | constexpr size_t MAX_IDS_PER_AROUND_DEL = (CN_PACKET_BODY_SIZE - sizeof(int32_t)) / sizeof(int32_t); | ||||||
|  | constexpr size_t MAX_TRANSPORTATION_IDS_PER_AROUND_DEL = MAX_IDS_PER_AROUND_DEL - 1; // 1 less for eTT | ||||||
|  |  | ||||||
| std::map<ChunkPos, Chunk*> Chunking::chunks; | std::map<ChunkPos, Chunk*> Chunking::chunks; | ||||||
|  |  | ||||||
| @@ -75,11 +83,80 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) { | |||||||
|         deleteChunk(chunkPos); |         deleteChunk(chunkPos); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | template<class T, size_t N> | ||||||
|  | static void sendAroundPackets(const EntityRef recipient, std::vector<Bucket<T, N>>& buckets, uint32_t packetId) { | ||||||
|  |     assert(recipient.kind == EntityKind::PLAYER); | ||||||
|  |  | ||||||
|  |     uint8_t pktBuf[CN_PACKET_BODY_SIZE]; | ||||||
|  |     for (const auto& bucket : buckets) { | ||||||
|  |         memset(pktBuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |         int count = bucket.size(); | ||||||
|  |         *((int32_t*)pktBuf) = count; | ||||||
|  |         T* data = (T*)(pktBuf + sizeof(int32_t)); | ||||||
|  |         for (size_t i = 0; i < count; i++) { | ||||||
|  |             data[i] = bucket.get(i).value(); | ||||||
|  |         } | ||||||
|  |         recipient.sock->sendPacket(pktBuf, packetId, sizeof(int32_t) + (count * sizeof(T))); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template<size_t N> | ||||||
|  | static void sendAroundDelPackets(const EntityRef recipient, std::vector<Bucket<int32_t, N>>& buckets, uint32_t packetId) { | ||||||
|  |     assert(recipient.kind == EntityKind::PLAYER); | ||||||
|  |  | ||||||
|  |     uint8_t pktBuf[CN_PACKET_BODY_SIZE]; | ||||||
|  |     for (const auto& bucket : buckets) { | ||||||
|  |         memset(pktBuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |         int count = bucket.size(); | ||||||
|  |         assert(count <= N); | ||||||
|  |  | ||||||
|  |         size_t baseSize; | ||||||
|  |         if (packetId == P_FE2CL_AROUND_DEL_TRANSPORTATION) { | ||||||
|  |             sP_FE2CL_AROUND_DEL_TRANSPORTATION* pkt = (sP_FE2CL_AROUND_DEL_TRANSPORTATION*)pktBuf; | ||||||
|  |             pkt->eTT = 3; | ||||||
|  |             pkt->iCnt = count; | ||||||
|  |             baseSize = sizeof(sP_FE2CL_AROUND_DEL_TRANSPORTATION); | ||||||
|  |         } else { | ||||||
|  |             *((int32_t*)pktBuf) = count; | ||||||
|  |             baseSize = sizeof(int32_t); | ||||||
|  |         } | ||||||
|  |         int32_t* ids = (int32_t*)(pktBuf + baseSize); | ||||||
|  |  | ||||||
|  |         for (size_t i = 0; i < count; i++) { | ||||||
|  |             ids[i] = bucket.get(i).value(); | ||||||
|  |         } | ||||||
|  |         recipient.sock->sendPacket(pktBuf, packetId, baseSize + (count * sizeof(int32_t))); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template<class T, size_t N> | ||||||
|  | static void bufferAppearanceData(std::vector<Bucket<T, N>>& buckets, const T& data) { | ||||||
|  |     if (buckets.empty()) | ||||||
|  |         buckets.push_back({}); | ||||||
|  |     auto& bucket = buckets[buckets.size() - 1]; | ||||||
|  |     bucket.add(data); | ||||||
|  |     if (bucket.isFull()) | ||||||
|  |         buckets.push_back({}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | template<size_t N> | ||||||
|  | static void bufferIdForDisappearance(std::vector<Bucket<int32_t, N>>& buckets, int32_t id) { | ||||||
|  |     if (buckets.empty()) | ||||||
|  |         buckets.push_back({}); | ||||||
|  |     auto& bucket = buckets[buckets.size() - 1]; | ||||||
|  |     bucket.add(id); | ||||||
|  |     if (bucket.isFull()) | ||||||
|  |         buckets.push_back({}); | ||||||
|  | } | ||||||
|  |  | ||||||
| void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) { | void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) { | ||||||
|     Entity *ent = ref.getEntity(); |     Entity *ent = ref.getEntity(); | ||||||
|     bool alive = ent->isExtant(); |     bool alive = ent->isExtant(); | ||||||
|  |  | ||||||
|     // TODO: maybe optimize this, potentially using AROUND packets? |     std::vector<Bucket<sPCAppearanceData, MAX_PC_PER_AROUND>> pcAppearances; | ||||||
|  |     std::vector<Bucket<sNPCAppearanceData, MAX_NPC_PER_AROUND>> npcAppearances; | ||||||
|  |     std::vector<Bucket<sShinyAppearanceData, MAX_SHINY_PER_AROUND>> shinyAppearances; | ||||||
|  |     std::vector<Bucket<sTransportationAppearanceData, MAX_TRANSPORTATION_PER_AROUND>> transportationAppearances; | ||||||
|     for (Chunk *chunk : chnks) { |     for (Chunk *chunk : chnks) { | ||||||
|         for (const EntityRef otherRef : chunk->entities) { |         for (const EntityRef otherRef : chunk->entities) { | ||||||
|             // skip oneself |             // skip oneself | ||||||
| @@ -95,7 +172,38 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) { | |||||||
|  |  | ||||||
|             // notify this *player* of the existence of all visible Entities |             // notify this *player* of the existence of all visible Entities | ||||||
|             if (ref.kind == EntityKind::PLAYER && other->isExtant()) { |             if (ref.kind == EntityKind::PLAYER && other->isExtant()) { | ||||||
|                 other->enterIntoViewOf(ref.sock); |                 sPCAppearanceData pcData; | ||||||
|  |                 sNPCAppearanceData npcData; | ||||||
|  |                 sShinyAppearanceData eggData; | ||||||
|  |                 sTransportationAppearanceData busData; | ||||||
|  |                 switch(otherRef.kind) { | ||||||
|  |                 case EntityKind::PLAYER: | ||||||
|  |                     pcData = dynamic_cast<Player*>(other)->getAppearanceData(); | ||||||
|  |                     bufferAppearanceData(pcAppearances, pcData); | ||||||
|  |                     break; | ||||||
|  |                 case EntityKind::SIMPLE_NPC: | ||||||
|  |                     npcData = dynamic_cast<BaseNPC*>(other)->getAppearanceData(); | ||||||
|  |                     bufferAppearanceData(npcAppearances, npcData); | ||||||
|  |                     break; | ||||||
|  |                 case EntityKind::COMBAT_NPC: | ||||||
|  |                     npcData = dynamic_cast<CombatNPC*>(other)->getAppearanceData(); | ||||||
|  |                     bufferAppearanceData(npcAppearances, npcData); | ||||||
|  |                     break; | ||||||
|  |                 case EntityKind::MOB: | ||||||
|  |                     npcData = dynamic_cast<Mob*>(other)->getAppearanceData(); | ||||||
|  |                     bufferAppearanceData(npcAppearances, npcData); | ||||||
|  |                     break; | ||||||
|  |                 case EntityKind::EGG: | ||||||
|  |                     eggData = dynamic_cast<Egg*>(other)->getShinyAppearanceData(); | ||||||
|  |                     bufferAppearanceData(shinyAppearances, eggData); | ||||||
|  |                     break; | ||||||
|  |                 case EntityKind::BUS: | ||||||
|  |                     busData = dynamic_cast<Bus*>(other)->getTransportationAppearanceData(); | ||||||
|  |                     bufferAppearanceData(transportationAppearances, busData); | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // for mobs, increment playersInView |             // for mobs, increment playersInView | ||||||
| @@ -105,13 +213,27 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) { | |||||||
|                 ((Mob*)other)->playersInView++; |                 ((Mob*)other)->playersInView++; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (ref.kind == EntityKind::PLAYER) { | ||||||
|  |         if (!pcAppearances.empty()) | ||||||
|  |             sendAroundPackets(ref, pcAppearances, P_FE2CL_PC_AROUND); | ||||||
|  |         if (!npcAppearances.empty()) | ||||||
|  |             sendAroundPackets(ref, npcAppearances, P_FE2CL_NPC_AROUND); | ||||||
|  |         if (!shinyAppearances.empty()) | ||||||
|  |             sendAroundPackets(ref, shinyAppearances, P_FE2CL_SHINY_AROUND); | ||||||
|  |         if (!transportationAppearances.empty()) | ||||||
|  |             sendAroundPackets(ref, transportationAppearances, P_FE2CL_TRANSPORTATION_AROUND); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref) { | void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref) { | ||||||
|     Entity *ent = ref.getEntity(); |     Entity *ent = ref.getEntity(); | ||||||
|     bool alive = ent->isExtant(); |     bool alive = ent->isExtant(); | ||||||
|  |  | ||||||
|     // TODO: same as above |     std::vector<Bucket<int32_t, MAX_IDS_PER_AROUND_DEL>> pcDisappearances; | ||||||
|  |     std::vector<Bucket<int32_t, MAX_IDS_PER_AROUND_DEL>> npcDisappearances; | ||||||
|  |     std::vector<Bucket<int32_t, MAX_IDS_PER_AROUND_DEL>> shinyDisappearances; | ||||||
|  |     std::vector<Bucket<int32_t, MAX_TRANSPORTATION_IDS_PER_AROUND_DEL>> transportationDisappearances; | ||||||
|     for (Chunk *chunk : chnks) { |     for (Chunk *chunk : chnks) { | ||||||
|         for (const EntityRef otherRef : chunk->entities) { |         for (const EntityRef otherRef : chunk->entities) { | ||||||
|             // skip oneself |             // skip oneself | ||||||
| @@ -127,7 +249,29 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef re | |||||||
|  |  | ||||||
|             // notify this *player* of the departure of all visible Entities |             // notify this *player* of the departure of all visible Entities | ||||||
|             if (ref.kind == EntityKind::PLAYER && other->isExtant()) { |             if (ref.kind == EntityKind::PLAYER && other->isExtant()) { | ||||||
|                 other->disappearFromViewOf(ref.sock); |                 int32_t id; | ||||||
|  |                 switch(otherRef.kind) { | ||||||
|  |                 case EntityKind::PLAYER: | ||||||
|  |                     id = dynamic_cast<Player*>(other)->iID; | ||||||
|  |                     bufferIdForDisappearance(pcDisappearances, id); | ||||||
|  |                     break; | ||||||
|  |                 case EntityKind::SIMPLE_NPC: | ||||||
|  |                 case EntityKind::COMBAT_NPC: | ||||||
|  |                 case EntityKind::MOB: | ||||||
|  |                     id = dynamic_cast<BaseNPC*>(other)->id; | ||||||
|  |                     bufferIdForDisappearance(npcDisappearances, id); | ||||||
|  |                     break; | ||||||
|  |                 case EntityKind::EGG: | ||||||
|  |                     id = dynamic_cast<Egg*>(other)->id; | ||||||
|  |                     bufferIdForDisappearance(shinyDisappearances, id); | ||||||
|  |                     break; | ||||||
|  |                 case EntityKind::BUS: | ||||||
|  |                     id = dynamic_cast<Bus*>(other)->id; | ||||||
|  |                     bufferIdForDisappearance(transportationDisappearances, id); | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             // for mobs, decrement playersInView |             // for mobs, decrement playersInView | ||||||
| @@ -137,6 +281,17 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef re | |||||||
|                 ((Mob*)other)->playersInView--; |                 ((Mob*)other)->playersInView--; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (ref.kind == EntityKind::PLAYER) { | ||||||
|  |         if (!pcDisappearances.empty()) | ||||||
|  |             sendAroundDelPackets(ref, pcDisappearances, P_FE2CL_AROUND_DEL_PC); | ||||||
|  |         if (!npcDisappearances.empty()) | ||||||
|  |             sendAroundDelPackets(ref, npcDisappearances, P_FE2CL_AROUND_DEL_NPC); | ||||||
|  |         if (!shinyDisappearances.empty()) | ||||||
|  |             sendAroundDelPackets(ref, shinyDisappearances, P_FE2CL_AROUND_DEL_SHINY); | ||||||
|  |         if (!transportationDisappearances.empty()) | ||||||
|  |             sendAroundDelPackets(ref, transportationDisappearances, P_FE2CL_AROUND_DEL_TRANSPORTATION); | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| static void emptyChunk(ChunkPos chunkPos) { | static void emptyChunk(ChunkPos chunkPos) { | ||||||
|   | |||||||
| @@ -539,9 +539,9 @@ static void dealGooDamage(CNSocket *sock) { | |||||||
|         return; // ignore completely |         return; // ignore completely | ||||||
|  |  | ||||||
|     size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage); |     size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage); | ||||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE - 8); |     assert(resplen < CN_PACKET_BODY_SIZE); | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; |     sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; | ||||||
|     sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); |     sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); | ||||||
| @@ -633,9 +633,9 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) { | |||||||
|  |  | ||||||
|     // initialize response struct |     // initialize response struct | ||||||
|     size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC) + pkt->iTargetCnt * sizeof(sAttackResult); |     size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC) + pkt->iTargetCnt * sizeof(sAttackResult); | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|  |  | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     sP_FE2CL_PC_ATTACK_CHARs_SUCC *resp = (sP_FE2CL_PC_ATTACK_CHARs_SUCC*)respbuf; |     sP_FE2CL_PC_ATTACK_CHARs_SUCC *resp = (sP_FE2CL_PC_ATTACK_CHARs_SUCC*)respbuf; | ||||||
|     sAttackResult *respdata = (sAttackResult*)(respbuf+sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC)); |     sAttackResult *respdata = (sAttackResult*)(respbuf+sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC)); | ||||||
| @@ -847,9 +847,9 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) { | |||||||
|      */ |      */ | ||||||
|  |  | ||||||
|     size_t resplen = sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT) + pkt->iTargetCnt * sizeof(sAttackResult); |     size_t resplen = sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT) + pkt->iTargetCnt * sizeof(sAttackResult); | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|  |  | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     sP_FE2CL_PC_GRENADE_STYLE_HIT* resp = (sP_FE2CL_PC_GRENADE_STYLE_HIT*)respbuf; |     sP_FE2CL_PC_GRENADE_STYLE_HIT* resp = (sP_FE2CL_PC_GRENADE_STYLE_HIT*)respbuf; | ||||||
|     sAttackResult* respdata = (sAttackResult*)(respbuf + sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT)); |     sAttackResult* respdata = (sAttackResult*)(respbuf + sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT)); | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								src/Eggs.cpp
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								src/Eggs.cpp
									
									
									
									
									
								
							| @@ -87,8 +87,8 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) { | |||||||
|  |  | ||||||
|     // initialize response struct |     // initialize response struct | ||||||
|     size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size; |     size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size; | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; |     sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; | ||||||
|     pkt->iNPC_ID = eggId; |     pkt->iNPC_ID = eggId; | ||||||
| @@ -126,15 +126,6 @@ static void eggStep(CNServer* serv, time_t currTime) { | |||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void Eggs::npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg) { |  | ||||||
|     egg->iX = x; |  | ||||||
|     egg->iY = y; |  | ||||||
|     egg->iZ = z; |  | ||||||
|     // client doesn't care about egg->iMapNum |  | ||||||
|     egg->iShinyType = npc->iNPCType; |  | ||||||
|     egg->iShiny_ID = npc->iNPC_ID; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| static void eggPickup(CNSocket* sock, CNPacketData* data) { | static void eggPickup(CNSocket* sock, CNPacketData* data) { | ||||||
|     auto pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf; |     auto pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf; | ||||||
|     Player* plr = PlayerManager::getPlayer(sock); |     Player* plr = PlayerManager::getPlayer(sock); | ||||||
| @@ -192,7 +183,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) { | |||||||
|     // drop |     // drop | ||||||
|     if (type->dropCrateId != 0) { |     if (type->dropCrateId != 0) { | ||||||
|         const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); |         const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); | ||||||
|         assert(resplen < CN_PACKET_BUFFER_SIZE - 8); |         assert(resplen < CN_PACKET_BODY_SIZE); | ||||||
|         // we know it's only one trailing struct, so we can skip full validation |         // we know it's only one trailing struct, so we can skip full validation | ||||||
|  |  | ||||||
|         uint8_t respbuf[resplen]; // not a variable length array, don't worry |         uint8_t respbuf[resplen]; // not a variable length array, don't worry | ||||||
|   | |||||||
| @@ -15,5 +15,4 @@ namespace Eggs { | |||||||
|     void init(); |     void init(); | ||||||
|  |  | ||||||
|     void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration); |     void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration); | ||||||
|     void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg); |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
| #include "core/Core.hpp" | #include "core/Core.hpp" | ||||||
| #include "db/Database.hpp" | #include "db/Database.hpp" | ||||||
| #include "servers/CNShardServer.hpp" | #include "servers/CNShardServer.hpp" | ||||||
|  | #include "servers/Monitor.hpp" | ||||||
|  |  | ||||||
| #include "PlayerManager.hpp" | #include "PlayerManager.hpp" | ||||||
| #include "Items.hpp" | #include "Items.hpp" | ||||||
| @@ -10,8 +11,6 @@ | |||||||
|  |  | ||||||
| using namespace Email; | using namespace Email; | ||||||
|  |  | ||||||
| std::vector<std::string> Email::dump; |  | ||||||
|  |  | ||||||
| // New email notification | // New email notification | ||||||
| static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) { | static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) { | ||||||
|     INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp); |     INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp); | ||||||
| @@ -324,7 +323,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) { | |||||||
|  |  | ||||||
|     std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody; |     std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody; | ||||||
|     std::cout << logEmail << std::endl; |     std::cout << logEmail << std::endl; | ||||||
|     dump.push_back(logEmail); |     Monitor::emails.push_back(logEmail); | ||||||
|  |  | ||||||
|     // notification to recipient if online |     // notification to recipient if online | ||||||
|     CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID); |     CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID); | ||||||
|   | |||||||
| @@ -4,7 +4,5 @@ | |||||||
| #include <string> | #include <string> | ||||||
|  |  | ||||||
| namespace Email { | namespace Email { | ||||||
|     extern std::vector<std::string> dump; |  | ||||||
|      |  | ||||||
|     void init(); |     void init(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -70,11 +70,7 @@ void Bus::enterIntoViewOf(CNSocket *sock) { | |||||||
|     INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, pkt); |     INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, pkt); | ||||||
|  |  | ||||||
|     // TODO: Potentially decouple this from BaseNPC? |     // TODO: Potentially decouple this from BaseNPC? | ||||||
|     pkt.AppearanceData = { |     pkt.AppearanceData = getTransportationAppearanceData(); | ||||||
|         3, id, type, |  | ||||||
|         x, y, z |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_ENTER); |     sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_ENTER); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -82,12 +78,22 @@ void Egg::enterIntoViewOf(CNSocket *sock) { | |||||||
|     INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt); |     INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt); | ||||||
|  |  | ||||||
|     // TODO: Potentially decouple this from BaseNPC? |     // TODO: Potentially decouple this from BaseNPC? | ||||||
|     pkt.ShinyAppearanceData = { |     pkt.ShinyAppearanceData = getShinyAppearanceData(); | ||||||
|  |     sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sTransportationAppearanceData Bus::getTransportationAppearanceData() { | ||||||
|  |     return sTransportationAppearanceData { | ||||||
|  |         3, id, type, | ||||||
|  |         x, y, z | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | sShinyAppearanceData Egg::getShinyAppearanceData() { | ||||||
|  |     return sShinyAppearanceData { | ||||||
|         id, type, 0, // client doesn't care about map num |         id, type, 0, // client doesn't care about map num | ||||||
|         x, y, z |         x, y, z | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| sNano* Player::getActiveNano() { | sNano* Player::getActiveNano() { | ||||||
|   | |||||||
| @@ -161,6 +161,8 @@ struct Egg : public BaseNPC { | |||||||
|  |  | ||||||
|     virtual void enterIntoViewOf(CNSocket *sock) override; |     virtual void enterIntoViewOf(CNSocket *sock) override; | ||||||
|     virtual void disappearFromViewOf(CNSocket *sock) override; |     virtual void disappearFromViewOf(CNSocket *sock) override; | ||||||
|  |  | ||||||
|  |     sShinyAppearanceData getShinyAppearanceData(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct Bus : public BaseNPC { | struct Bus : public BaseNPC { | ||||||
| @@ -172,4 +174,6 @@ struct Bus : public BaseNPC { | |||||||
|  |  | ||||||
|     virtual void enterIntoViewOf(CNSocket *sock) override; |     virtual void enterIntoViewOf(CNSocket *sock) override; | ||||||
|     virtual void disappearFromViewOf(CNSocket *sock) override; |     virtual void disappearFromViewOf(CNSocket *sock) override; | ||||||
|  |  | ||||||
|  |     sTransportationAppearanceData getTransportationAppearanceData(); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -87,8 +87,8 @@ void Groups::addToGroup(Group* group, EntityRef member) { | |||||||
|         size_t pcCount = pcs.size(); |         size_t pcCount = pcs.size(); | ||||||
|         size_t npcCount = npcs.size(); |         size_t npcCount = npcs.size(); | ||||||
|  |  | ||||||
|         uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |         uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|         memset(respbuf, 0, CN_PACKET_BUFFER_SIZE); |         memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|         sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf; |         sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf; | ||||||
|  |  | ||||||
|         pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID; |         pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID; | ||||||
| @@ -143,8 +143,8 @@ bool Groups::removeFromGroup(Group* group, EntityRef member) { | |||||||
|         size_t pcCount = pcs.size(); |         size_t pcCount = pcs.size(); | ||||||
|         size_t npcCount = npcs.size(); |         size_t npcCount = npcs.size(); | ||||||
|  |  | ||||||
|         uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |         uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|         memset(respbuf, 0, CN_PACKET_BUFFER_SIZE); |         memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|         sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf; |         sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf; | ||||||
|  |  | ||||||
|         pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID; |         pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID; | ||||||
| @@ -288,8 +288,8 @@ void Groups::groupTickInfo(CNSocket* sock) { | |||||||
|     size_t pcCount = pcs.size(); |     size_t pcCount = pcs.size(); | ||||||
|     size_t npcCount = npcs.size(); |     size_t npcCount = npcs.size(); | ||||||
|  |  | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|     memset(respbuf, 0, CN_PACKET_BUFFER_SIZE); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|     sP_FE2CL_PC_GROUP_MEMBER_INFO* pkt = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf; |     sP_FE2CL_PC_GROUP_MEMBER_INFO* pkt = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf; | ||||||
|  |  | ||||||
|     pkt->iID = plr->iID; |     pkt->iID = plr->iID; | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ static void nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest) { | |||||||
|  |  | ||||||
|     // in order to remove capsule form inventory, we have to send item reward packet with empty item |     // in order to remove capsule form inventory, we have to send item reward packet with empty item | ||||||
|     const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); |     const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); | ||||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE - 8); |     assert(resplen < CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     // we know it's only one trailing struct, so we can skip full validation |     // we know it's only one trailing struct, so we can skip full validation | ||||||
|     uint8_t respbuf[resplen]; // not a variable length array, don't worry |     uint8_t respbuf[resplen]; // not a variable length array, don't worry | ||||||
| @@ -475,8 +475,8 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) { | |||||||
|     if (gumball.iOpt == 0) |     if (gumball.iOpt == 0) | ||||||
|         gumball = {}; |         gumball = {}; | ||||||
|  |  | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     sP_FE2CL_REP_PC_ITEM_USE_SUCC *resp = (sP_FE2CL_REP_PC_ITEM_USE_SUCC*)respbuf; |     sP_FE2CL_REP_PC_ITEM_USE_SUCC *resp = (sP_FE2CL_REP_PC_ITEM_USE_SUCC*)respbuf; | ||||||
|     sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC)); |     sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC)); | ||||||
| @@ -556,7 +556,7 @@ static void chestOpenHandler(CNSocket *sock, CNPacketData *data) { | |||||||
|  |  | ||||||
|     // item giving packet |     // item giving packet | ||||||
|     const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); |     const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); | ||||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE - 8); |     assert(resplen < CN_PACKET_BODY_SIZE); | ||||||
|     // we know it's only one trailing struct, so we can skip full validation |     // we know it's only one trailing struct, so we can skip full validation | ||||||
|  |  | ||||||
|     uint8_t respbuf[resplen]; // not a variable length array, don't worry |     uint8_t respbuf[resplen]; // not a variable length array, don't worry | ||||||
| @@ -645,7 +645,7 @@ void Items::checkItemExpire(CNSocket* sock, Player* player) { | |||||||
|     */ |     */ | ||||||
|  |  | ||||||
|     const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL); |     const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL); | ||||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE - 8); |     assert(resplen < CN_PACKET_BODY_SIZE); | ||||||
|     // we know it's only one trailing struct, so we can skip full validation |     // we know it's only one trailing struct, so we can skip full validation | ||||||
|     uint8_t respbuf[resplen]; // not a variable length array, don't worry |     uint8_t respbuf[resplen]; // not a variable length array, don't worry | ||||||
|     auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf; |     auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf; | ||||||
| @@ -715,7 +715,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo | |||||||
|     Player *plr = PlayerManager::getPlayer(sock); |     Player *plr = PlayerManager::getPlayer(sock); | ||||||
|  |  | ||||||
|     const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); |     const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); | ||||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE - 8); |     assert(resplen < CN_PACKET_BODY_SIZE); | ||||||
|     // we know it's only one trailing struct, so we can skip full validation |     // we know it's only one trailing struct, so we can skip full validation | ||||||
|  |  | ||||||
|     uint8_t respbuf[resplen]; // not a variable length array, don't worry |     uint8_t respbuf[resplen]; // not a variable length array, don't worry | ||||||
|   | |||||||
| @@ -64,7 +64,7 @@ static bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount) { | |||||||
| static void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid) { | static void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid) { | ||||||
|     std::cout << "Altered item id " << id << " by " << count << " for task id " << task << std::endl; |     std::cout << "Altered item id " << id << " by " << count << " for task id " << task << std::endl; | ||||||
|     const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); |     const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); | ||||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE); |     assert(resplen < CN_PACKET_BODY_SIZE); | ||||||
|     // we know it's only one trailing struct, so we can skip full validation |     // we know it's only one trailing struct, so we can skip full validation | ||||||
|  |  | ||||||
|     Player *plr = PlayerManager::getPlayer(sock); |     Player *plr = PlayerManager::getPlayer(sock); | ||||||
| @@ -152,14 +152,14 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) { | |||||||
|         plr->Inven[slots[i]] = { 999, 999, 999, 0 }; // temp item; overwritten later |         plr->Inven[slots[i]] = { 999, 999, 999, 0 }; // temp item; overwritten later | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|     size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + nrewards * sizeof(sItemReward); |     size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + nrewards * sizeof(sItemReward); | ||||||
|     assert(resplen < CN_PACKET_BUFFER_SIZE); |     assert(resplen < CN_PACKET_BODY_SIZE); | ||||||
|     sP_FE2CL_REP_REWARD_ITEM *resp = (sP_FE2CL_REP_REWARD_ITEM *)respbuf; |     sP_FE2CL_REP_REWARD_ITEM *resp = (sP_FE2CL_REP_REWARD_ITEM *)respbuf; | ||||||
|     sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM)); |     sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM)); | ||||||
|  |  | ||||||
|     // don't forget to zero the buffer! |     // don't forget to zero the buffer! | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     // update player |     // update player | ||||||
|     plr->money += reward->money; |     plr->money += reward->money; | ||||||
|   | |||||||
| @@ -238,8 +238,8 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     uint8_t respbuf[CN_PACKET_BUFFER_SIZE]; |     uint8_t respbuf[CN_PACKET_BODY_SIZE]; | ||||||
|     memset(respbuf, 0, resplen); |     memset(respbuf, 0, CN_PACKET_BODY_SIZE); | ||||||
|  |  | ||||||
|     sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf; |     sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf; | ||||||
|     sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT)); |     sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT)); | ||||||
| @@ -478,6 +478,14 @@ void MobAI::deadStep(CombatNPC* npc, time_t currTime) { | |||||||
|     if (self->groupLeader == self->id) |     if (self->groupLeader == self->id) | ||||||
|         roamingStep(self, currTime); |         roamingStep(self, currTime); | ||||||
|  |  | ||||||
|  |     /* | ||||||
|  |      * If the mob hasn't fully despanwed yet, don't try to respawn it. This protects | ||||||
|  |      * against the edge case where mobs with a very short regenTime would try to respawn | ||||||
|  |      * before they've faded away; and would respawn even if they were meant to be removed. | ||||||
|  |      */ | ||||||
|  |     if (!self->despawned) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|     if (self->killedTime != 0 && currTime - self->killedTime < self->regenTime * 100) |     if (self->killedTime != 0 && currTime - self->killedTime < self->regenTime * 100) | ||||||
|         return; |         return; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -155,7 +155,7 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z) { | |||||||
|  * Nanos the player hasn't unlocked will (and should) be greyed out. Thus, all nanos should be accounted |  * Nanos the player hasn't unlocked will (and should) be greyed out. Thus, all nanos should be accounted | ||||||
|  * for in these packets, even if the player hasn't unlocked them. |  * for in these packets, even if the player hasn't unlocked them. | ||||||
|  */ |  */ | ||||||
| static void sendNanoBookSubset(CNSocket *sock, Player *plr) { | static void sendNanoBook(CNSocket *sock, Player *plr, bool resizeOnly) { | ||||||
| #ifdef ACADEMY | #ifdef ACADEMY | ||||||
|     int16_t id = 0; |     int16_t id = 0; | ||||||
|     INITSTRUCT(sP_FE2CL_REP_NANO_BOOK_SUBSET, pkt); |     INITSTRUCT(sP_FE2CL_REP_NANO_BOOK_SUBSET, pkt); | ||||||
| @@ -163,6 +163,13 @@ static void sendNanoBookSubset(CNSocket *sock, Player *plr) { | |||||||
|     pkt.PCUID = plr->iID; |     pkt.PCUID = plr->iID; | ||||||
|     pkt.bookSize = NANO_COUNT; |     pkt.bookSize = NANO_COUNT; | ||||||
|  |  | ||||||
|  |     if (resizeOnly) { | ||||||
|  |         // triggers nano array resizing without | ||||||
|  |         // actually sending nanos | ||||||
|  |         sock->sendPacket(pkt, P_FE2CL_REP_NANO_BOOK_SUBSET); | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     while (id < NANO_COUNT) { |     while (id < NANO_COUNT) { | ||||||
|         pkt.elementOffset = id; |         pkt.elementOffset = id; | ||||||
|  |  | ||||||
| @@ -293,14 +300,14 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) { | |||||||
|     sock->setFEKey(lm->FEKey); |     sock->setFEKey(lm->FEKey); | ||||||
|     sock->setActiveKey(SOCKETKEY_FE); // send all packets using the FE key from now on |     sock->setActiveKey(SOCKETKEY_FE); // send all packets using the FE key from now on | ||||||
|  |  | ||||||
|     // Academy builds receive nanos in a separate packet. These need to be sent |     // Academy builds receive nanos in a separate packet. An initial one with the size of the | ||||||
|     // before P_FE2CL_REP_PC_ENTER_SUCC as well as after |     // nano book needs to be sent before PC_ENTER_SUCC so the client can resize its nano arrays, | ||||||
|     // due to a race condition in the client :( |     // and then proper packets with the nanos included must be sent after, while the game is loading. | ||||||
|     sendNanoBookSubset(sock, plr); |     sendNanoBook(sock, plr, true); | ||||||
|  |  | ||||||
|     sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC); |     sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC); | ||||||
|  |  | ||||||
|     sendNanoBookSubset(sock, plr); |     sendNanoBook(sock, plr, false); | ||||||
|  |  | ||||||
|     // transfer ownership of Player object into the shard (still valid in this function though) |     // transfer ownership of Player object into the shard (still valid in this function though) | ||||||
|     addPlayer(sock, plr); |     addPlayer(sock, plr); | ||||||
| @@ -389,6 +396,14 @@ static void heartbeatPlayer(CNSocket* sock, CNPacketData* data) { | |||||||
|  |  | ||||||
| static void exitGame(CNSocket* sock, CNPacketData* data) { | static void exitGame(CNSocket* sock, CNPacketData* data) { | ||||||
|     auto exitData = (sP_CL2FE_REQ_PC_EXIT*)data->buf; |     auto exitData = (sP_CL2FE_REQ_PC_EXIT*)data->buf; | ||||||
|  |  | ||||||
|  |     // Refresh any auth cookie, in case "change character" was used | ||||||
|  |     Player* plr = getPlayer(sock); | ||||||
|  |     if (plr != nullptr) { | ||||||
|  |         // 5 seconds should be enough to log in again | ||||||
|  |         Database::refreshCookie(plr->accountId, 5); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response); |     INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response); | ||||||
|  |  | ||||||
|     response.iID = exitData->iID; |     response.iID = exitData->iID; | ||||||
| @@ -605,6 +620,10 @@ std::string PlayerManager::getPlayerName(Player *plr, bool id) { | |||||||
|     if (plr == nullptr) |     if (plr == nullptr) | ||||||
|         return "NOT IN GAME"; |         return "NOT IN GAME"; | ||||||
|  |  | ||||||
|  |     if (plr->PCStyle.iNameCheck != 1) { | ||||||
|  |         return "Player " + std::to_string(plr->iID); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     std::string ret = ""; |     std::string ret = ""; | ||||||
|     if (id && plr->accountLevel <= 30) |     if (id && plr->accountLevel <= 30) | ||||||
|         ret += "(GM) "; |         ret += "(GM) "; | ||||||
|   | |||||||
| @@ -1,5 +1,7 @@ | |||||||
| #include "TableData.hpp" | #include "TableData.hpp" | ||||||
|  |  | ||||||
|  | #include "servers/CNLoginServer.hpp" | ||||||
|  |  | ||||||
| #include "NPCManager.hpp" | #include "NPCManager.hpp" | ||||||
| #include "Missions.hpp" | #include "Missions.hpp" | ||||||
| #include "Items.hpp" | #include "Items.hpp" | ||||||
| @@ -79,6 +81,25 @@ static void loadXDT(json& xdtData) { | |||||||
|     NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"]; |     NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"]; | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|  |         // load name wheel names | ||||||
|  |         json firstNameData = xdtData["m_pNameTable"]["m_pFirstName"]; | ||||||
|  |         for (json::iterator _name = firstNameData.begin(); _name != firstNameData.end(); _name++) { | ||||||
|  |             auto name = _name.value(); | ||||||
|  |             LoginServer::WheelFirstNames.push_back(name["m_pstrNameString"]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         json middleNameData = xdtData["m_pNameTable"]["m_pMiddleName"]; | ||||||
|  |         for (json::iterator _name = middleNameData.begin(); _name != middleNameData.end(); _name++) { | ||||||
|  |             auto name = _name.value(); | ||||||
|  |             LoginServer::WheelMiddleNames.push_back(name["m_pstrNameString"]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         json lastNameData = xdtData["m_pNameTable"]["m_pLastName"]; | ||||||
|  |         for (json::iterator _name = lastNameData.begin(); _name != lastNameData.end(); _name++) { | ||||||
|  |             auto name = _name.value(); | ||||||
|  |             LoginServer::WheelLastNames.push_back(name["m_pstrNameString"]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // load warps |         // load warps | ||||||
|         json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"]; |         json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"]; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -388,8 +388,21 @@ NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) { | |||||||
|  |  | ||||||
| void Transport::constructPathNPC(int32_t id, NPCPath* path) { | void Transport::constructPathNPC(int32_t id, NPCPath* path) { | ||||||
|     BaseNPC* npc = NPCManager::NPCs[id]; |     BaseNPC* npc = NPCManager::NPCs[id]; | ||||||
|     if (npc->kind == EntityKind::MOB) |  | ||||||
|         ((Mob*)(npc))->staticPath = true; |     if (npc->kind == EntityKind::MOB) { | ||||||
|  |         auto mob = (Mob*)npc; | ||||||
|  |         mob->staticPath = true; | ||||||
|  |  | ||||||
|  |         Vec3 firstPoint = path->points.front(); | ||||||
|  |  | ||||||
|  |         // Ensure that the first point coincides with the mob's spawn point. | ||||||
|  |         if (mob->spawnX != firstPoint.x || mob->spawnY != firstPoint.y) { | ||||||
|  |             std::cout << "[FATAL] The first point of the route for mob " << mob->id << " (type " << mob->type | ||||||
|  |                 << ") does not correspond with its spawn point." << std::endl; | ||||||
|  |             exit(1); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     npc->loopingPath = path->isLoop; |     npc->loopingPath = path->isLoop; | ||||||
|  |  | ||||||
|     // Interpolate |     // Interpolate | ||||||
|   | |||||||
| @@ -428,7 +428,7 @@ void CNServer::removePollFD(int fd) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void CNServer::start() { | void CNServer::start() { | ||||||
|     std::cout << "Starting server at *:" << port << std::endl; |     std::cout << "Starting " << serverType << " server at *:" << port << std::endl; | ||||||
|     while (active) { |     while (active) { | ||||||
|         // the timeout is to ensure shard timers are ticking |         // the timeout is to ensure shard timers are ticking | ||||||
|         int n = poll(fds.data(), fds.size(), 50); |         int n = poll(fds.data(), fds.size(), 50); | ||||||
|   | |||||||
| @@ -95,14 +95,14 @@ inline constexpr bool isOutboundPacketID(uint32_t id) { | |||||||
| // for outbound packets | // for outbound packets | ||||||
| inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t plsize) { | inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t plsize) { | ||||||
|     // check for multiplication overflow |     // check for multiplication overflow | ||||||
|     if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize) |     if (npayloads > 0 && (CN_PACKET_BODY_SIZE) / (size_t)npayloads < plsize) | ||||||
|         return false; |         return false; | ||||||
|  |  | ||||||
|     // it's safe to multiply |     // it's safe to multiply | ||||||
|     size_t trailing = npayloads * plsize; |     size_t trailing = npayloads * plsize; | ||||||
|  |  | ||||||
|     // does it fit in a packet? |     // does it fit in a packet? | ||||||
|     if (base + trailing > CN_PACKET_BUFFER_SIZE - 8) |     if (base + trailing > CN_PACKET_BODY_SIZE) | ||||||
|         return false; |         return false; | ||||||
|  |  | ||||||
|     // everything is a-ok! |     // everything is a-ok! | ||||||
| @@ -112,14 +112,14 @@ inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t pl | |||||||
| // for inbound packets | // for inbound packets | ||||||
| inline constexpr bool validInVarPacket(size_t base, size_t npayloads, size_t plsize, size_t datasize) { | inline constexpr bool validInVarPacket(size_t base, size_t npayloads, size_t plsize, size_t datasize) { | ||||||
|     // check for multiplication overflow |     // check for multiplication overflow | ||||||
|     if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize) |     if (npayloads > 0 && CN_PACKET_BODY_SIZE / (size_t)npayloads < plsize) | ||||||
|         return false; |         return false; | ||||||
|  |  | ||||||
|     // it's safe to multiply |     // it's safe to multiply | ||||||
|     size_t trailing = npayloads * plsize; |     size_t trailing = npayloads * plsize; | ||||||
|  |  | ||||||
|     // make sure size is exact |     // make sure size is exact | ||||||
|     // datasize has already been validated against CN_PACKET_BUFFER_SIZE |     // datasize has already been validated against CN_PACKET_BODY_SIZE | ||||||
|     if (datasize != base + trailing) |     if (datasize != base + trailing) | ||||||
|         return false; |         return false; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -29,8 +29,8 @@ | |||||||
| #define INITSTRUCT(T, x) T x; \ | #define INITSTRUCT(T, x) T x; \ | ||||||
|     memset(&x, 0, sizeof(T)); |     memset(&x, 0, sizeof(T)); | ||||||
|  |  | ||||||
| #define INITVARPACKET(_buf, _Pkt, _pkt, _Trailer, _trailer) uint8_t _buf[CN_PACKET_BUFFER_SIZE]; \ | #define INITVARPACKET(_buf, _Pkt, _pkt, _Trailer, _trailer) uint8_t _buf[CN_PACKET_BODY_SIZE]; \ | ||||||
|     memset(&_buf, 0, CN_PACKET_BUFFER_SIZE); \ |     memset(&_buf, 0, CN_PACKET_BODY_SIZE); \ | ||||||
|     auto _pkt = (_Pkt*)_buf; \ |     auto _pkt = (_Pkt*)_buf; \ | ||||||
|     auto _trailer = (_Trailer*)(_pkt + 1); |     auto _trailer = (_Trailer*)(_pkt + 1); | ||||||
|  |  | ||||||
| @@ -40,6 +40,7 @@ | |||||||
|  |  | ||||||
| // wrapper for U16toU8 | // wrapper for U16toU8 | ||||||
| #define ARRLEN(x) (sizeof(x)/sizeof(*x)) | #define ARRLEN(x) (sizeof(x)/sizeof(*x)) | ||||||
|  | #define AUTOU8(x) std::string((char*)x, ARRLEN(x)) | ||||||
| #define AUTOU16TOU8(x) U16toU8(x, ARRLEN(x))  | #define AUTOU16TOU8(x) U16toU8(x, ARRLEN(x))  | ||||||
|  |  | ||||||
| // TODO: rewrite U16toU8 & U8toU16 to not use codecvt | // TODO: rewrite U16toU8 & U8toU16 to not use codecvt | ||||||
| @@ -48,6 +49,7 @@ std::string U16toU8(char16_t* src, size_t max); | |||||||
| size_t U8toU16(std::string src, char16_t* des, size_t max); // returns number of char16_t that was written at des | size_t U8toU16(std::string src, char16_t* des, size_t max); // returns number of char16_t that was written at des | ||||||
| time_t getTime(); | time_t getTime(); | ||||||
| time_t getTimestamp(); | time_t getTimestamp(); | ||||||
|  | int timingSafeStrcmp(const char* a, const char* b); | ||||||
| void terminate(int); | void terminate(int); | ||||||
|  |  | ||||||
| // The PROTOCOL_VERSION definition can be defined by the build system. | // The PROTOCOL_VERSION definition can be defined by the build system. | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| /* enum definitions from the client */ | /* enum definitions from the client */ | ||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include "core/CNStructs.hpp" | ||||||
|  |  | ||||||
| // floats | // floats | ||||||
| const float VALUE_BATTERY_EMPTY_PENALTY = 0.5f; | const float VALUE_BATTERY_EMPTY_PENALTY = 0.5f; | ||||||
| const float CN_EP_RANK_1 = 0.8f; | const float CN_EP_RANK_1 = 0.8f; | ||||||
| @@ -410,7 +412,13 @@ enum { | |||||||
|     SEND_ANYCAST_NEW = 3, |     SEND_ANYCAST_NEW = 3, | ||||||
|     SEND_BROADCAST = 4, |     SEND_BROADCAST = 4, | ||||||
|  |  | ||||||
|  | #if PROTOCOL_VERSION == 728 | ||||||
|  |     CN_PACKET_BUFFER_SIZE = 8192, | ||||||
|  | #elif PROTOCOL_VERSION == 1013 | ||||||
|  |     CN_PACKET_BUFFER_SIZE = 8192, | ||||||
|  | #else | ||||||
|     CN_PACKET_BUFFER_SIZE = 4096, |     CN_PACKET_BUFFER_SIZE = 4096, | ||||||
|  | #endif | ||||||
|  |  | ||||||
|     P_CL2LS_REQ_LOGIN = 0x12000001,		// 301989889 |     P_CL2LS_REQ_LOGIN = 0x12000001,		// 301989889 | ||||||
|     P_CL2LS_REQ_CHECK_CHAR_NAME = 0x12000002,		// 301989890 |     P_CL2LS_REQ_CHECK_CHAR_NAME = 0x12000002,		// 301989890 | ||||||
| @@ -934,3 +942,8 @@ enum { | |||||||
|  |  | ||||||
|     N_PACKETS = N_CL2LS + N_CL2FE + N_FE2CL + N_LS2CL |     N_PACKETS = N_CL2LS + N_CL2FE + N_FE2CL + N_LS2CL | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Usable space in the packet buffer = CN_PACKET_BUFFER_SIZE - type - size | ||||||
|  |  */ | ||||||
|  | constexpr size_t CN_PACKET_BODY_SIZE = CN_PACKET_BUFFER_SIZE - 2 * sizeof(int32_t); | ||||||
|   | |||||||
| @@ -235,7 +235,7 @@ std::map<uint32_t, PacketDesc> Packets::packets = { | |||||||
|     PACKET(P_FE2CL_REP_PC_EXIT_FAIL), |     PACKET(P_FE2CL_REP_PC_EXIT_FAIL), | ||||||
|     PACKET(P_FE2CL_REP_PC_EXIT_SUCC), |     PACKET(P_FE2CL_REP_PC_EXIT_SUCC), | ||||||
|     PACKET(P_FE2CL_PC_EXIT), |     PACKET(P_FE2CL_PC_EXIT), | ||||||
|     PACKET(P_FE2CL_PC_AROUND), |     VAR_PACKET(P_FE2CL_PC_AROUND, iPCCnt, sPCAppearanceData), | ||||||
|     PACKET(P_FE2CL_PC_MOVE), |     PACKET(P_FE2CL_PC_MOVE), | ||||||
|     PACKET(P_FE2CL_PC_STOP), |     PACKET(P_FE2CL_PC_STOP), | ||||||
|     PACKET(P_FE2CL_PC_JUMP), |     PACKET(P_FE2CL_PC_JUMP), | ||||||
| @@ -243,9 +243,9 @@ std::map<uint32_t, PacketDesc> Packets::packets = { | |||||||
|     PACKET(P_FE2CL_NPC_EXIT), |     PACKET(P_FE2CL_NPC_EXIT), | ||||||
|     PACKET(P_FE2CL_NPC_MOVE), |     PACKET(P_FE2CL_NPC_MOVE), | ||||||
|     PACKET(P_FE2CL_NPC_NEW), |     PACKET(P_FE2CL_NPC_NEW), | ||||||
|     PACKET(P_FE2CL_NPC_AROUND), |     VAR_PACKET(P_FE2CL_NPC_AROUND, iNPCCnt, sNPCAppearanceData), | ||||||
|     PACKET(P_FE2CL_AROUND_DEL_PC), |     VAR_PACKET(P_FE2CL_AROUND_DEL_PC, iPCCnt, int32_t), | ||||||
|     PACKET(P_FE2CL_AROUND_DEL_NPC), |     VAR_PACKET(P_FE2CL_AROUND_DEL_NPC, iNPCCnt, int32_t), | ||||||
|     PACKET(P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC), |     PACKET(P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC), | ||||||
|     PACKET(P_FE2CL_REP_SEND_FREECHAT_MESSAGE_FAIL), |     PACKET(P_FE2CL_REP_SEND_FREECHAT_MESSAGE_FAIL), | ||||||
|     VAR_PACKET(P_FE2CL_PC_ATTACK_NPCs_SUCC, iNPCCnt, sAttackResult), |     VAR_PACKET(P_FE2CL_PC_ATTACK_NPCs_SUCC, iNPCCnt, sAttackResult), | ||||||
| @@ -387,8 +387,8 @@ std::map<uint32_t, PacketDesc> Packets::packets = { | |||||||
|     PACKET(P_FE2CL_TRANSPORTATION_EXIT), |     PACKET(P_FE2CL_TRANSPORTATION_EXIT), | ||||||
|     PACKET(P_FE2CL_TRANSPORTATION_MOVE), |     PACKET(P_FE2CL_TRANSPORTATION_MOVE), | ||||||
|     PACKET(P_FE2CL_TRANSPORTATION_NEW), |     PACKET(P_FE2CL_TRANSPORTATION_NEW), | ||||||
|     PACKET(P_FE2CL_TRANSPORTATION_AROUND), |     VAR_PACKET(P_FE2CL_TRANSPORTATION_AROUND, iCnt, sTransportationAppearanceData), | ||||||
|     PACKET(P_FE2CL_AROUND_DEL_TRANSPORTATION), |     VAR_PACKET(P_FE2CL_AROUND_DEL_TRANSPORTATION, iCnt, int32_t), | ||||||
|     PACKET(P_FE2CL_REP_EP_RANK_LIST), |     PACKET(P_FE2CL_REP_EP_RANK_LIST), | ||||||
|     PACKET(P_FE2CL_REP_EP_RANK_DETAIL), |     PACKET(P_FE2CL_REP_EP_RANK_DETAIL), | ||||||
|     PACKET(P_FE2CL_REP_EP_RANK_PC_INFO), |     PACKET(P_FE2CL_REP_EP_RANK_PC_INFO), | ||||||
| @@ -404,8 +404,8 @@ std::map<uint32_t, PacketDesc> Packets::packets = { | |||||||
|     PACKET(P_FE2CL_SHINY_ENTER), |     PACKET(P_FE2CL_SHINY_ENTER), | ||||||
|     PACKET(P_FE2CL_SHINY_EXIT), |     PACKET(P_FE2CL_SHINY_EXIT), | ||||||
|     PACKET(P_FE2CL_SHINY_NEW), |     PACKET(P_FE2CL_SHINY_NEW), | ||||||
|     PACKET(P_FE2CL_SHINY_AROUND), |     VAR_PACKET(P_FE2CL_SHINY_AROUND, iShinyCnt, sShinyAppearanceData), | ||||||
|     PACKET(P_FE2CL_AROUND_DEL_SHINY), |     VAR_PACKET(P_FE2CL_AROUND_DEL_SHINY, iShinyCnt, int32_t), | ||||||
|     PACKET(P_FE2CL_REP_SHINY_PICKUP_FAIL), |     PACKET(P_FE2CL_REP_SHINY_PICKUP_FAIL), | ||||||
|     PACKET(P_FE2CL_REP_SHINY_PICKUP_SUCC), |     PACKET(P_FE2CL_REP_SHINY_PICKUP_SUCC), | ||||||
|     PACKET(P_FE2CL_PC_MOVETRANSPORTATION), |     PACKET(P_FE2CL_PC_MOVETRANSPORTATION), | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
| #include <string> | #include <string> | ||||||
| #include <vector> | #include <vector> | ||||||
|  |  | ||||||
| #define DATABASE_VERSION 4 | #define DATABASE_VERSION 6 | ||||||
|  |  | ||||||
| namespace Database { | namespace Database { | ||||||
|  |  | ||||||
| @@ -53,6 +53,11 @@ namespace Database { | |||||||
|  |  | ||||||
|     void updateAccountLevel(int accountId, int accountLevel); |     void updateAccountLevel(int accountId, int accountLevel); | ||||||
|  |  | ||||||
|  |     // return true if cookie is valid for the account. | ||||||
|  |     // invalidates the stored cookie afterwards | ||||||
|  |     bool checkCookie(int accountId, const char *cookie); | ||||||
|  |     void refreshCookie(int accountId, int durationSec); | ||||||
|  |  | ||||||
|     // interface for the /ban command |     // interface for the /ban command | ||||||
|     bool banPlayer(int playerId, std::string& reason); |     bool banPlayer(int playerId, std::string& reason); | ||||||
|     bool unbanPlayer(int playerId); |     bool unbanPlayer(int playerId); | ||||||
| @@ -64,7 +69,7 @@ namespace Database { | |||||||
|     bool isNameFree(std::string firstName, std::string lastName); |     bool isNameFree(std::string firstName, std::string lastName); | ||||||
|     bool isSlotFree(int accountId, int slotNum); |     bool isSlotFree(int accountId, int slotNum); | ||||||
|     /// returns ID, 0 if something failed |     /// returns ID, 0 if something failed | ||||||
|     int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID); |     int createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck); | ||||||
|     /// returns true if query succeeded |     /// returns true if query succeeded | ||||||
|     bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId); |     bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId); | ||||||
|     /// returns true if query succeeded |     /// returns true if query succeeded | ||||||
| @@ -80,7 +85,7 @@ namespace Database { | |||||||
|     }; |     }; | ||||||
|     void evaluateCustomName(int characterID, CustomName decision); |     void evaluateCustomName(int characterID, CustomName decision); | ||||||
|     /// returns true if query succeeded |     /// returns true if query succeeded | ||||||
|     bool changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId); |     bool changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck); | ||||||
|  |  | ||||||
|     // getting players |     // getting players | ||||||
|     void getPlayer(Player* plr, int id); |     void getPlayer(Player* plr, int id); | ||||||
|   | |||||||
							
								
								
									
										104
									
								
								src/db/login.cpp
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								src/db/login.cpp
									
									
									
									
									
								
							| @@ -1,5 +1,9 @@ | |||||||
|  | #include "core/CNStructs.hpp" | ||||||
|  |  | ||||||
| #include "db/internal.hpp" | #include "db/internal.hpp" | ||||||
|  |  | ||||||
|  | #include "servers/CNLoginServer.hpp" | ||||||
|  |  | ||||||
| #include "bcrypt/BCrypt.hpp" | #include "bcrypt/BCrypt.hpp" | ||||||
|  |  | ||||||
| void Database::findAccount(Account* account, std::string login) { | void Database::findAccount(Account* account, std::string login) { | ||||||
| @@ -98,6 +102,78 @@ void Database::updateAccountLevel(int accountId, int accountLevel) { | |||||||
|     sqlite3_finalize(stmt); |     sqlite3_finalize(stmt); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool Database::checkCookie(int accountId, const char *tryCookie) { | ||||||
|  |     std::lock_guard<std::mutex> lock(dbCrit); | ||||||
|  |  | ||||||
|  |     const char* sql_get = R"( | ||||||
|  |         SELECT Cookie | ||||||
|  |         FROM Auth | ||||||
|  |         WHERE AccountID = ? AND Expires > ?; | ||||||
|  |         )"; | ||||||
|  |  | ||||||
|  |     const char* sql_invalidate = R"( | ||||||
|  |         UPDATE Auth | ||||||
|  |         SET Expires = 0 | ||||||
|  |         WHERE AccountID = ?; | ||||||
|  |         )"; | ||||||
|  |  | ||||||
|  |     sqlite3_stmt* stmt; | ||||||
|  |  | ||||||
|  |     sqlite3_prepare_v2(db, sql_get, -1, &stmt, NULL); | ||||||
|  |     sqlite3_bind_int(stmt, 1, accountId); | ||||||
|  |     sqlite3_bind_int(stmt, 2, getTimestamp()); | ||||||
|  |     int rc = sqlite3_step(stmt); | ||||||
|  |     if (rc != SQLITE_ROW) { | ||||||
|  |         sqlite3_finalize(stmt); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const char *cookie = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0)); | ||||||
|  |     if (strlen(cookie) != strlen(tryCookie)) { | ||||||
|  |         sqlite3_finalize(stmt); | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bool match = (timingSafeStrcmp(cookie, tryCookie) == 0); | ||||||
|  |     sqlite3_finalize(stmt); | ||||||
|  |  | ||||||
|  |     /* | ||||||
|  |      * Only invalidate the cookie if it was correct. This prevents | ||||||
|  |      * replay attacks without enabling DOS attacks on accounts. | ||||||
|  |      */ | ||||||
|  |     if (match) { | ||||||
|  |         sqlite3_prepare_v2(db, sql_invalidate, -1, &stmt, NULL); | ||||||
|  |         sqlite3_bind_int(stmt, 1, accountId); | ||||||
|  |         rc = sqlite3_step(stmt); | ||||||
|  |         sqlite3_finalize(stmt); | ||||||
|  |         if (rc != SQLITE_DONE) | ||||||
|  |             std::cout << "[WARN] Database fail on checkCookie(): " << sqlite3_errmsg(db) << std::endl; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return match; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Database::refreshCookie(int accountId, int durationSec) { | ||||||
|  |     std::lock_guard<std::mutex> lock(dbCrit); | ||||||
|  |  | ||||||
|  |     const char* sql = R"( | ||||||
|  |         UPDATE Auth | ||||||
|  |         SET Expires = ? | ||||||
|  |         WHERE AccountID = ?; | ||||||
|  |         )"; | ||||||
|  |      | ||||||
|  |     int expires = getTimestamp() + durationSec; | ||||||
|  |  | ||||||
|  |     sqlite3_stmt* stmt; | ||||||
|  |     sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); | ||||||
|  |     sqlite3_bind_int(stmt, 1, expires); | ||||||
|  |     sqlite3_bind_int(stmt, 2, accountId); | ||||||
|  |     int rc = sqlite3_step(stmt); | ||||||
|  |     sqlite3_finalize(stmt); | ||||||
|  |     if (rc != SQLITE_DONE) | ||||||
|  |         std::cout << "[WARN] Database fail on refreshCookie(): " << sqlite3_errmsg(db) << std::endl; | ||||||
|  | } | ||||||
|  |  | ||||||
| void Database::updateSelected(int accountId, int slot) { | void Database::updateSelected(int accountId, int slot) { | ||||||
|     std::lock_guard<std::mutex> lock(dbCrit); |     std::lock_guard<std::mutex> lock(dbCrit); | ||||||
|  |  | ||||||
| @@ -217,7 +293,7 @@ bool Database::isSlotFree(int accountId, int slotNum) { | |||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  |  | ||||||
| int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) { | int Database::createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck) { | ||||||
|     std::lock_guard<std::mutex> lock(dbCrit); |     std::lock_guard<std::mutex> lock(dbCrit); | ||||||
|  |  | ||||||
|     sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL); |     sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL); | ||||||
| @@ -230,22 +306,17 @@ int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) | |||||||
|         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); |         VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); | ||||||
|         )"; |         )"; | ||||||
|     sqlite3_stmt* stmt; |     sqlite3_stmt* stmt; | ||||||
|     std::string firstName = AUTOU16TOU8(save->szFirstName); |  | ||||||
|     std::string lastName =  AUTOU16TOU8(save->szLastName); |  | ||||||
|  |  | ||||||
|     sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); |     sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); | ||||||
|     sqlite3_bind_int(stmt, 1, AccountID); |     sqlite3_bind_int(stmt, 1, accountId); | ||||||
|     sqlite3_bind_int(stmt, 2, save->iSlotNum); |     sqlite3_bind_int(stmt, 2, slot); | ||||||
|     sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, NULL); |     sqlite3_bind_text(stmt, 3, firstName, -1, NULL); | ||||||
|     sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, NULL); |     sqlite3_bind_text(stmt, 4, lastName, -1, NULL); | ||||||
|     sqlite3_bind_int(stmt, 5, settings::SPAWN_X); |     sqlite3_bind_int(stmt, 5, settings::SPAWN_X); | ||||||
|     sqlite3_bind_int(stmt, 6, settings::SPAWN_Y); |     sqlite3_bind_int(stmt, 6, settings::SPAWN_Y); | ||||||
|     sqlite3_bind_int(stmt, 7, settings::SPAWN_Z); |     sqlite3_bind_int(stmt, 7, settings::SPAWN_Z); | ||||||
|     sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE); |     sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE); | ||||||
|     sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1)); |     sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1)); | ||||||
|  |  | ||||||
|     // if FNCode isn't 0, it's a wheel name |  | ||||||
|     int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0; |  | ||||||
|     sqlite3_bind_int(stmt, 10, nameCheck); |     sqlite3_bind_int(stmt, 10, nameCheck); | ||||||
|  |  | ||||||
|     // blobs |     // blobs | ||||||
| @@ -574,7 +645,7 @@ void Database::evaluateCustomName(int characterID, CustomName decision) { | |||||||
|     sqlite3_finalize(stmt); |     sqlite3_finalize(stmt); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) { | bool Database::changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck) { | ||||||
|     std::lock_guard<std::mutex> lock(dbCrit); |     std::lock_guard<std::mutex> lock(dbCrit); | ||||||
|  |  | ||||||
|     const char* sql = R"( |     const char* sql = R"( | ||||||
| @@ -588,15 +659,10 @@ bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) { | |||||||
|     sqlite3_stmt* stmt; |     sqlite3_stmt* stmt; | ||||||
|     sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); |     sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); | ||||||
|  |  | ||||||
|     std::string firstName = AUTOU16TOU8(save->szFirstName); |     sqlite3_bind_text(stmt, 1, firstName, -1, NULL); | ||||||
|     std::string lastName = AUTOU16TOU8(save->szLastName); |     sqlite3_bind_text(stmt, 2, lastName, -1, NULL); | ||||||
|  |  | ||||||
|     sqlite3_bind_text(stmt, 1, firstName.c_str(), -1, NULL); |  | ||||||
|     sqlite3_bind_text(stmt, 2, lastName.c_str(), -1, NULL); |  | ||||||
|     // if FNCode isn't 0, it's a wheel name |  | ||||||
|     int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0; |  | ||||||
|     sqlite3_bind_int(stmt, 3, nameCheck); |     sqlite3_bind_int(stmt, 3, nameCheck); | ||||||
|     sqlite3_bind_int(stmt, 4, save->iPCUID); |     sqlite3_bind_int(stmt, 4, playerId); | ||||||
|     sqlite3_bind_int(stmt, 5, accountId); |     sqlite3_bind_int(stmt, 5, accountId); | ||||||
|  |  | ||||||
|     int rc = sqlite3_step(stmt); |     int rc = sqlite3_step(stmt); | ||||||
|   | |||||||
							
								
								
									
										15
									
								
								src/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								src/main.cpp
									
									
									
									
									
								
							| @@ -49,6 +49,7 @@ CNShardServer *shardServer = nullptr; | |||||||
| std::thread *shardThread = nullptr; | std::thread *shardThread = nullptr; | ||||||
|  |  | ||||||
| void startShard(CNShardServer* server) { | void startShard(CNShardServer* server) { | ||||||
|  |     sandbox_thread_start(); | ||||||
|     server->start(); |     server->start(); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -150,6 +151,8 @@ int main() { | |||||||
|         /* not reached */ |         /* not reached */ | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     sandbox_init(); | ||||||
|  |  | ||||||
|     std::cout << "[INFO] Starting Server Threads..." << std::endl; |     std::cout << "[INFO] Starting Server Threads..." << std::endl; | ||||||
|     CNLoginServer loginServer(settings::LOGINPORT); |     CNLoginServer loginServer(settings::LOGINPORT); | ||||||
|     shardServer = new CNShardServer(settings::SHARDPORT); |     shardServer = new CNShardServer(settings::SHARDPORT); | ||||||
| @@ -157,6 +160,7 @@ int main() { | |||||||
|     shardThread = new std::thread(startShard, (CNShardServer*)shardServer); |     shardThread = new std::thread(startShard, (CNShardServer*)shardServer); | ||||||
|  |  | ||||||
|     sandbox_start(); |     sandbox_start(); | ||||||
|  |     sandbox_thread_start(); | ||||||
|  |  | ||||||
|     loginServer.start(); |     loginServer.start(); | ||||||
|  |  | ||||||
| @@ -218,6 +222,17 @@ time_t getTimestamp() { | |||||||
|     return (time_t)value.count(); |     return (time_t)value.count(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // timing safe strcmp implementation for e.g. cookie validation | ||||||
|  | int timingSafeStrcmp(const char* a, const char* b) { | ||||||
|  |     int diff = 0; | ||||||
|  |     while (*a && *b) { | ||||||
|  |         diff |= *a++ ^ *b++; | ||||||
|  |     } | ||||||
|  |     diff |= *a; | ||||||
|  |     diff |= *b; | ||||||
|  |     return diff; | ||||||
|  | } | ||||||
|  |  | ||||||
| // convert integer timestamp (in s) to FF systime struct | // convert integer timestamp (in s) to FF systime struct | ||||||
| sSYSTEMTIME timeStampToStruct(uint64_t time) { | sSYSTEMTIME timeStampToStruct(uint64_t time) { | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,11 +4,16 @@ | |||||||
| #if defined(__linux__) || defined(__OpenBSD__) | #if defined(__linux__) || defined(__OpenBSD__) | ||||||
|  |  | ||||||
| # if !defined(CONFIG_NOSANDBOX) | # if !defined(CONFIG_NOSANDBOX) | ||||||
|  | void sandbox_init(); | ||||||
| void sandbox_start(); | void sandbox_start(); | ||||||
|  | void sandbox_thread_start(); | ||||||
| # else | # else | ||||||
|  |  | ||||||
| #include <iostream> | #include <iostream> | ||||||
|  |  | ||||||
|  | inline void sandbox_init() {} | ||||||
|  | inline void sandbox_thread_start() {} | ||||||
|  |  | ||||||
| inline void sandbox_start() { | inline void sandbox_start() { | ||||||
|     std::cout << "[WARN] Built without a sandbox" << std::endl; |     std::cout << "[WARN] Built without a sandbox" << std::endl; | ||||||
| } | } | ||||||
| @@ -17,5 +22,7 @@ inline void sandbox_start() { | |||||||
|  |  | ||||||
| #else | #else | ||||||
| // stub for unsupported platforms | // stub for unsupported platforms | ||||||
|  | inline void sandbox_init() {} | ||||||
| inline void sandbox_start() {} | inline void sandbox_start() {} | ||||||
|  | inline void sandbox_thread_start() {} | ||||||
| #endif | #endif | ||||||
|   | |||||||
| @@ -13,6 +13,9 @@ static void eunveil(const char *path, const char *permissions) { | |||||||
|         err(1, "unveil"); |         err(1, "unveil"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void sandbox_init() {} | ||||||
|  | void sandbox_thread_start() {} | ||||||
|  |  | ||||||
| void sandbox_start() { | void sandbox_start() { | ||||||
|     /* |     /* | ||||||
|      * There shouldn't ever be a reason to disable this one, but might as well |      * There shouldn't ever be a reason to disable this one, but might as well | ||||||
|   | |||||||
| @@ -4,6 +4,9 @@ | |||||||
| #include "settings.hpp" | #include "settings.hpp" | ||||||
|  |  | ||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
|  | #include <fcntl.h> | ||||||
|  |  | ||||||
|  | #include <filesystem> | ||||||
|  |  | ||||||
| #include <sys/prctl.h> | #include <sys/prctl.h> | ||||||
| #include <sys/ptrace.h> | #include <sys/ptrace.h> | ||||||
| @@ -17,6 +20,10 @@ | |||||||
| #include <linux/audit.h> | #include <linux/audit.h> | ||||||
| #include <linux/net.h> // for socketcall() args | #include <linux/net.h> // for socketcall() args | ||||||
|  |  | ||||||
|  | #ifndef CONFIG_NOLANDLOCK | ||||||
|  | #include <linux/landlock.h> | ||||||
|  | #endif | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Macros adapted from https://outflux.net/teach-seccomp/ |  * Macros adapted from https://outflux.net/teach-seccomp/ | ||||||
|  * Relevant license: |  * Relevant license: | ||||||
| @@ -54,7 +61,7 @@ | |||||||
|     BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO|(_errno)) |     BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO|(_errno)) | ||||||
|  |  | ||||||
| #define KILL_PROCESS \ | #define KILL_PROCESS \ | ||||||
|     BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS) |     BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_TRAP) | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Macros adapted from openssh's sandbox-seccomp-filter.c |  * Macros adapted from openssh's sandbox-seccomp-filter.c | ||||||
| @@ -297,25 +304,201 @@ static sock_fprog prog = { | |||||||
|     ARRLEN(filter), filter |     ARRLEN(filter), filter | ||||||
| }; | }; | ||||||
|  |  | ||||||
| // our own wrapper for the seccomp() syscall | // Our own wrapper for the seccomp() syscall. | ||||||
| int seccomp(unsigned int operation, unsigned int flags, void *args) { | int seccomp(unsigned int operation, unsigned int flags, void *args) { | ||||||
|     return syscall(__NR_seccomp, operation, flags, args); |     return syscall(__NR_seccomp, operation, flags, args); | ||||||
| } | } | ||||||
|  |  | ||||||
| void sandbox_start() { | #ifndef CONFIG_NOLANDLOCK | ||||||
|  |  | ||||||
|  | // Support compilation on systems that only have older Landlock headers. | ||||||
|  | #ifndef LANDLOCK_ACCESS_FS_REFER | ||||||
|  | #define LANDLOCK_ACCESS_FS_REFER 0 | ||||||
|  | #endif | ||||||
|  | #ifndef LANDLOCK_ACCESS_FS_TRUNCATE | ||||||
|  | #define LANDLOCK_ACCESS_FS_TRUNCATE 0 | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | struct landlock_ruleset_attr ruleset_attr = { | ||||||
|  |     .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE | ||||||
|  |         | LANDLOCK_ACCESS_FS_WRITE_FILE | ||||||
|  |         | LANDLOCK_ACCESS_FS_READ_DIR | ||||||
|  |         | LANDLOCK_ACCESS_FS_MAKE_REG | ||||||
|  |         | LANDLOCK_ACCESS_FS_MAKE_DIR | ||||||
|  |         | LANDLOCK_ACCESS_FS_MAKE_SYM | ||||||
|  |         | LANDLOCK_ACCESS_FS_MAKE_SOCK | ||||||
|  |         | LANDLOCK_ACCESS_FS_MAKE_FIFO | ||||||
|  |         | LANDLOCK_ACCESS_FS_MAKE_BLOCK | ||||||
|  |         | LANDLOCK_ACCESS_FS_REMOVE_FILE | ||||||
|  |         | LANDLOCK_ACCESS_FS_REMOVE_DIR | ||||||
|  |         | LANDLOCK_ACCESS_FS_TRUNCATE | ||||||
|  |         | LANDLOCK_ACCESS_FS_REFER | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | uint64_t landlock_perms = LANDLOCK_ACCESS_FS_READ_FILE | ||||||
|  |     | LANDLOCK_ACCESS_FS_WRITE_FILE | ||||||
|  |     | LANDLOCK_ACCESS_FS_TRUNCATE | ||||||
|  |     | LANDLOCK_ACCESS_FS_MAKE_REG | ||||||
|  |     | LANDLOCK_ACCESS_FS_REMOVE_FILE; | ||||||
|  |  | ||||||
|  | int landlock_fd; | ||||||
|  | bool landlock_supported; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Our own wrappers for Landlock syscalls. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | int landlock_create_ruleset(const struct landlock_ruleset_attr *attr, size_t size, uint32_t flags) { | ||||||
|  |     return syscall(__NR_landlock_create_ruleset, attr, size, flags); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int landlock_add_rule(int ruleset_fd, enum landlock_rule_type rule_type, const void *rule_attr, uint32_t flags) { | ||||||
|  |     return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int landlock_restrict_self(int ruleset_fd, uint32_t flags) { | ||||||
|  |     return syscall(__NR_landlock_restrict_self, ruleset_fd, flags); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void landlock_path(std::string path, uint32_t perms) { | ||||||
|  |     struct landlock_path_beneath_attr path_beneath = { | ||||||
|  |         .allowed_access = perms | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     path_beneath.parent_fd = open(path.c_str(), O_PATH|O_CLOEXEC); | ||||||
|  |     if (path_beneath.parent_fd < 0) { | ||||||
|  |         perror(path.c_str()); | ||||||
|  |         exit(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (landlock_add_rule(landlock_fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0)) { | ||||||
|  |         perror("landlock_add_rule"); | ||||||
|  |         exit(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     close(path_beneath.parent_fd); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static bool landlock_detect() { | ||||||
|  |     int abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION); | ||||||
|  |  | ||||||
|  |     if (abi < 0) { | ||||||
|  |         if (errno == ENOSYS || errno == EOPNOTSUPP) { | ||||||
|  |             std::cout << "[WARN] No Landlock support on this system" << std::endl; | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |         perror("landlock_create_ruleset"); | ||||||
|  |         exit(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     std::cout << "[INFO] Detected Landlock ABI version: " << abi << std::endl; | ||||||
|  |  | ||||||
|  |     switch (abi) { | ||||||
|  |     case 1: | ||||||
|  |         ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER; | ||||||
|  |         landlock_perms &= ~LANDLOCK_ACCESS_FS_REFER; | ||||||
|  |         // fallthrough | ||||||
|  |     case 2: | ||||||
|  |         ruleset_attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_TRUNCATE; | ||||||
|  |         landlock_perms &= ~LANDLOCK_ACCESS_FS_TRUNCATE; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void landlock_init() { | ||||||
|  |     std::cout << "[INFO] Setting up Landlock sandbox..." << std::endl; | ||||||
|  |  | ||||||
|  |     landlock_supported = landlock_detect(); | ||||||
|  |  | ||||||
|  |     if (!landlock_supported) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|  |     landlock_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0); | ||||||
|  |     if (landlock_fd < 0) { | ||||||
|  |         perror("landlock_create_ruleset"); | ||||||
|  |         exit(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     std::string dbdir = std::filesystem::path(settings::DBPATH).parent_path(); | ||||||
|  |  | ||||||
|  |     // for the DB files (we can't rely on them being in the working directory) | ||||||
|  |     landlock_path(dbdir == "" ? "." : dbdir, landlock_perms); | ||||||
|  |     // for writing the gruntwork file | ||||||
|  |     landlock_path(settings::TDATADIR, landlock_perms); | ||||||
|  |     // for passowrd salting during account creation | ||||||
|  |     landlock_path("/dev/urandom", LANDLOCK_ACCESS_FS_READ_FILE); | ||||||
|  |     // for core dumps, optionally | ||||||
|  |     if (settings::SANDBOXEXTRAPATH != "") | ||||||
|  |         landlock_path(settings::SANDBOXEXTRAPATH, landlock_perms); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif // !CONFIG_NOLANDLOCK | ||||||
|  |  | ||||||
|  | static void sigsys_handler(int signo, siginfo_t *info, void *context) { | ||||||
|  |     // report the unhandled syscall | ||||||
|  |     std::cout << "[FATAL] Unhandled syscall " << info->si_syscall | ||||||
|  |        << " at " << std::hex << info->si_call_addr << " on arch " << info->si_arch << std::endl; | ||||||
|  |  | ||||||
|  |     std::cout << "If you're unsure why this is happening, please read https://openfusion.dev/docs/development/the-sandbox/" << std::endl | ||||||
|  |               << "for more information and possibly open an issue at https://github.com/OpenFusionProject/OpenFusion/issues to report" | ||||||
|  |               << " needed changes in our seccomp filter." << std::endl; | ||||||
|  |  | ||||||
|  |     exit(1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void sandbox_init() { | ||||||
|     if (!settings::SANDBOX) { |     if (!settings::SANDBOX) { | ||||||
|         std::cout << "[WARN] Running without a sandbox" << std::endl; |         std::cout << "[WARN] Running without a sandbox" << std::endl; | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // listen to SIGSYS to report unhandled syscalls | ||||||
|  |     struct sigaction sa = {}; | ||||||
|  |  | ||||||
|  |     sa.sa_flags = SA_SIGINFO; | ||||||
|  |     sa.sa_sigaction = sigsys_handler; | ||||||
|  |  | ||||||
|  |     if (sigaction(SIGSYS, &sa, NULL) < 0) { | ||||||
|  |         perror("sigaction"); | ||||||
|  |         exit(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | #ifndef CONFIG_NOLANDLOCK | ||||||
|  |     landlock_init(); | ||||||
|  | #else | ||||||
|  |     std::cout << "[WARN] Built without Landlock" << std::endl; | ||||||
|  | #endif | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void sandbox_start() { | ||||||
|  |     if (!settings::SANDBOX) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|     std::cout << "[INFO] Starting seccomp-bpf sandbox..." << std::endl; |     std::cout << "[INFO] Starting seccomp-bpf sandbox..." << std::endl; | ||||||
|  |  | ||||||
|  |     // Sandboxing starts in sandbox_thread_start(). | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void sandbox_thread_start() { | ||||||
|  |     if (!settings::SANDBOX) | ||||||
|  |         return; | ||||||
|  |  | ||||||
|     if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { |     if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { | ||||||
|         perror("prctl"); |         perror("prctl"); | ||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &prog) < 0) { | #ifndef CONFIG_NOLANDLOCK | ||||||
|  |     if (landlock_supported) { | ||||||
|  |         if (landlock_restrict_self(landlock_fd, 0)) { | ||||||
|  |             perror("landlock_restrict_self"); | ||||||
|  |             exit(1); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |     if (seccomp(SECCOMP_SET_MODE_FILTER, 0, &prog) < 0) { | ||||||
|         perror("seccomp"); |         perror("seccomp"); | ||||||
|         exit(1); |         exit(1); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| #include "servers/CNLoginServer.hpp" | #include "servers/CNLoginServer.hpp" | ||||||
|  | #include "servers/Monitor.hpp" | ||||||
|  |  | ||||||
| #include "core/CNShared.hpp" | #include "core/CNShared.hpp" | ||||||
| #include "db/Database.hpp" | #include "db/Database.hpp" | ||||||
| @@ -12,6 +13,12 @@ | |||||||
|  |  | ||||||
| std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions; | std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions; | ||||||
|  |  | ||||||
|  | namespace LoginServer { | ||||||
|  |     std::vector<std::string> WheelFirstNames; | ||||||
|  |     std::vector<std::string> WheelMiddleNames; | ||||||
|  |     std::vector<std::string> WheelLastNames; | ||||||
|  | } | ||||||
|  |  | ||||||
| CNLoginServer::CNLoginServer(uint16_t p) { | CNLoginServer::CNLoginServer(uint16_t p) { | ||||||
|     serverType = "login"; |     serverType = "login"; | ||||||
|     port = p; |     port = p; | ||||||
| @@ -105,77 +112,63 @@ void loginFail(LoginError errorCode, std::string userLogin, CNSocket* sock) { | |||||||
|  |  | ||||||
| void CNLoginServer::login(CNSocket* sock, CNPacketData* data) { | void CNLoginServer::login(CNSocket* sock, CNPacketData* data) { | ||||||
|     auto login = (sP_CL2LS_REQ_LOGIN*)data->buf; |     auto login = (sP_CL2LS_REQ_LOGIN*)data->buf; | ||||||
|     // TODO: implement better way of sending credentials |  | ||||||
|     std::string userLogin((char*)login->szCookie_TEGid); |     std::string userLogin; | ||||||
|     std::string userPassword((char*)login->szCookie_authid); |     std::string userToken; // could be password or auth cookie | ||||||
|  |  | ||||||
|     /* |     /* | ||||||
|      * Sometimes the client sends garbage cookie data. |      * In this context, "cookie auth" just means the credentials were sent | ||||||
|      * Validate it as normal credentials instead of using a length check before falling back. |      * in the szCookie fields instead of szID and szPassword. | ||||||
|      */ |      */ | ||||||
|     if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) { |     bool isCookieAuth = login->iLoginType == USE_COOKIE_FIELDS; | ||||||
|  |  | ||||||
|     /* |     /* | ||||||
|      * The std::string -> char* -> std::string maneuver should remove any |      * The std::string -> char* -> std::string maneuver should remove any | ||||||
|      * trailing garbage after the null terminator. |      * trailing garbage after the null terminator. | ||||||
|      */ |      */ | ||||||
|  |     if (isCookieAuth) { | ||||||
|  |         userLogin = std::string(AUTOU8(login->szCookie_TEGid).c_str()); | ||||||
|  |         userToken = std::string(AUTOU8(login->szCookie_authid).c_str()); | ||||||
|  |     } else { | ||||||
|         userLogin = std::string(AUTOU16TOU8(login->szID).c_str()); |         userLogin = std::string(AUTOU16TOU8(login->szID).c_str()); | ||||||
|         userPassword = std::string(AUTOU16TOU8(login->szPassword).c_str()); |         userToken = std::string(AUTOU16TOU8(login->szPassword).c_str()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // the client inserts a "\n" in the password if you press enter key in the middle of the password |     if (!CNLoginServer::checkUsername(sock, userLogin)) { | ||||||
|     // (not at the start or the end of the password field) |  | ||||||
|     if (int(userPassword.find("\n")) > 0) |  | ||||||
|         userPassword.erase(userPassword.find("\n"), 1); |  | ||||||
|  |  | ||||||
|     // check regex |  | ||||||
|     if (!CNLoginServer::isLoginDataGood(userLogin, userPassword)) { |  | ||||||
|         // send a custom error message |  | ||||||
|         INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg); |  | ||||||
|         std::string text = "Invalid login or password\n"; |  | ||||||
|         text += "Login has to be 4 - 32 characters long and can't contain special characters other than dash and underscore\n"; |  | ||||||
|         text += "Password has to be 8 - 32 characters long";           |  | ||||||
|         U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg)); |  | ||||||
|         msg.iDuringTime = 15; |  | ||||||
|         sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); |  | ||||||
|  |  | ||||||
|         // we still have to send login fail to prevent softlock |  | ||||||
|         return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); |         return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     if (!isCookieAuth) { | ||||||
|  |         // password was sent in plaintext | ||||||
|  |         if (!CNLoginServer::checkPassword(sock, userToken)) { | ||||||
|  |             return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     Database::Account findUser = {}; |     Database::Account findUser = {}; | ||||||
|     Database::findAccount(&findUser, userLogin); |     Database::findAccount(&findUser, userLogin); | ||||||
|  |  | ||||||
|     // account was not found |     // account was not found | ||||||
|     if (findUser.AccountID == 0) { |     if (findUser.AccountID == 0) { | ||||||
|         if (settings::AUTOCREATEACCOUNTS) |         /* | ||||||
|             return newAccount(sock, userLogin, userPassword, login->iClientVerC); |          * Don't auto-create accounts if it's a cookie login. | ||||||
|  |          * It'll either be a bad cookie or a plaintext password sent by auto-login; | ||||||
|  |          * either way, we only want to allow auto-creation if the user explicitly entered their credentials. | ||||||
|  |          */ | ||||||
|  |         if (settings::AUTOCREATEACCOUNTS && !isCookieAuth) { | ||||||
|  |             return newAccount(sock, userLogin, userToken, login->iClientVerC); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock); |         return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!CNLoginServer::isPasswordCorrect(findUser.Password, userPassword)) |     // make sure either a valid cookie or password was sent | ||||||
|  |     if (!CNLoginServer::checkToken(sock, findUser, userToken, isCookieAuth)) { | ||||||
|         return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock); |         return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // is the account banned |     if (CNLoginServer::checkBan(sock, findUser)) { | ||||||
|     if (findUser.BannedUntil > getTimestamp()) { |         return; // don't send fail packet | ||||||
|         // send a custom error message |  | ||||||
|         INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg); |  | ||||||
|  |  | ||||||
|         // ceiling devision |  | ||||||
|         int64_t remainingDays = (findUser.BannedUntil-getTimestamp()) / 86400 + ((findUser.BannedUntil - getTimestamp()) % 86400 != 0); |  | ||||||
|  |  | ||||||
|         std::string text = "Your account has been banned. \nReason: "; |  | ||||||
|         text += findUser.BanReason; |  | ||||||
|         text += "\nBan expires in " + std::to_string(remainingDays) + " day"; |  | ||||||
|         if (remainingDays > 1) |  | ||||||
|             text += "s"; |  | ||||||
|  |  | ||||||
|         U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg)); |  | ||||||
|         msg.iDuringTime = 99999999; |  | ||||||
|         sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); |  | ||||||
|         // don't send fail packet |  | ||||||
|         return; |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /*  |     /*  | ||||||
| @@ -299,11 +292,30 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) { | |||||||
|     INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp); |     INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp); | ||||||
|  |  | ||||||
|     int errorCode = 0; |     int errorCode = 0; | ||||||
|     if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) { |  | ||||||
|  |     std::string firstName = AUTOU16TOU8(save->szFirstName); | ||||||
|  |     std::string lastName = AUTOU16TOU8(save->szLastName); | ||||||
|  |     int nameCheck = 0; | ||||||
|  |  | ||||||
|  |     // if FNCode isn't 0, it's a wheel name | ||||||
|  |     if (save->iFNCode != 0) { | ||||||
|  |         if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) { | ||||||
|             errorCode = 4; |             errorCode = 4; | ||||||
|     } else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) { |         } else { | ||||||
|  |             nameCheck = settings:: APPROVEWHEELNAMES ? 1 : 0; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         // custom name | ||||||
|  |         nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (errorCode == 0) { | ||||||
|  |         if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) { | ||||||
|  |             errorCode = 4; | ||||||
|  |         } else if (!Database::isNameFree(firstName, lastName)) { | ||||||
|             errorCode = 1; |             errorCode = 1; | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (errorCode != 0) { |     if (errorCode != 0) { | ||||||
|         INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp); |         INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp); | ||||||
| @@ -320,12 +332,19 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) { | |||||||
|     if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum)) |     if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum)) | ||||||
|         return invalidCharacter(sock); |         return invalidCharacter(sock); | ||||||
|  |  | ||||||
|     resp.iPC_UID = Database::createCharacter(save, loginSessions[sock].userID); |     resp.iPC_UID = Database::createCharacter(save->iSlotNum, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck); | ||||||
|     // if query somehow failed |     // if query somehow failed | ||||||
|     if (resp.iPC_UID == 0) { |     if (resp.iPC_UID == 0) { | ||||||
|         std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl; |         std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl; | ||||||
|         return invalidCharacter(sock); |         return invalidCharacter(sock); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     // fire name check event if needed | ||||||
|  |     if (nameCheck != 1) { | ||||||
|  |         std::string namereq = std::to_string(resp.iPC_UID) + " " + firstName + " " + lastName; | ||||||
|  |         Monitor::namereqs.push_back(namereq); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     resp.iSlotNum = save->iSlotNum; |     resp.iSlotNum = save->iSlotNum; | ||||||
|     resp.iGender = save->iGender; |     resp.iGender = save->iGender; | ||||||
|      |      | ||||||
| @@ -341,7 +360,9 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) { | |||||||
|     DEBUGLOG( |     DEBUGLOG( | ||||||
|         std::cout << "Login Server: new character created" << std::endl; |         std::cout << "Login Server: new character created" << std::endl; | ||||||
|         std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl; |         std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl; | ||||||
|         std::cout << "\tName: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl; |         std::cout << "\tName: " << firstName << " " << lastName; | ||||||
|  |         if (nameCheck != 1) std::cout << " (pending approval)"; | ||||||
|  |         std::cout << std::endl; | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -508,12 +529,31 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) { | |||||||
|     auto save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf; |     auto save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf; | ||||||
|  |  | ||||||
|     int errorCode = 0; |     int errorCode = 0; | ||||||
|     if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) { |  | ||||||
|  |     std::string firstName = AUTOU16TOU8(save->szFirstName); | ||||||
|  |     std::string lastName = AUTOU16TOU8(save->szLastName); | ||||||
|  |     int nameCheck = 0; | ||||||
|  |  | ||||||
|  |     // if FNCode isn't 0, it's a wheel name | ||||||
|  |     if (save->iFNCode != 0) { | ||||||
|  |         if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) { | ||||||
|  |             errorCode = 4; | ||||||
|  |         } else { | ||||||
|  |             nameCheck = settings::APPROVEWHEELNAMES ? 1 : 0; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         // custom name | ||||||
|  |         nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (errorCode == 0) { | ||||||
|  |         if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) { | ||||||
|             errorCode = 4; |             errorCode = 4; | ||||||
|         } |         } | ||||||
|     else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) { |         else if (!Database::isNameFree(firstName, lastName)) { | ||||||
|             errorCode = 1; |             errorCode = 1; | ||||||
|         } |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (errorCode != 0) { |     if (errorCode != 0) { | ||||||
|         INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp); |         INITSTRUCT(sP_LS2CL_REP_CHECK_CHAR_NAME_FAIL, resp); | ||||||
| @@ -527,9 +567,15 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) { | |||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (!Database::changeName(save, loginSessions[sock].userID)) |     if (!Database::changeName(save->iPCUID, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck)) | ||||||
|         return invalidCharacter(sock); |         return invalidCharacter(sock); | ||||||
|  |  | ||||||
|  |     // fire name check event if needed | ||||||
|  |     if (nameCheck != 1) { | ||||||
|  |         std::string namereq = std::to_string(save->iPCUID) + " " + firstName + " " + lastName; | ||||||
|  |         Monitor::namereqs.push_back(namereq); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp); |     INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp); | ||||||
|     resp.iPC_UID = save->iPCUID; |     resp.iPC_UID = save->iPCUID; | ||||||
|     memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName)); |     memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName)); | ||||||
| @@ -541,8 +587,10 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) { | |||||||
|     sock->sendPacket(resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC); |     sock->sendPacket(resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC); | ||||||
|  |  | ||||||
|     DEBUGLOG( |     DEBUGLOG( | ||||||
|         std::cout << "Login Server: Name check success for character [" << save->iPCUID << "]" << std::endl; |         std::cout << "Login Server: Name change request for character [" << save->iPCUID << "]" << std::endl; | ||||||
|         std::cout << "\tNew name: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl;      |         std::cout << "\tNew name: " << firstName << " " << lastName; | ||||||
|  |         if (nameCheck != 1) std::cout << " (pending approval)"; | ||||||
|  |         std::cout << std::endl; | ||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -621,21 +669,164 @@ bool CNLoginServer::exitDuplicate(int accountId) { | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool CNLoginServer::isLoginDataGood(std::string login, std::string password) { | bool CNLoginServer::isUsernameGood(std::string& login) { | ||||||
|     std::regex loginRegex("[a-zA-Z0-9_-]{4,32}"); |     const std::regex loginRegex("[a-zA-Z0-9_-]{4,32}"); | ||||||
|     std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}"); |     return (std::regex_match(login, loginRegex)); | ||||||
|  | } | ||||||
|  |  | ||||||
|     return (std::regex_match(login, loginRegex) && std::regex_match(password, passwordRegex)); | bool CNLoginServer::isPasswordGood(std::string& password) { | ||||||
|  |     const std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}"); | ||||||
|  |     return (std::regex_match(password, passwordRegex)); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword) { | bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tryPassword) { | ||||||
|     return BCrypt::validatePassword(tryPassword, actualPassword); |     return BCrypt::validatePassword(tryPassword, actualPassword); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool CNLoginServer::isNameWheelNameGood(int fnCode, int mnCode, int lnCode, std::string& firstName, std::string& lastName) { | ||||||
|  |     if (fnCode >= LoginServer::WheelFirstNames.size() | ||||||
|  |         || mnCode >= LoginServer::WheelMiddleNames.size() | ||||||
|  |         || lnCode >= LoginServer::WheelLastNames.size()) { | ||||||
|  |         std::cout << "[WARN] Login Server: Invalid name codes received: " << fnCode << " " << mnCode << " " << lnCode << std::endl; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // client sends 1 if not selected for these. they point to a single blank space. why. | ||||||
|  |     // just change them to 0, which points to an empty string; keeps the code much cleaner | ||||||
|  |     if (mnCode == 1) mnCode = 0; | ||||||
|  |     if (lnCode == 1) lnCode = 0; | ||||||
|  |  | ||||||
|  |     std::string firstNameFromWheel = LoginServer::WheelFirstNames[fnCode]; | ||||||
|  |  | ||||||
|  |     std::string middleNamePart = LoginServer::WheelMiddleNames[mnCode]; | ||||||
|  |     std::string lastNamePart = LoginServer::WheelLastNames[lnCode]; | ||||||
|  |     if (mnCode != 0 && middleNamePart[middleNamePart.size() - 1] != ' ') { | ||||||
|  |         // If there's a middle name, we need to lowercase the last name | ||||||
|  |         std::transform(lastNamePart.begin(), lastNamePart.end(), lastNamePart.begin(), ::tolower); | ||||||
|  |     } | ||||||
|  |     std::string lastNameFromWheel = middleNamePart + lastNamePart; | ||||||
|  |  | ||||||
|  |     if (firstNameFromWheel.empty() || lastNameFromWheel.empty()) { | ||||||
|  |         std::cout << "[WARN] Login Server: Invalid wheel name combo: " << fnCode << " " << mnCode << " " << lnCode << std::endl; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (firstName != firstNameFromWheel || lastName != lastNameFromWheel) { | ||||||
|  |         std::cout << "[WARN] Login Server: Name wheel mismatch. Expected " << firstNameFromWheel << " " << lastNameFromWheel << ", got " << firstName << " " << lastName << std::endl; | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  |  | ||||||
| bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) { | bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) { | ||||||
|     //Allow alphanumeric and dot characters in names(disallows dot and space characters at the beginning of a name) |     //Allow alphanumeric and dot characters in names(disallows dot and space characters at the beginning of a name) | ||||||
|     std::regex firstnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)"); |     std::regex firstnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)"); | ||||||
|     std::regex lastnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)"); |     std::regex lastnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)"); | ||||||
|     return (std::regex_match(Firstname, firstnamecheck) && std::regex_match(Lastname, lastnamecheck)); |     return (std::regex_match(Firstname, firstnamecheck) && std::regex_match(Lastname, lastnamecheck)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool CNLoginServer::isAuthMethodAllowed(AuthMethod authMethod) { | ||||||
|  |     // the config file specifies "comma-separated" but tbh we don't care | ||||||
|  |     switch (authMethod) { | ||||||
|  |     case AuthMethod::PASSWORD: | ||||||
|  |         return settings::AUTHMETHODS.find("password") != std::string::npos; | ||||||
|  |     case AuthMethod::COOKIE: | ||||||
|  |         return settings::AUTHMETHODS.find("cookie") != std::string::npos; | ||||||
|  |     default: | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CNLoginServer::checkPassword(CNSocket* sock, std::string& password) { | ||||||
|  |     // check password auth allowed | ||||||
|  |     if (!CNLoginServer::isAuthMethodAllowed(AuthMethod::PASSWORD)) { | ||||||
|  |         // send a custom error message | ||||||
|  |         INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg); | ||||||
|  |         std::string text = "Password login disabled\n"; | ||||||
|  |         text += "This server has disabled logging in with plaintext passwords.\n"; | ||||||
|  |         text += "Please contact an admin for assistance."; | ||||||
|  |         U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg)); | ||||||
|  |         msg.iDuringTime = 12; | ||||||
|  |         sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // check regex | ||||||
|  |     if (!CNLoginServer::isPasswordGood(password)) { | ||||||
|  |         // send a custom error message | ||||||
|  |         INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg); | ||||||
|  |         std::string text = "Invalid password\n"; | ||||||
|  |         text += "Password has to be 8 - 32 characters long"; | ||||||
|  |         U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg)); | ||||||
|  |         msg.iDuringTime = 10; | ||||||
|  |         sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CNLoginServer::checkUsername(CNSocket* sock, std::string& username) { | ||||||
|  |     // check username regex | ||||||
|  |     if (!CNLoginServer::isUsernameGood(username)) { | ||||||
|  |         // send a custom error message | ||||||
|  |         INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg); | ||||||
|  |         std::string text = "Invalid login\n"; | ||||||
|  |         text += "Login has to be 4 - 32 characters long and can't contain special characters other than dash and underscore"; | ||||||
|  |         U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg)); | ||||||
|  |         msg.iDuringTime = 10; | ||||||
|  |         sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CNLoginServer::checkToken(CNSocket* sock, Database::Account& account, std::string& token, bool isCookieAuth) { | ||||||
|  |     // check for valid cookie first | ||||||
|  |     if (isCookieAuth && CNLoginServer::isAuthMethodAllowed(AuthMethod::COOKIE)) { | ||||||
|  |         const char *cookie = token.c_str(); | ||||||
|  |         if (Database::checkCookie(account.AccountID, cookie)) { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // cookie check disabled or failed; check to see if it's a plaintext password | ||||||
|  |     if (CNLoginServer::isAuthMethodAllowed(AuthMethod::PASSWORD) | ||||||
|  |         && CNLoginServer::isPasswordCorrect(account.Password, token)) { | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return false; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool CNLoginServer::checkBan(CNSocket* sock, Database::Account& account) { | ||||||
|  |     // check if the account is banned | ||||||
|  |     if (account.BannedUntil > getTimestamp()) { | ||||||
|  |         // send a custom error message | ||||||
|  |         INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg); | ||||||
|  |  | ||||||
|  |         // ceiling devision | ||||||
|  |         int64_t remainingDays = (account.BannedUntil-getTimestamp()) / 86400 + ((account.BannedUntil - getTimestamp()) % 86400 != 0); | ||||||
|  |  | ||||||
|  |         std::string text = "Your account has been banned. \nReason: "; | ||||||
|  |         text += account.BanReason; | ||||||
|  |         text += "\nBan expires in " + std::to_string(remainingDays) + " day"; | ||||||
|  |         if (remainingDays > 1) | ||||||
|  |             text += "s"; | ||||||
|  |  | ||||||
|  |         U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg)); | ||||||
|  |         msg.iDuringTime = 99999999; | ||||||
|  |         sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return false; | ||||||
|  | } | ||||||
| #pragma endregion | #pragma endregion | ||||||
|   | |||||||
| @@ -1,11 +1,18 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
| #include "core/Core.hpp" | #include "core/Core.hpp" | ||||||
|  | #include "db/Database.hpp" | ||||||
|  |  | ||||||
| #include "Player.hpp" | #include "Player.hpp" | ||||||
|  |  | ||||||
| #include <map> | #include <map> | ||||||
|  |  | ||||||
|  | namespace LoginServer { | ||||||
|  |     extern std::vector<std::string> WheelFirstNames; | ||||||
|  |     extern std::vector<std::string> WheelMiddleNames; | ||||||
|  |     extern std::vector<std::string> WheelLastNames; | ||||||
|  | } | ||||||
|  |  | ||||||
| struct CNLoginData { | struct CNLoginData { | ||||||
|     int userID; |     int userID; | ||||||
|     time_t lastHeartbeat; |     time_t lastHeartbeat; | ||||||
| @@ -23,6 +30,13 @@ enum class LoginError { | |||||||
|     UPDATED_EUALA_REQUIRED = 9 |     UPDATED_EUALA_REQUIRED = 9 | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | #define USE_COOKIE_FIELDS 2 | ||||||
|  |  | ||||||
|  | enum class AuthMethod { | ||||||
|  |     PASSWORD = 1, | ||||||
|  |     COOKIE = 2 | ||||||
|  | }; | ||||||
|  |  | ||||||
| // WARNING: THERE CAN ONLY BE ONE OF THESE SERVERS AT A TIME!!!!!! TODO: change loginSessions & packet handlers to be non-static | // WARNING: THERE CAN ONLY BE ONE OF THESE SERVERS AT A TIME!!!!!! TODO: change loginSessions & packet handlers to be non-static | ||||||
| class CNLoginServer : public CNServer { | class CNLoginServer : public CNServer { | ||||||
| private: | private: | ||||||
| @@ -39,10 +53,18 @@ private: | |||||||
|     static void changeName(CNSocket* sock, CNPacketData* data); |     static void changeName(CNSocket* sock, CNPacketData* data); | ||||||
|     static void duplicateExit(CNSocket* sock, CNPacketData* data); |     static void duplicateExit(CNSocket* sock, CNPacketData* data); | ||||||
|  |  | ||||||
|     static bool isLoginDataGood(std::string login, std::string password); |     static bool isUsernameGood(std::string& login); | ||||||
|  |     static bool isPasswordGood(std::string& password); | ||||||
|     static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword); |     static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword); | ||||||
|     static bool isAccountInUse(int accountId); |     static bool isAccountInUse(int accountId); | ||||||
|  |     static bool isNameWheelNameGood(int fnCode, int mnCode, int lnCode, std::string& firstName, std::string& lastName); | ||||||
|     static bool isCharacterNameGood(std::string Firstname, std::string Lastname); |     static bool isCharacterNameGood(std::string Firstname, std::string Lastname); | ||||||
|  |     static bool isAuthMethodAllowed(AuthMethod authMethod); | ||||||
|  |     static bool checkUsername(CNSocket* sock, std::string& username); | ||||||
|  |     static bool checkPassword(CNSocket* sock, std::string& password); | ||||||
|  |     static bool checkToken(CNSocket* sock, Database::Account& account, std::string& token, bool isCookieAuth); | ||||||
|  |     static bool checkBan(CNSocket* sock, Database::Account& account); | ||||||
|  |  | ||||||
|     static void newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC); |     static void newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC); | ||||||
|     // returns true if success |     // returns true if success | ||||||
|     static bool exitDuplicate(int accountId); |     static bool exitDuplicate(int accountId); | ||||||
|   | |||||||
| @@ -14,6 +14,13 @@ static std::mutex sockLock; // guards socket list | |||||||
| static std::list<SOCKET> sockets; | static std::list<SOCKET> sockets; | ||||||
| static sockaddr_in address; | static sockaddr_in address; | ||||||
|  |  | ||||||
|  | std::vector<std::string> Monitor::chats; | ||||||
|  | std::vector<std::string> Monitor::bcasts; | ||||||
|  | std::vector<std::string> Monitor::emails; | ||||||
|  | std::vector<std::string> Monitor::namereqs; | ||||||
|  |  | ||||||
|  | using namespace Monitor; | ||||||
|  |  | ||||||
| static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) { | static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) { | ||||||
|     int n = 0; |     int n = 0; | ||||||
|     int sock = *it; |     int sock = *it; | ||||||
| @@ -99,15 +106,23 @@ outer: | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         // chat |         // chat | ||||||
|         for (auto& str : Chat::dump) { |         for (auto& str : chats) { | ||||||
|             n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str()); |             n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str()); | ||||||
|  |  | ||||||
|             if (!transmit(it, buff, n)) |             if (!transmit(it, buff, n)) | ||||||
|                 goto outer; |                 goto outer; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // announcements | ||||||
|  |         for (auto& str : bcasts) { | ||||||
|  |             n = std::snprintf(buff, sizeof(buff), "bcast %s\n", str.c_str()); | ||||||
|  |  | ||||||
|  |             if (!transmit(it, buff, n)) | ||||||
|  |                 goto outer; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         // emails |         // emails | ||||||
|         for (auto& str : Email::dump) { |         for (auto& str : emails) { | ||||||
|             n = process_email(buff, str); |             n = process_email(buff, str); | ||||||
|  |  | ||||||
|             if (!transmit(it, buff, n)) |             if (!transmit(it, buff, n)) | ||||||
| @@ -117,14 +132,24 @@ outer: | |||||||
|                 goto outer; |                 goto outer; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         // name requests | ||||||
|  |         for (auto& str : namereqs) { | ||||||
|  |             n = std::snprintf(buff, sizeof(buff), "namereq %s\n", str.c_str()); | ||||||
|  |  | ||||||
|  |             if (!transmit(it, buff, n)) | ||||||
|  |                 goto outer; | ||||||
|  |         } | ||||||
|  |  | ||||||
|         if (!transmit(it, (char*)"end\n", 4)) |         if (!transmit(it, (char*)"end\n", 4)) | ||||||
|             continue; |             continue; | ||||||
|  |  | ||||||
|         it++; |         it++; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     Chat::dump.clear(); |     chats.clear(); | ||||||
|     Email::dump.clear(); |     bcasts.clear(); | ||||||
|  |     emails.clear(); | ||||||
|  |     namereqs.clear(); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) { | bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) { | ||||||
| @@ -180,9 +205,14 @@ SOCKET Monitor::init() { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     address.sin_family = AF_INET; |     address.sin_family = AF_INET; | ||||||
|     address.sin_addr.s_addr = INADDR_ANY; |  | ||||||
|     address.sin_port = htons(settings::MONITORPORT); |     address.sin_port = htons(settings::MONITORPORT); | ||||||
|  |  | ||||||
|  |     if (!inet_pton(AF_INET, settings::MONITORLISTENIP.c_str(), &address.sin_addr)) { | ||||||
|  |         std::cout << "Failed to set monitor listen address" << std::endl; | ||||||
|  |         printSocketError("inet_pton"); | ||||||
|  |         exit(1); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     if (SOCKETERROR(bind(listener, (struct sockaddr*)&address, sizeof(address)))) { |     if (SOCKETERROR(bind(listener, (struct sockaddr*)&address, sizeof(address)))) { | ||||||
|         std::cout << "Failed to bind to monitor port" << std::endl; |         std::cout << "Failed to bind to monitor port" << std::endl; | ||||||
|         printSocketError("bind"); |         printSocketError("bind"); | ||||||
| @@ -206,7 +236,7 @@ SOCKET Monitor::init() { | |||||||
|         exit(EXIT_FAILURE); |         exit(EXIT_FAILURE); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     std::cout << "Monitor listening on *:" << settings::MONITORPORT << std::endl; |     std::cout << "Monitor listening on " << settings::MONITORLISTENIP << ":" << settings::MONITORPORT << std::endl; | ||||||
|  |  | ||||||
|     REGISTER_SHARD_TIMER(tick, settings::MONITORINTERVAL); |     REGISTER_SHARD_TIMER(tick, settings::MONITORINTERVAL); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -3,6 +3,11 @@ | |||||||
| #include "core/Core.hpp" | #include "core/Core.hpp" | ||||||
|  |  | ||||||
| namespace Monitor { | namespace Monitor { | ||||||
|  |     extern std::vector<std::string> chats; | ||||||
|  |     extern std::vector<std::string> bcasts; | ||||||
|  |     extern std::vector<std::string> emails; | ||||||
|  |     extern std::vector<std::string> namereqs; | ||||||
|  |  | ||||||
|     SOCKET init(); |     SOCKET init(); | ||||||
|     bool acceptConnection(SOCKET, uint16_t); |     bool acceptConnection(SOCKET, uint16_t); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -9,10 +9,13 @@ | |||||||
| // defaults :) | // defaults :) | ||||||
| int settings::VERBOSITY = 1; | int settings::VERBOSITY = 1; | ||||||
| bool settings::SANDBOX = true; | bool settings::SANDBOX = true; | ||||||
|  | std::string settings::SANDBOXEXTRAPATH = ""; | ||||||
|  |  | ||||||
| int settings::LOGINPORT = 23000; | int settings::LOGINPORT = 23000; | ||||||
| bool settings::APPROVEALLNAMES = true; | bool settings::APPROVEWHEELNAMES = true; | ||||||
|  | bool settings::APPROVECUSTOMNAMES = true; | ||||||
| bool settings::AUTOCREATEACCOUNTS = true; | bool settings::AUTOCREATEACCOUNTS = true; | ||||||
|  | std::string settings::AUTHMETHODS = "password"; | ||||||
| int settings::DBSAVEINTERVAL = 240; | int settings::DBSAVEINTERVAL = 240; | ||||||
|  |  | ||||||
| int settings::SHARDPORT = 23001; | int settings::SHARDPORT = 23001; | ||||||
| @@ -62,6 +65,7 @@ bool settings::DISABLEFIRSTUSEFLAG = true; | |||||||
| // monitor settings | // monitor settings | ||||||
| bool settings::MONITORENABLED = false; | bool settings::MONITORENABLED = false; | ||||||
| int settings::MONITORPORT = 8003; | int settings::MONITORPORT = 8003; | ||||||
|  | std::string settings::MONITORLISTENIP = "127.0.0.1"; | ||||||
| int settings::MONITORINTERVAL = 5000; | int settings::MONITORINTERVAL = 5000; | ||||||
|  |  | ||||||
| // event mode settings | // event mode settings | ||||||
| @@ -84,9 +88,12 @@ void settings::init() { | |||||||
|  |  | ||||||
|     VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY); |     VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY); | ||||||
|     SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX); |     SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX); | ||||||
|  |     SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH); | ||||||
|     LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); |     LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); | ||||||
|     APPROVEALLNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVEALLNAMES); |     APPROVEWHEELNAMES = reader.GetBoolean("login", "acceptallwheelnames", APPROVEWHEELNAMES); | ||||||
|  |     APPROVECUSTOMNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVECUSTOMNAMES); | ||||||
|     AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS); |     AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS); | ||||||
|  |     AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS); | ||||||
|     DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL); |     DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL); | ||||||
|     SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT); |     SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT); | ||||||
|     SHARDSERVERIP = reader.Get("shard", "ip", SHARDSERVERIP); |     SHARDSERVERIP = reader.Get("shard", "ip", SHARDSERVERIP); | ||||||
| @@ -117,5 +124,6 @@ void settings::init() { | |||||||
|     IZRACESCORECAPPED = reader.GetBoolean("shard", "izracescorecapped", IZRACESCORECAPPED); |     IZRACESCORECAPPED = reader.GetBoolean("shard", "izracescorecapped", IZRACESCORECAPPED); | ||||||
|     MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED); |     MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED); | ||||||
|     MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT); |     MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT); | ||||||
|  |     MONITORLISTENIP = reader.Get("monitor", "listenip", MONITORLISTENIP); | ||||||
|     MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL); |     MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,13 +1,18 @@ | |||||||
| #pragma once | #pragma once | ||||||
|  |  | ||||||
|  | #include <stdint.h> | ||||||
| #include <string> | #include <string> | ||||||
|  | #include <time.h> | ||||||
|  |  | ||||||
| namespace settings { | namespace settings { | ||||||
|     extern int VERBOSITY; |     extern int VERBOSITY; | ||||||
|     extern bool SANDBOX; |     extern bool SANDBOX; | ||||||
|  |     extern std::string SANDBOXEXTRAPATH; | ||||||
|     extern int LOGINPORT; |     extern int LOGINPORT; | ||||||
|     extern bool APPROVEALLNAMES; |     extern bool APPROVEWHEELNAMES; | ||||||
|  |     extern bool APPROVECUSTOMNAMES; | ||||||
|     extern bool AUTOCREATEACCOUNTS; |     extern bool AUTOCREATEACCOUNTS; | ||||||
|  |     extern std::string AUTHMETHODS; | ||||||
|     extern int DBSAVEINTERVAL; |     extern int DBSAVEINTERVAL; | ||||||
|     extern int SHARDPORT; |     extern int SHARDPORT; | ||||||
|     extern std::string SHARDSERVERIP; |     extern std::string SHARDSERVERIP; | ||||||
| @@ -36,6 +41,7 @@ namespace settings { | |||||||
|     extern int EVENTMODE; |     extern int EVENTMODE; | ||||||
|     extern bool MONITORENABLED; |     extern bool MONITORENABLED; | ||||||
|     extern int MONITORPORT; |     extern int MONITORPORT; | ||||||
|  |     extern std::string MONITORLISTENIP; | ||||||
|     extern int MONITORINTERVAL; |     extern int MONITORINTERVAL; | ||||||
|     extern bool DISABLEFIRSTUSEFLAG; |     extern bool DISABLEFIRSTUSEFLAG; | ||||||
|     extern bool IZRACESCORECAPPED; |     extern bool IZRACESCORECAPPED; | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								tdata
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								tdata
									
									
									
									
									
								
							 Submodule tdata updated: 8c98c83682...bdb611b092
									
								
							
							
								
								
									
										8
									
								
								vendor/bcrypt/bcrypt.c
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								vendor/bcrypt/bcrypt.c
									
									
									
									
										vendored
									
									
								
							| @@ -22,13 +22,14 @@ | |||||||
| #endif | #endif | ||||||
| #include <errno.h> | #include <errno.h> | ||||||
|  |  | ||||||
| #if defined(_WIN32) || defined(_WIN64) |  | ||||||
| // On windows we need to generate random bytes differently. |  | ||||||
| #if defined(_WIN32) && !defined(_WIN64) | #if defined(_WIN32) && !defined(_WIN64) | ||||||
| typedef __int32 ssize_t; | typedef __int32 ssize_t; | ||||||
| #elif defined(_WIN32) && defined(_WIN64) | #elif defined(_WIN32) && defined(_WIN64) | ||||||
| typedef __int64 ssize_t; | typedef __int64 ssize_t; | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #if defined(_WIN32) || defined(_WIN64) | ||||||
|  | // On windows we need to generate random bytes differently. | ||||||
| #define BCRYPT_HASHSIZE 60 | #define BCRYPT_HASHSIZE 60 | ||||||
|  |  | ||||||
| #include "bcrypt.h" | #include "bcrypt.h" | ||||||
| @@ -37,9 +38,10 @@ typedef __int64 ssize_t; | |||||||
| #include <wincrypt.h> /* CryptAcquireContext, CryptGenRandom */ | #include <wincrypt.h> /* CryptAcquireContext, CryptGenRandom */ | ||||||
| #else | #else | ||||||
| #include "bcrypt.h" | #include "bcrypt.h" | ||||||
| #include "ow-crypt.h" |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | #include "ow-crypt.h" | ||||||
|  |  | ||||||
| #define RANDBYTES (16) | #define RANDBYTES (16) | ||||||
|  |  | ||||||
| static int try_close(int fd) | static int try_close(int fd) | ||||||
|   | |||||||
							
								
								
									
										52
									
								
								win-setup.ps1
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								win-setup.ps1
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | |||||||
|  | # Run this first if needed: | ||||||
|  | # Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process | ||||||
|  | param ( | ||||||
|  |     # height of largest column without top bar | ||||||
|  |     [Parameter(Mandatory=$true)] | ||||||
|  |     [string]$protocolVersion | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | $ErrorActionPreference = 'Stop' | ||||||
|  | # check for vscmd | ||||||
|  | if ([string]::IsNullOrEmpty($env:VSCMD_VER)) { | ||||||
|  |     Write-Host 'Must be run inside of VS Developer Powershell' | ||||||
|  |     exit 1 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # check for git | ||||||
|  | try { | ||||||
|  |     $git_version = git --version | ||||||
|  | } catch { | ||||||
|  |     Write-Host 'git not installed' | ||||||
|  |     exit 1 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | # setup vcpkg | ||||||
|  | if (Test-Path -Path 'vcpkg\') { | ||||||
|  |     Write-Host 'vcpkg already setup' | ||||||
|  | } else { | ||||||
|  |     Write-Host 'Setting up vcpkg...' | ||||||
|  |     git clone "https://github.com/microsoft/vcpkg.git" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | if (-not (Test-Path -Path 'vcpkg\vcpkg.exe')) { | ||||||
|  |     Write-Host 'Bootstrapping vcpkg...' | ||||||
|  |     Start-Process -Wait -NoNewWindow -FilePath 'vcpkg\bootstrap-vcpkg.bat' -ArgumentList '-disableMetrics' | ||||||
|  | } | ||||||
|  |  | ||||||
|  | $env:VCPKG_ROOT='' # ignore msvc's vcpkg root, it doesn't work | ||||||
|  | $vcpkg = (Resolve-Path -Path '.\vcpkg') | ||||||
|  | Write-Host "vcpkg installed to $vcpkg" | ||||||
|  | Start-Process -Wait -NoNewWindow -FilePath 'vcpkg\vcpkg.exe' -ArgumentList 'install sqlite3:x64-windows' | ||||||
|  |  | ||||||
|  | # setup cmake project | ||||||
|  | if (Test-Path -Path 'build\') { | ||||||
|  |     Write-Host 'cmake project already setup'; | ||||||
|  | } else { | ||||||
|  |     Write-Host 'Setting up cmake project...' | ||||||
|  |     Start-Process -Wait -NoNewWindow -FilePath 'cmake' -ArgumentList "-B build -DPROTOCOL_VERSION=$protocolVersion -DCMAKE_TOOLCHAIN_FILE=$vcpkg\scripts\buildsystems\vcpkg.cmake" | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Write-Host 'Done!' | ||||||
|  | $sln = (Resolve-Path -Path '.\build\OpenFusion.sln') | ||||||
|  | Write-Host "Solution file is at $sln" | ||||||
		Reference in New Issue
	
	Block a user