mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-10-20 12:21:04 +00:00
Compare commits
88 Commits
1.4
...
9cdec59810
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9cdec59810 | ||
c636c538eb | |||
d3bef95a7f | |||
![]() |
650f947451 | ||
![]() |
f4b36b8f73 | ||
![]() |
b12aecad63 | ||
![]() |
5bf0c8f3ea | ||
![]() |
2ddc956c9b | ||
![]() |
4f0ae027a5 | ||
23ab908366 | |||
be6a4c0a5d | |||
8eb1af20c8 | |||
e73daa0865 | |||
743a39c125 | |||
a9af8713bc | |||
4825267537 | |||
a92cfaff25 | |||
abcfa3445b | |||
2bf14200f7 | |||
876a9c82cd | |||
fb5b0eeeb9 | |||
7aabc507e7 | |||
2914b95cff | |||
dbd2ec2270 | |||
50e00a6772 | |||
7471bcbf38 | |||
100b4605ec | |||
741b898230 | |||
3f44f53f97 | |||
d92b407349 | |||
9b3e856a05 | |||
eb8e54c1f0 | |||
1ba0f5e14a | |||
12dde394c0 | |||
b1eea6d4fe | |||
f126b88781 | |||
2dbe2629c1 | |||
271eef83d3 | |||
ca0d608a87 | |||
741bfb675b | |||
c5dd745aa1 | |||
998b12617e | |||
129d1c2fe3 | |||
![]() |
1bd4d2fbee | ||
63d4087488 | |||
abda9dc158 | |||
![]() |
7b7d8bce45 | ||
![]() |
a9942eadab | ||
![]() |
36638b1522 | ||
![]() |
685cee2561 | ||
![]() |
b683152fbf | ||
![]() |
86576d48f6 | ||
![]() |
4e1767ad58 | ||
![]() |
4354cab7e3 | ||
4f6979f236 | |||
1404fa0bb7 | |||
7f65ec5b96 | |||
041908ddda | |||
57c9f139a2 | |||
d3af99fcef | |||
94af318139 | |||
91f9a2085b | |||
00865e1c7b | |||
28bfd14362 | |||
6412a9a89e | |||
f376c68115 | |||
3c6afa0322 | |||
384a2ece78 | |||
![]() |
f4a7ab7373 | ||
bc1153c97e | |||
c6ffcd4804 | |||
b3c844650b | |||
13bd299de4 | |||
1e3d183f9a | |||
dfe596447b | |||
9297e82589 | |||
4319ee57a0 | |||
09e452a09d | |||
3c1e08372d | |||
05d6174351 | |||
9ab998688c | |||
7249b54127 | |||
4fa18a9642 | |||
![]() |
8a294cb2be | ||
57e9834786 | |||
70c3650ee1 | |||
e2c85aa03f | |||
![]() |
ed285e5d24 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
version.h
|
146
.github/workflows/check-builds.yaml
vendored
Normal file
146
.github/workflows/check-builds.yaml
vendored
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
name: Check Builds
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
- vendor/**
|
||||||
|
- .github/workflows/check-builds.yaml
|
||||||
|
- CMakeLists.txt
|
||||||
|
- Makefile
|
||||||
|
pull_request:
|
||||||
|
types: ready_for_review
|
||||||
|
paths:
|
||||||
|
- src/**
|
||||||
|
- vendor/**
|
||||||
|
- CMakeLists.txt
|
||||||
|
- Makefile
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ubuntu-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set environment
|
||||||
|
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||||
|
shell: bash
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Install dependencies
|
||||||
|
run: sudo apt install clang cmake snap -y && sudo snap install powershell --classic
|
||||||
|
- name: Check compilation
|
||||||
|
run: |
|
||||||
|
$versions = "104", "728", "1013"
|
||||||
|
|
||||||
|
foreach ($version in $versions) {
|
||||||
|
Write-Output "Cleaning old output"
|
||||||
|
Invoke-Expression "make clean"
|
||||||
|
if ($LASTEXITCODE -ne "0") {
|
||||||
|
Write-Error "make clean failed for version $version" -ErrorAction Stop
|
||||||
|
}
|
||||||
|
Write-Output "Building version $version"
|
||||||
|
Invoke-Expression "make -j8 PROTOCOL_VERSION=$version"
|
||||||
|
if ($LASTEXITCODE -ne "0") {
|
||||||
|
Write-Error "make failed for version $version" -ErrorAction Stop
|
||||||
|
}
|
||||||
|
Rename-Item -Path "bin/fusion" -newName "$version-fusion"
|
||||||
|
Write-Output "Built version $version"
|
||||||
|
}
|
||||||
|
Copy-Item -Path "sql" -Destination "bin/sql" -Recurse
|
||||||
|
Copy-Item -Path "config.ini" -Destination "bin"
|
||||||
|
shell: pwsh
|
||||||
|
- name: Upload build artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: 'ubuntu20_04-bin-x64-${{ env.SHORT_SHA }}'
|
||||||
|
path: bin
|
||||||
|
|
||||||
|
windows-build:
|
||||||
|
runs-on: windows-2019
|
||||||
|
steps:
|
||||||
|
- name: Set environment
|
||||||
|
run: $s = $env:GITHUB_SHA.subString(0, 7); echo "SHORT_SHA=$s" >> $env:GITHUB_ENV
|
||||||
|
shell: pwsh
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Check compilation
|
||||||
|
run: |
|
||||||
|
$versions = "104", "728", "1013"
|
||||||
|
$configurations = "Release"
|
||||||
|
# "Debug" builds are disabled, since we don't really need them
|
||||||
|
|
||||||
|
$vsPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise"
|
||||||
|
|
||||||
|
Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
|
||||||
|
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation
|
||||||
|
|
||||||
|
Invoke-Expression "vcpkg install sqlite3:x64-windows"
|
||||||
|
Invoke-Expression "vcpkg integrate install"
|
||||||
|
|
||||||
|
foreach ($version in $versions) {
|
||||||
|
if (Test-Path -LiteralPath "build") {
|
||||||
|
Remove-Item "build" -Recurse
|
||||||
|
Write-Output "Deleted existing build folder"
|
||||||
|
}
|
||||||
|
Invoke-Expression "cmake -B build -DPROTOCOL_VERSION=$version -DCMAKE_TOOLCHAIN_FILE=C:/vcpkg/scripts/buildsystems/vcpkg.cmake"
|
||||||
|
if ($LASTEXITCODE -ne "0") {
|
||||||
|
Write-Error "cmake generation failed for version $version" -ErrorAction Stop
|
||||||
|
}
|
||||||
|
Write-Output "Generated build files for version $version"
|
||||||
|
|
||||||
|
foreach ($configuration in $configurations) {
|
||||||
|
Write-Output "Building version $version $configuration"
|
||||||
|
Invoke-Expression "msbuild build\OpenFusion.sln /maxcpucount:8 /p:BuildInParallel=true /p:CL_MPCount=8 /p:UseMultiToolTask=true /p:Configuration=$configuration"
|
||||||
|
if ($LASTEXITCODE -ne "0") {
|
||||||
|
Write-Error "msbuild build failed for version $version" -ErrorAction Stop
|
||||||
|
}
|
||||||
|
Rename-Item -Path "bin/$configuration" -newName "$version-$configuration"
|
||||||
|
Write-Output "Built version $version $configuration"
|
||||||
|
Copy-Item -Path "sql" -Destination "bin/$version-$configuration/sql" -Recurse
|
||||||
|
Copy-Item -Path "config.ini" -Destination "bin/$version-$configuration"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shell: pwsh
|
||||||
|
- name: Upload build artifact
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: 'windows-vs2019-bin-x64-${{ env.SHORT_SHA }}'
|
||||||
|
path: bin
|
||||||
|
|
||||||
|
copy-artifacts:
|
||||||
|
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [windows-build, ubuntu-build]
|
||||||
|
env:
|
||||||
|
BOT_SSH_KEY: ${{ secrets.BOT_SSH_KEY }}
|
||||||
|
ENDPOINT: ${{ secrets.ENDPOINT }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: recursive
|
||||||
|
fetch-depth: 0
|
||||||
|
- run: |
|
||||||
|
GITDESC=$(git describe --tags)
|
||||||
|
mkdir $GITDESC
|
||||||
|
echo "ARTDIR=$GITDESC" >> $GITHUB_ENV
|
||||||
|
- uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
path: ${{ env.ARTDIR }}
|
||||||
|
- name: Upload artifacts
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
sudo apt install zip -y
|
||||||
|
cd $ARTDIR
|
||||||
|
for build in *; do
|
||||||
|
cd $build
|
||||||
|
zip -r ../$build.zip *
|
||||||
|
cd ..
|
||||||
|
rm -r $build
|
||||||
|
done
|
||||||
|
cd ..
|
||||||
|
umask 077
|
||||||
|
printf %s "$BOT_SSH_KEY" > cdn_key
|
||||||
|
scp -i cdn_key -o StrictHostKeyChecking=no -r $ARTDIR $ENDPOINT
|
26
.vscode/launch.json
vendored
Normal file
26
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Debug (Linux)",
|
||||||
|
"type": "cppdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/bin/fusion",
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Debug (Windows)",
|
||||||
|
"type": "cppvsdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/bin/Debug/winfusion.exe",
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Release (Windows)",
|
||||||
|
"type": "cppvsdbg",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/bin/Release/winfusion.exe",
|
||||||
|
"cwd": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@@ -43,7 +43,10 @@ add_executable(openfusion ${SOURCES})
|
|||||||
|
|
||||||
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
|
set_target_properties(openfusion PROPERTIES OUTPUT_NAME ${BIN_NAME})
|
||||||
|
|
||||||
target_link_libraries(openfusion sqlite3)
|
# find sqlite3 and use it
|
||||||
|
find_package(SQLite3 REQUIRED)
|
||||||
|
target_include_directories(openfusion PRIVATE ${SQLite3_INCLUDE_DIRS})
|
||||||
|
target_link_libraries(openfusion PRIVATE ${SQLite3_LIBRARIES})
|
||||||
|
|
||||||
# Makes it so config, tdata, etc. get picked up when starting via the debugger in VS
|
# Makes it so config, tdata, etc. get picked up when starting via the debugger in VS
|
||||||
set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}")
|
||||||
@@ -53,5 +56,5 @@ set_property(TARGET openfusion PROPERTY VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_S
|
|||||||
# It's not something you should do, but it's there if you need it...
|
# It's not something you should do, but it's there if you need it...
|
||||||
if (NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "MinGW Makefiles")
|
if (NOT CMAKE_GENERATOR MATCHES "Visual Studio" AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_GENERATOR MATCHES "MinGW Makefiles")
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
target_link_libraries(openfusion pthread)
|
target_link_libraries(openfusion PRIVATE pthread)
|
||||||
endif()
|
endif()
|
||||||
|
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM debian:latest
|
||||||
|
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
RUN apt-get -y update && apt-get install -y \
|
||||||
|
git \
|
||||||
|
clang \
|
||||||
|
make \
|
||||||
|
libsqlite3-dev
|
||||||
|
|
||||||
|
COPY . ./
|
||||||
|
|
||||||
|
RUN make -j8
|
||||||
|
|
||||||
|
# tabledata should be copied from the host;
|
||||||
|
# clone it there before building the container
|
||||||
|
#RUN git submodule update --init --recursive
|
||||||
|
|
||||||
|
CMD ["./bin/fusion"]
|
||||||
|
|
||||||
|
LABEL Name=openfusion Version=0.0.1
|
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020-2021 OpenFusion Contributors
|
Copyright (c) 2020-2023 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
|
||||||
|
19
Makefile
19
Makefile
@@ -4,9 +4,9 @@ CC=clang
|
|||||||
CXX=clang++
|
CXX=clang++
|
||||||
# -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!)
|
# -w suppresses all warnings (the part that's commented out helps me find memory leaks, it ruins performance though!)
|
||||||
# If compiling with ASAN, invoke like this: $ LSAN_OPTIONS=suppressions=suppr.txt bin/fusion
|
# If compiling with ASAN, invoke like this: $ LSAN_OPTIONS=suppressions=suppr.txt bin/fusion
|
||||||
CFLAGS=-O3 #-g3 -fsanitize=address
|
CFLAGS=-Wall -Wno-unknown-pragmas -O2 -fPIE -D_FORTIFY_SOURCE=1 -fstack-protector #-g3 -fsanitize=address
|
||||||
CXXFLAGS=-Wall -Wno-unknown-pragmas -std=c++17 -O2 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor #-g3 -fsanitize=address
|
CXXFLAGS=$(CFLAGS) -std=c++17 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor
|
||||||
LDFLAGS=-lpthread -lsqlite3 #-g3 -fsanitize=address
|
LDFLAGS=-lpthread -lsqlite3 -pie -Wl,-z,relro -Wl,-z,now #-g3 -fsanitize=address
|
||||||
# specifies the name of our exectuable
|
# specifies the name of our exectuable
|
||||||
SERVER=bin/fusion
|
SERVER=bin/fusion
|
||||||
|
|
||||||
@@ -17,9 +17,9 @@ PROTOCOL_VERSION?=104
|
|||||||
# Windows-specific
|
# Windows-specific
|
||||||
WIN_CC=x86_64-w64-mingw32-gcc
|
WIN_CC=x86_64-w64-mingw32-gcc
|
||||||
WIN_CXX=x86_64-w64-mingw32-g++
|
WIN_CXX=x86_64-w64-mingw32-g++
|
||||||
WIN_CFLAGS=-O3 #-g3 -fsanitize=address
|
WIN_CFLAGS=-O2 -D_WIN32_WINNT=0x0601 -Wall -Wno-unknown-pragmas
|
||||||
WIN_CXXFLAGS=-D_WIN32_WINNT=0x0601 -Wall -Wno-unknown-pragmas -std=c++17 -O3 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor #-g3 -fsanitize=address
|
WIN_CXXFLAGS=$(WIN_CFLAGS) -std=c++17 -DPROTOCOL_VERSION=$(PROTOCOL_VERSION) -DGIT_VERSION=\"$(GIT_VERSION)\" -I./src -I./vendor
|
||||||
WIN_LDFLAGS=-static -lws2_32 -lwsock32 -lsqlite3 #-g3 -fsanitize=address
|
WIN_LDFLAGS=-static -lws2_32 -lwsock32 -lsqlite3
|
||||||
WIN_SERVER=bin/winfusion.exe
|
WIN_SERVER=bin/winfusion.exe
|
||||||
|
|
||||||
# C code; currently exclusively from vendored libraries
|
# C code; currently exclusively from vendored libraries
|
||||||
@@ -48,6 +48,8 @@ CXXSRC=\
|
|||||||
src/db/shard.cpp\
|
src/db/shard.cpp\
|
||||||
src/db/player.cpp\
|
src/db/player.cpp\
|
||||||
src/db/email.cpp\
|
src/db/email.cpp\
|
||||||
|
src/sandbox/seccomp.cpp\
|
||||||
|
src/sandbox/openbsd.cpp\
|
||||||
src/Chat.cpp\
|
src/Chat.cpp\
|
||||||
src/CustomCommands.cpp\
|
src/CustomCommands.cpp\
|
||||||
src/Entities.cpp\
|
src/Entities.cpp\
|
||||||
@@ -88,6 +90,7 @@ CXXHDR=\
|
|||||||
src/servers/Monitor.hpp\
|
src/servers/Monitor.hpp\
|
||||||
src/db/Database.hpp\
|
src/db/Database.hpp\
|
||||||
src/db/internal.hpp\
|
src/db/internal.hpp\
|
||||||
|
src/sandbox/Sandbox.hpp\
|
||||||
vendor/bcrypt/BCrypt.hpp\
|
vendor/bcrypt/BCrypt.hpp\
|
||||||
vendor/INIReader.hpp\
|
vendor/INIReader.hpp\
|
||||||
vendor/JSON.hpp\
|
vendor/JSON.hpp\
|
||||||
@@ -139,7 +142,7 @@ windows : CXXFLAGS=$(WIN_CXXFLAGS)
|
|||||||
windows : LDFLAGS=$(WIN_LDFLAGS)
|
windows : LDFLAGS=$(WIN_LDFLAGS)
|
||||||
windows : SERVER=$(WIN_SERVER)
|
windows : SERVER=$(WIN_SERVER)
|
||||||
|
|
||||||
.SUFFIX: .o .c .cpp .h .hpp
|
.SUFFIXES: .o .c .cpp .h .hpp
|
||||||
|
|
||||||
.c.o:
|
.c.o:
|
||||||
$(CC) -c $(CFLAGS) -o $@ $<
|
$(CC) -c $(CFLAGS) -o $@ $<
|
||||||
@@ -148,7 +151,7 @@ windows : SERVER=$(WIN_SERVER)
|
|||||||
$(CXX) -c $(CXXFLAGS) -o $@ $<
|
$(CXX) -c $(CXXFLAGS) -o $@ $<
|
||||||
|
|
||||||
# header timestamps are a prerequisite for OF object files
|
# header timestamps are a prerequisite for OF object files
|
||||||
$(CXXOBJ): $(CXXHDR)
|
$(CXXOBJ): $(HDR)
|
||||||
|
|
||||||
$(SERVER): $(OBJ) $(CHDR) $(CXXHDR)
|
$(SERVER): $(OBJ) $(CHDR) $(CXXHDR)
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
|
25
README.md
25
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<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://ci.appveyor.com/project/OpenFusionProject/openfusion"><img src="https://ci.appveyor.com/api/projects/status/github/OpenFusionProject/OpenFusion?svg=true" alt="AppVeyor"></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://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>
|
||||||
@@ -12,21 +12,32 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Getting Started
|
### Getting Started
|
||||||
|
#### Method A: Installer (Easiest)
|
||||||
|
1. Download the client installer by clicking [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.4/OpenFusionClient-1.4-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.
|
||||||
|
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.
|
||||||
|
|
||||||
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.3/OpenFusionClient-1.3.zip).
|
#### Method B: Standalone .zip file
|
||||||
|
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.4/OpenFusionClient-1.4.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 OpenFusionClient.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).
|
||||||
|
|
||||||
### Hosting a server
|
### Hosting a server
|
||||||
|
|
||||||
1. Grab `OpenFusionServer-1.3-original.zip` or `OpenFusionServer-1.3-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.3).
|
1. Grab `OpenFusionServer-1.4-original.zip` or `OpenFusionServer-1.4-academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.4).
|
||||||
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: the default port is 23000, so the full IP with default settings would be 127.0.0.1:23000.
|
3. Add a new server to the client's list:
|
||||||
4. Once you've added the server to the list, connect to it and log in. If you're having trouble with this, refer to steps 4 and 5 from the previous section.
|
1. For Description, enter anything you want. This is what will show up in the server list.
|
||||||
|
2. For Server IP, enter the IP address and port of the login server. If you're hosting and playing on the same PC, this would be `127.0.0.1:23000`.
|
||||||
|
3. Lastly Game Version - select `beta-20100104` if you downloaded the original zip, or `beta-20111013` if you downloaded the academy zip.
|
||||||
|
5. Once you've added the server to the list, connect to it and log in. If you're having trouble with this, refer to steps 4 and 5 from the previous section.
|
||||||
|
|
||||||
If you want, [compiled binaries (artifacts) for each new commit can be found on AppVeyor.](https://ci.appveyor.com/project/OpenFusionProject/openfusion)
|
If you want to run the latest development builds of the server, [compiled binaries (artifacts) for each functional commit can be found here.](http://cdn.dexlabs.systems/of-builds/)
|
||||||
|
|
||||||
For a more detailed overview of the game's architecture and how to configure it, read the following sections.
|
For a more detailed overview of the game's architecture and how to configure it, read the following sections.
|
||||||
|
|
||||||
@@ -72,7 +83,7 @@ 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. 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 on the wiki](https://github.com/OpenFusionProject/OpenFusion/wiki/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.
|
||||||
|
|
||||||
|
90
appveyor.yml
90
appveyor.yml
@@ -1,90 +0,0 @@
|
|||||||
version: 'openfusion-{branch}-{build}'
|
|
||||||
|
|
||||||
build_cloud: GCE us-east1-b n2-standard-8
|
|
||||||
skip_branch_with_pr: true
|
|
||||||
|
|
||||||
image:
|
|
||||||
- GCP-Windows-VS2019
|
|
||||||
- GCP-Linux-Ubuntu2004
|
|
||||||
|
|
||||||
platform:
|
|
||||||
- x64
|
|
||||||
|
|
||||||
configuration:
|
|
||||||
- Release
|
|
||||||
|
|
||||||
for:
|
|
||||||
-
|
|
||||||
matrix:
|
|
||||||
only:
|
|
||||||
- image: GCP-Linux-Ubuntu2004
|
|
||||||
build_script:
|
|
||||||
- ps: |
|
|
||||||
$versions = "104", "728", "1013"
|
|
||||||
|
|
||||||
foreach ($version in $versions) {
|
|
||||||
Write-Output "Cleaning old output"
|
|
||||||
Invoke-Expression "make clean"
|
|
||||||
if ($LASTEXITCODE -ne "0") {
|
|
||||||
Write-Error "make clean failed for version $version" -ErrorAction Stop
|
|
||||||
}
|
|
||||||
Write-Output "Building version $version"
|
|
||||||
Invoke-Expression "make -j8 PROTOCOL_VERSION=$version"
|
|
||||||
if ($LASTEXITCODE -ne "0") {
|
|
||||||
Write-Error "make failed for version $version" -ErrorAction Stop
|
|
||||||
}
|
|
||||||
Rename-Item -Path "bin/fusion" -newName "$version-fusion"
|
|
||||||
Write-Output "Built version $version"
|
|
||||||
}
|
|
||||||
Copy-Item -Path "sql" -Destination "bin/sql" -Recurse
|
|
||||||
Copy-Item -Path "config.ini" -Destination "bin"
|
|
||||||
artifacts:
|
|
||||||
- path: bin
|
|
||||||
name: ubuntu20_04-bin-x64
|
|
||||||
type: zip
|
|
||||||
-
|
|
||||||
matrix:
|
|
||||||
only:
|
|
||||||
- image: GCP-Windows-VS2019
|
|
||||||
install:
|
|
||||||
- cmd: vcpkg install sqlite3:x64-windows
|
|
||||||
- cmd: vcpkg integrate install
|
|
||||||
build_script:
|
|
||||||
- ps: |
|
|
||||||
$versions = "104", "728", "1013"
|
|
||||||
$configurations = "Release"
|
|
||||||
# "Debug" builds are disabled, since we don't really need them
|
|
||||||
|
|
||||||
# AppVeyor uses VS2019 Community
|
|
||||||
$vsPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community"
|
|
||||||
|
|
||||||
Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
|
|
||||||
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation
|
|
||||||
|
|
||||||
foreach ($version in $versions) {
|
|
||||||
if (Test-Path -LiteralPath "build") {
|
|
||||||
Remove-Item "build" -Recurse
|
|
||||||
Write-Output "Deleted existing build folder"
|
|
||||||
}
|
|
||||||
Invoke-Expression "cmake -B build -DPROTOCOL_VERSION=$version"
|
|
||||||
if ($LASTEXITCODE -ne "0") {
|
|
||||||
Write-Error "cmake generation failed for version $version" -ErrorAction Stop
|
|
||||||
}
|
|
||||||
Write-Output "Generated build files for version $version"
|
|
||||||
|
|
||||||
foreach ($configuration in $configurations) {
|
|
||||||
Write-Output "Building version $version $configuration"
|
|
||||||
Invoke-Expression "msbuild build\OpenFusion.sln /maxcpucount:8 /p:BuildInParallel=true /p:CL_MPCount=8 /p:UseMultiToolTask=true /p:Configuration=$configuration"
|
|
||||||
if ($LASTEXITCODE -ne "0") {
|
|
||||||
Write-Error "msbuild build failed for version $version" -ErrorAction Stop
|
|
||||||
}
|
|
||||||
Rename-Item -Path "bin/$configuration" -newName "$version-$configuration"
|
|
||||||
Write-Output "Built version $version $configuration"
|
|
||||||
Copy-Item -Path "sql" -Destination "bin/$version-$configuration/sql" -Recurse
|
|
||||||
Copy-Item -Path "config.ini" -Destination "bin/$version-$configuration"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
artifacts:
|
|
||||||
- path: bin
|
|
||||||
name: windows-vs2019-bin-x64
|
|
||||||
type: zip
|
|
@@ -5,12 +5,18 @@
|
|||||||
# 3 = print all packets
|
# 3 = print all packets
|
||||||
verbosity=1
|
verbosity=1
|
||||||
|
|
||||||
|
# sandbox the process on supported platforms
|
||||||
|
sandbox=true
|
||||||
|
|
||||||
# Login Server configuration
|
# Login Server configuration
|
||||||
[login]
|
[login]
|
||||||
# must be kept in sync with loginInfo.php
|
# must be kept in sync with loginInfo.php
|
||||||
port=23000
|
port=23000
|
||||||
# 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
|
||||||
|
# automatically create them?
|
||||||
|
autocreateaccounts=true
|
||||||
# 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
|
||||||
|
12
docker-compose.yml
Normal file
12
docker-compose.yml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
version: '3.4'
|
||||||
|
|
||||||
|
services:
|
||||||
|
openfusion:
|
||||||
|
image: openfusion
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./Dockerfile
|
||||||
|
ports:
|
||||||
|
- "23000:23000"
|
||||||
|
- "23001:23001"
|
||||||
|
- "8003:8003"
|
@@ -70,30 +70,36 @@ static void setValuePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
// Handle serverside value-changes
|
// Handle serverside value-changes
|
||||||
switch (setData->iSetValueType) {
|
switch (setData->iSetValueType) {
|
||||||
case 1:
|
case 1:
|
||||||
plr->HP = setData->iSetValue;
|
response.iSetValue = plr->HP = setData->iSetValue;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
plr->batteryW = setData->iSetValue;
|
plr->batteryW = setData->iSetValue;
|
||||||
|
|
||||||
// caps
|
// caps
|
||||||
if (plr->batteryW > 9999)
|
if (plr->batteryW > 9999)
|
||||||
plr->batteryW = 9999;
|
plr->batteryW = 9999;
|
||||||
|
|
||||||
|
response.iSetValue = plr->batteryW;
|
||||||
break;
|
break;
|
||||||
case 3:
|
case 3:
|
||||||
plr->batteryN = setData->iSetValue;
|
plr->batteryN = setData->iSetValue;
|
||||||
|
|
||||||
// caps
|
// caps
|
||||||
if (plr->batteryN > 9999)
|
if (plr->batteryN > 9999)
|
||||||
plr->batteryN = 9999;
|
plr->batteryN = 9999;
|
||||||
|
|
||||||
|
response.iSetValue = plr->batteryN;
|
||||||
break;
|
break;
|
||||||
case 4:
|
case 4:
|
||||||
Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
|
Missions::updateFusionMatter(sock, setData->iSetValue - plr->fusionmatter);
|
||||||
|
response.iSetValue = plr->fusionmatter;
|
||||||
break;
|
break;
|
||||||
case 5:
|
case 5:
|
||||||
plr->money = setData->iSetValue;
|
response.iSetValue = plr->money = setData->iSetValue;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
response.iPC_ID = setData->iPC_ID;
|
response.iPC_ID = setData->iPC_ID;
|
||||||
response.iSetValue = setData->iSetValue;
|
|
||||||
response.iSetValueType = setData->iSetValueType;
|
response.iSetValueType = setData->iSetValueType;
|
||||||
|
|
||||||
sock->sendPacket(response, P_FE2CL_GM_REP_PC_SET_VALUE);
|
sock->sendPacket(response, P_FE2CL_GM_REP_PC_SET_VALUE);
|
||||||
|
@@ -7,11 +7,16 @@
|
|||||||
|
|
||||||
using namespace Chunking;
|
using namespace Chunking;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The initial chunkPos value before a player is placed into the world.
|
||||||
|
*/
|
||||||
|
const ChunkPos Chunking::INVALID_CHUNK = {};
|
||||||
|
|
||||||
std::map<ChunkPos, Chunk*> Chunking::chunks;
|
std::map<ChunkPos, Chunk*> Chunking::chunks;
|
||||||
|
|
||||||
static void newChunk(ChunkPos pos) {
|
static void newChunk(ChunkPos pos) {
|
||||||
if (chunkExists(pos)) {
|
if (chunkExists(pos)) {
|
||||||
std::cout << "[WARN] Tried to create a chunk that already exists\n";
|
std::cout << "[WARN] Tried to create a chunk that already exists" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +32,7 @@ static void newChunk(ChunkPos pos) {
|
|||||||
|
|
||||||
static void deleteChunk(ChunkPos pos) {
|
static void deleteChunk(ChunkPos pos) {
|
||||||
if (!chunkExists(pos)) {
|
if (!chunkExists(pos)) {
|
||||||
std::cout << "[WARN] Tried to delete a chunk that doesn't exist\n";
|
std::cout << "[WARN] Tried to delete a chunk that doesn't exist" << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -200,7 +205,7 @@ bool Chunking::chunkExists(ChunkPos chunk) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ChunkPos Chunking::chunkPosAt(int posX, int posY, uint64_t instanceID) {
|
ChunkPos Chunking::chunkPosAt(int posX, int posY, uint64_t instanceID) {
|
||||||
return std::make_tuple(posX / (settings::VIEWDISTANCE / 3), posY / (settings::VIEWDISTANCE / 3), instanceID);
|
return ChunkPos(posX / (settings::VIEWDISTANCE / 3), posY / (settings::VIEWDISTANCE / 3), instanceID);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::set<Chunk*> Chunking::getViewableChunks(ChunkPos chunk) {
|
std::set<Chunk*> Chunking::getViewableChunks(ChunkPos chunk) {
|
||||||
@@ -213,7 +218,7 @@ std::set<Chunk*> Chunking::getViewableChunks(ChunkPos chunk) {
|
|||||||
// grabs surrounding chunks if they exist
|
// grabs surrounding chunks if they exist
|
||||||
for (int i = -1; i < 2; i++) {
|
for (int i = -1; i < 2; i++) {
|
||||||
for (int z = -1; z < 2; z++) {
|
for (int z = -1; z < 2; z++) {
|
||||||
ChunkPos pos = std::make_tuple(x+i, y+z, inst);
|
ChunkPos pos = ChunkPos(x+i, y+z, inst);
|
||||||
|
|
||||||
// if chunk exists, add it to the set
|
// if chunk exists, add it to the set
|
||||||
if (chunkExists(pos))
|
if (chunkExists(pos))
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "Entities.hpp"
|
|
||||||
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <set>
|
#include <set>
|
||||||
@@ -9,14 +8,23 @@
|
|||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
struct EntityRef;
|
||||||
|
|
||||||
class Chunk {
|
class Chunk {
|
||||||
public:
|
public:
|
||||||
//std::set<CNSocket*> players;
|
|
||||||
//std::set<int32_t> NPCs;
|
|
||||||
std::set<EntityRef> entities;
|
std::set<EntityRef> entities;
|
||||||
int nplayers = 0;
|
int nplayers = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// to help the readability of ChunkPos
|
||||||
|
typedef std::tuple<int, int, uint64_t> _ChunkPos;
|
||||||
|
|
||||||
|
class ChunkPos : public _ChunkPos {
|
||||||
|
public:
|
||||||
|
ChunkPos() : _ChunkPos(0, 0, (uint64_t) -1) {}
|
||||||
|
ChunkPos(int x, int y, uint64_t inst) : _ChunkPos(x, y, inst) {}
|
||||||
|
};
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
INSTANCE_OVERWORLD, // default instance every player starts in
|
INSTANCE_OVERWORLD, // default instance every player starts in
|
||||||
INSTANCE_IZ, // these aren't actually used
|
INSTANCE_IZ, // these aren't actually used
|
||||||
@@ -26,6 +34,8 @@ enum {
|
|||||||
namespace Chunking {
|
namespace Chunking {
|
||||||
extern std::map<ChunkPos, Chunk*> chunks;
|
extern std::map<ChunkPos, Chunk*> chunks;
|
||||||
|
|
||||||
|
extern const ChunkPos INVALID_CHUNK;
|
||||||
|
|
||||||
void updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to);
|
void updateEntityChunk(const EntityRef& ref, ChunkPos from, ChunkPos to);
|
||||||
|
|
||||||
void trackEntity(ChunkPos chunkPos, const EntityRef& ref);
|
void trackEntity(ChunkPos chunkPos, const EntityRef& ref);
|
||||||
|
@@ -56,14 +56,10 @@ static std::pair<int,int> getDamage(int attackPower, int defensePower, bool shou
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
static bool checkRapidFire(CNSocket *sock, int targetCount) {
|
||||||
auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
|
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
auto targets = (int32_t*)data->trailers;
|
|
||||||
|
|
||||||
// rapid fire anti-cheat
|
|
||||||
// TODO: move this out of here, when generalizing packet frequency validation
|
|
||||||
time_t currTime = getTime();
|
time_t currTime = getTime();
|
||||||
|
|
||||||
if (currTime - plr->lastShot < plr->fireRate * 80)
|
if (currTime - plr->lastShot < plr->fireRate * 80)
|
||||||
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
|
plr->suspicionRating += plr->fireRate * 100 + plr->lastShot - currTime; // gain suspicion for rapid firing
|
||||||
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
|
else if (currTime - plr->lastShot < plr->fireRate * 180 && plr->suspicionRating > 0)
|
||||||
@@ -71,15 +67,28 @@ static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
|||||||
|
|
||||||
plr->lastShot = currTime;
|
plr->lastShot = currTime;
|
||||||
|
|
||||||
if (pkt->iNPCCnt > 3) // 3+ targets should never be possible
|
// 3+ targets should never be possible
|
||||||
plr->suspicionRating += 10000;
|
if (targetCount > 3)
|
||||||
|
plr->suspicionRating += 10001;
|
||||||
|
|
||||||
if (plr->suspicionRating > 10000) { // kill the socket when the player is too suspicious
|
// kill the socket when the player is too suspicious
|
||||||
|
if (plr->suspicionRating > 10000) {
|
||||||
sock->kill();
|
sock->kill();
|
||||||
CNShardServer::_killConnection(sock);
|
CNShardServer::_killConnection(sock);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pcAttackNpcs(CNSocket *sock, CNPacketData *data) {
|
||||||
|
auto pkt = (sP_CL2FE_REQ_PC_ATTACK_NPCs*)data->buf;
|
||||||
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
auto targets = (int32_t*)data->trailers;
|
||||||
|
|
||||||
|
// kick the player if firing too rapidly
|
||||||
|
if (settings::ANTICHEAT && checkRapidFire(sock, pkt->iNPCCnt))
|
||||||
|
return;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* IMPORTANT: This validates memory safety in addition to preventing
|
* IMPORTANT: This validates memory safety in addition to preventing
|
||||||
@@ -217,6 +226,30 @@ int Combat::hitMob(CNSocket *sock, Mob *mob, int damage) {
|
|||||||
return damage;
|
return damage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* When a group of players is doing missions together, we want them to all get
|
||||||
|
* quest items at the same time, but we don't want the odds of quest item
|
||||||
|
* drops from different missions to be linked together. That's why we use a
|
||||||
|
* single RNG roll per mission task, and every group member shares that same
|
||||||
|
* set of rolls.
|
||||||
|
*/
|
||||||
|
static void genQItemRolls(Player *leader, std::map<int, int>& rolls) {
|
||||||
|
for (int i = 0; i < leader->groupCnt; i++) {
|
||||||
|
if (leader->groupIDs[i] == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
CNSocket *otherSock = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
||||||
|
if (otherSock == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Player *member = PlayerManager::getPlayer(otherSock);
|
||||||
|
|
||||||
|
for (int j = 0; j < ACTIVE_MISSION_COUNT; j++)
|
||||||
|
if (member->tasks[j] != 0)
|
||||||
|
rolls[member->tasks[j]] = Rand::rand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Combat::killMob(CNSocket *sock, Mob *mob) {
|
void Combat::killMob(CNSocket *sock, Mob *mob) {
|
||||||
mob->state = MobState::DEAD;
|
mob->state = MobState::DEAD;
|
||||||
mob->target = nullptr;
|
mob->target = nullptr;
|
||||||
@@ -231,19 +264,19 @@ void Combat::killMob(CNSocket *sock, Mob *mob) {
|
|||||||
|
|
||||||
Items::DropRoll rolled;
|
Items::DropRoll rolled;
|
||||||
Items::DropRoll eventRolled;
|
Items::DropRoll eventRolled;
|
||||||
int rolledQItem = Rand::rand();
|
std::map<int, int> qitemRolls;
|
||||||
|
|
||||||
|
Player *leader = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
||||||
|
assert(leader != nullptr); // should never happen
|
||||||
|
|
||||||
|
genQItemRolls(leader, qitemRolls);
|
||||||
|
|
||||||
if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) {
|
if (plr->groupCnt == 1 && plr->iIDGroup == plr->iID) {
|
||||||
Items::giveMobDrop(sock, mob, rolled, eventRolled);
|
Items::giveMobDrop(sock, mob, rolled, eventRolled);
|
||||||
Missions::mobKilled(sock, mob->appearanceData.iNPCType, rolledQItem);
|
Missions::mobKilled(sock, mob->appearanceData.iNPCType, qitemRolls);
|
||||||
} else {
|
} else {
|
||||||
Player* otherPlayer = PlayerManager::getPlayerFromID(plr->iIDGroup);
|
for (int i = 0; i < leader->groupCnt; i++) {
|
||||||
|
CNSocket* sockTo = PlayerManager::getSockFromID(leader->groupIDs[i]);
|
||||||
if (otherPlayer == nullptr)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (int i = 0; i < otherPlayer->groupCnt; i++) {
|
|
||||||
CNSocket* sockTo = PlayerManager::getSockFromID(otherPlayer->groupIDs[i]);
|
|
||||||
if (sockTo == nullptr)
|
if (sockTo == nullptr)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -255,7 +288,7 @@ void Combat::killMob(CNSocket *sock, Mob *mob) {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
Items::giveMobDrop(sockTo, mob, rolled, eventRolled);
|
Items::giveMobDrop(sockTo, mob, rolled, eventRolled);
|
||||||
Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, rolledQItem);
|
Missions::mobKilled(sockTo, mob->appearanceData.iNPCType, qitemRolls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,7 +412,7 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
|
|||||||
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
sP_CL2FE_REQ_PC_ATTACK_CHARs* pkt = (sP_CL2FE_REQ_PC_ATTACK_CHARs*)data->buf;
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
// only GMs can use this this variant
|
// only GMs can use this variant
|
||||||
if (plr->accountLevel > 30)
|
if (plr->accountLevel > 30)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@@ -358,32 +358,25 @@ static void npcRotateCommand(std::string full, std::vector<std::string>& args, C
|
|||||||
int angle = (plr->angle + 180) % 360;
|
int angle = (plr->angle + 180) % 360;
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
||||||
|
|
||||||
// if it's a gruntwork NPC, rotate in-place
|
bool isGruntworkNpc = true;
|
||||||
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) != TableData::RunningMobs.end()) {
|
|
||||||
NPCManager::updateNPCPosition(npc->appearanceData.iNPC_ID, npc->x, npc->y, npc->z, npc->instanceID, angle);
|
|
||||||
|
|
||||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for gruntwork NPC "
|
// add a rotation entry to the gruntwork file, unless it's already a gruntwork NPC
|
||||||
+ std::to_string(npc->appearanceData.iNPC_ID));
|
if (TableData::RunningMobs.find(npc->appearanceData.iNPC_ID) == TableData::RunningMobs.end()) {
|
||||||
} else {
|
|
||||||
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle;
|
TableData::RunningNPCRotations[npc->appearanceData.iNPC_ID] = angle;
|
||||||
|
isGruntworkNpc = false;
|
||||||
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) + " for NPC "
|
|
||||||
+ std::to_string(npc->appearanceData.iNPC_ID));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update rotation clientside
|
Chat::sendServerMessage(sock, "[NPCR] Successfully set angle to " + std::to_string(angle) +
|
||||||
INITSTRUCT(sP_FE2CL_NPC_ENTER, pkt);
|
" for " + (isGruntworkNpc ? "gruntwork " : "") + "NPC " + std::to_string(npc->appearanceData.iNPC_ID));
|
||||||
pkt.NPCAppearanceData = npc->appearanceData;
|
|
||||||
sock->sendPacket(pkt, P_FE2CL_NPC_ENTER);
|
// update rotation clientside by refreshing the player's chunks (same as the /refresh command)
|
||||||
|
PlayerManager::updatePlayerPositionForWarp(sock, plr->x, plr->y, plr->z, plr->instanceID);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void refreshCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
static void refreshCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
EntityRef ref = {sock};
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
Entity* plr = ref.getEntity();
|
|
||||||
ChunkPos currentChunk = plr->chunkPos;
|
PlayerManager::updatePlayerPositionForWarp(sock, plr->x, plr->y, plr->z, plr->instanceID);
|
||||||
ChunkPos nullChunk = std::make_tuple(0, 0, 0);
|
|
||||||
Chunking::updateEntityChunk(ref, currentChunk, nullChunk);
|
|
||||||
Chunking::updateEntityChunk(ref, nullChunk, currentChunk);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void instanceCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
static void instanceCommand(std::string full, std::vector<std::string>& args, CNSocket* sock) {
|
||||||
|
@@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
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);
|
||||||
@@ -91,7 +93,7 @@ static void emailReceiveItemSingle(CNSocket* sock, CNPacketData* data) {
|
|||||||
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf;
|
auto pkt = (sP_CL2FE_REQ_PC_RECV_EMAIL_ITEM*)data->buf;
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iSlotNum < 1 || pkt->iSlotNum > 4)
|
if (pkt->iSlotNum < 0 || pkt->iSlotNum >= AINVEN_COUNT || pkt->iEmailItemSlot < 1 || pkt->iEmailItemSlot > 4)
|
||||||
return; // sanity check
|
return; // sanity check
|
||||||
|
|
||||||
// get email item from db and delete it
|
// get email item from db and delete it
|
||||||
@@ -226,10 +228,10 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_SUCC, resp);
|
||||||
|
|
||||||
if (pkt->iCash || pkt->aItem[0].ItemInven.iID) {
|
|
||||||
// if there are item or taro attachments
|
|
||||||
Player otherPlr = {};
|
Player otherPlr = {};
|
||||||
Database::getPlayer(&otherPlr, pkt->iTo_PCUID);
|
Database::getPlayer(&otherPlr, pkt->iTo_PCUID);
|
||||||
|
if (pkt->iCash || pkt->aItem[0].ItemInven.iID) {
|
||||||
|
// if there are item or taro attachments
|
||||||
if (otherPlr.iID != 0 && plr->PCStyle2.iPayzoneFlag != otherPlr.PCStyle2.iPayzoneFlag) {
|
if (otherPlr.iID != 0 && plr->PCStyle2.iPayzoneFlag != otherPlr.PCStyle2.iPayzoneFlag) {
|
||||||
// if the players are not in the same time period
|
// if the players are not in the same time period
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_FAIL, resp);
|
INITSTRUCT(sP_FE2CL_REP_PC_SEND_EMAIL_FAIL, resp);
|
||||||
@@ -250,11 +252,26 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
|||||||
if (attachment.ItemInven.iID == 0)
|
if (attachment.ItemInven.iID == 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
sItemBase* item = &pkt->aItem[i].ItemInven;
|
||||||
|
sItemBase* real = &plr->Inven[attachment.iSlotNum];
|
||||||
|
|
||||||
resp.aItem[i] = attachment;
|
resp.aItem[i] = attachment;
|
||||||
attachments.push_back(attachment.ItemInven);
|
attachments.push_back(attachment.ItemInven);
|
||||||
attSlots.push_back(attachment.iSlotNum);
|
attSlots.push_back(attachment.iSlotNum);
|
||||||
// delete item
|
if (real->iOpt <= item->iOpt) // delete item (if they attached the whole stack)
|
||||||
plr->Inven[attachment.iSlotNum] = { 0, 0, 0, 0 };
|
*real = { 0, 0, 0, 0 };
|
||||||
|
else // otherwise, decrement the item
|
||||||
|
real->iOpt -= item->iOpt;
|
||||||
|
|
||||||
|
// HACK: update the slot
|
||||||
|
INITSTRUCT(sP_FE2CL_PC_ITEM_MOVE_SUCC, itemResp);
|
||||||
|
itemResp.iFromSlotNum = attachment.iSlotNum;
|
||||||
|
itemResp.iToSlotNum = attachment.iSlotNum;
|
||||||
|
itemResp.FromSlotItem = *real;
|
||||||
|
itemResp.ToSlotItem = *real;
|
||||||
|
itemResp.eFrom = (int32_t)Items::SlotType::INVENTORY;
|
||||||
|
itemResp.eTo = (int32_t)Items::SlotType::INVENTORY;
|
||||||
|
sock->sendPacket(itemResp, P_FE2CL_PC_ITEM_MOVE_SUCC);
|
||||||
}
|
}
|
||||||
|
|
||||||
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
|
int cost = pkt->iCash + 50 + 20 * attachments.size(); // attached taros + postage
|
||||||
@@ -274,7 +291,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
|||||||
0 // DeleteTime (unimplemented)
|
0 // DeleteTime (unimplemented)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!Database::sendEmail(&email, attachments)) {
|
if (!Database::sendEmail(&email, attachments, plr)) {
|
||||||
plr->money += cost; // give money back
|
plr->money += cost; // give money back
|
||||||
// give items back
|
// give items back
|
||||||
while (!attachments.empty()) {
|
while (!attachments.empty()) {
|
||||||
@@ -304,6 +321,10 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp.iTo_PCUID = pkt->iTo_PCUID;
|
resp.iTo_PCUID = pkt->iTo_PCUID;
|
||||||
|
|
||||||
sock->sendPacket(resp, P_FE2CL_REP_PC_SEND_EMAIL_SUCC);
|
sock->sendPacket(resp, P_FE2CL_REP_PC_SEND_EMAIL_SUCC);
|
||||||
|
|
||||||
|
std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody;
|
||||||
|
std::cout << logEmail << std::endl;
|
||||||
|
dump.push_back(logEmail);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Email::init() {
|
void Email::init() {
|
||||||
|
@@ -1,5 +1,10 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace Email {
|
namespace Email {
|
||||||
|
extern std::vector<std::string> dump;
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
#include "Chunking.hpp"
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <set>
|
#include <set>
|
||||||
@@ -15,8 +16,6 @@ enum class EntityType : uint8_t {
|
|||||||
BUS
|
BUS
|
||||||
};
|
};
|
||||||
|
|
||||||
class Chunk;
|
|
||||||
|
|
||||||
struct Entity {
|
struct Entity {
|
||||||
EntityType type = EntityType::INVALID;
|
EntityType type = EntityType::INVALID;
|
||||||
int x = 0, y = 0, z = 0;
|
int x = 0, y = 0, z = 0;
|
||||||
|
@@ -219,28 +219,18 @@ static bool endTask(CNSocket *sock, int32_t taskNum, int choice=0) {
|
|||||||
// ugly pointer/reference juggling for the sake of operator overloading...
|
// ugly pointer/reference juggling for the sake of operator overloading...
|
||||||
TaskData& task = *Tasks[taskNum];
|
TaskData& task = *Tasks[taskNum];
|
||||||
|
|
||||||
// update player
|
// sanity check
|
||||||
int i;
|
int i;
|
||||||
bool found = false;
|
bool found = false;
|
||||||
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||||
if (plr->tasks[i] == taskNum) {
|
if (plr->tasks[i] == taskNum) {
|
||||||
found = true;
|
found = true;
|
||||||
plr->tasks[i] = 0;
|
break;
|
||||||
for (int j = 0; j < 3; j++) {
|
|
||||||
plr->RemainingNPCCount[i][j] = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!found) {
|
if (!found)
|
||||||
std::cout << "[WARN] Player tried to end task that isn't in journal?" << std::endl;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
if (i == ACTIVE_MISSION_COUNT - 1 && plr->tasks[i] != 0) {
|
|
||||||
std::cout << "[WARN] Player completed non-active mission!?" << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mission rewards
|
// mission rewards
|
||||||
if (Rewards.find(taskNum) != Rewards.end()) {
|
if (Rewards.find(taskNum) != Rewards.end()) {
|
||||||
@@ -249,6 +239,23 @@ static bool endTask(CNSocket *sock, int32_t taskNum, int choice=0) {
|
|||||||
}
|
}
|
||||||
// don't take away quest items if we haven't finished the quest
|
// don't take away quest items if we haven't finished the quest
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update player's active mission data.
|
||||||
|
*
|
||||||
|
* This must be done after all early returns have passed, otherwise we
|
||||||
|
* risk introducing non-atomic changes. For example, failing to finish
|
||||||
|
* a mission due to not having any inventory space could delete the
|
||||||
|
* mission server-side; leading to a desync.
|
||||||
|
*/
|
||||||
|
for (i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||||
|
if (plr->tasks[i] == taskNum) {
|
||||||
|
plr->tasks[i] = 0;
|
||||||
|
for (int j = 0; j < 3; j++) {
|
||||||
|
plr->RemainingNPCCount[i][j] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Give (or take away) quest items
|
* Give (or take away) quest items
|
||||||
*
|
*
|
||||||
@@ -291,7 +298,7 @@ bool Missions::startTask(Player* plr, int TaskID) {
|
|||||||
TaskData& task = *Missions::Tasks[TaskID];
|
TaskData& task = *Missions::Tasks[TaskID];
|
||||||
|
|
||||||
if (task["m_iCTRReqLvMin"] > plr->level) {
|
if (task["m_iCTRReqLvMin"] > plr->level) {
|
||||||
std::cout << "[WARN] Player tried to start a task below their level" << std::endl;
|
std::cout << "[WARN] Player tried to start a task above their level" << std::endl;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -380,11 +387,12 @@ static void taskStart(CNSocket* sock, CNPacketData* data) {
|
|||||||
static void taskEnd(CNSocket* sock, CNPacketData* data) {
|
static void taskEnd(CNSocket* sock, CNPacketData* data) {
|
||||||
sP_CL2FE_REQ_PC_TASK_END* missionData = (sP_CL2FE_REQ_PC_TASK_END*)data->buf;
|
sP_CL2FE_REQ_PC_TASK_END* missionData = (sP_CL2FE_REQ_PC_TASK_END*)data->buf;
|
||||||
|
|
||||||
// failed timed missions give an iNPC_ID of 0
|
|
||||||
if (missionData->iNPC_ID == 0) {
|
|
||||||
TaskData* task = Missions::Tasks[missionData->iTaskNum];
|
TaskData* task = Missions::Tasks[missionData->iTaskNum];
|
||||||
if (task->task["m_iSTGrantTimer"] > 0) { // its a timed mission
|
|
||||||
|
// handle timed mission failure
|
||||||
|
if (task->task["m_iSTGrantTimer"] > 0 && missionData->iNPC_ID == 0) {
|
||||||
Player* plr = PlayerManager::getPlayer(sock);
|
Player* plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Enemy killing missions
|
* Enemy killing missions
|
||||||
* this is gross and should be cleaned up later
|
* this is gross and should be cleaned up later
|
||||||
@@ -406,7 +414,6 @@ static void taskEnd(CNSocket* sock, CNPacketData* data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!mobsAreKilled) {
|
if (!mobsAreKilled) {
|
||||||
|
|
||||||
int failTaskID = task->task["m_iFOutgoingTask"];
|
int failTaskID = task->task["m_iFOutgoingTask"];
|
||||||
if (failTaskID != 0) {
|
if (failTaskID != 0) {
|
||||||
Missions::quitTask(sock, missionData->iTaskNum, false);
|
Missions::quitTask(sock, missionData->iTaskNum, false);
|
||||||
@@ -418,7 +425,6 @@ static void taskEnd(CNSocket* sock, CNPacketData* data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
|
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, response);
|
||||||
|
|
||||||
@@ -543,9 +549,6 @@ void Missions::updateFusionMatter(CNSocket* sock, int fusion) {
|
|||||||
response.iTaskNum = AvatarGrowth[plr->level]["m_iNanoQuestTaskID"];
|
response.iTaskNum = AvatarGrowth[plr->level]["m_iNanoQuestTaskID"];
|
||||||
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
sock->sendPacket((void*)&response, P_FE2CL_REP_PC_TASK_START_SUCC, sizeof(sP_FE2CL_REP_PC_TASK_START_SUCC));
|
||||||
#else
|
#else
|
||||||
if (plr->level >= 36)
|
|
||||||
return;
|
|
||||||
|
|
||||||
plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"];
|
plr->fusionmatter -= (int)Missions::AvatarGrowth[plr->level]["m_iReqBlob_NanoCreate"];
|
||||||
plr->level++;
|
plr->level++;
|
||||||
|
|
||||||
@@ -564,7 +567,7 @@ void Missions::updateFusionMatter(CNSocket* sock, int fusion) {
|
|||||||
PlayerManager::sendToViewable(sock, (void*)&bcast, P_FE2CL_PC_EVENT, sizeof(sP_FE2CL_PC_EVENT));
|
PlayerManager::sendToViewable(sock, (void*)&bcast, P_FE2CL_PC_EVENT, sizeof(sP_FE2CL_PC_EVENT));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Missions::mobKilled(CNSocket *sock, int mobid, int rolledQItem) {
|
void Missions::mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls) {
|
||||||
Player *plr = PlayerManager::getPlayer(sock);
|
Player *plr = PlayerManager::getPlayer(sock);
|
||||||
|
|
||||||
bool missionmob = false;
|
bool missionmob = false;
|
||||||
@@ -587,12 +590,29 @@ void Missions::mobKilled(CNSocket *sock, int mobid, int rolledQItem) {
|
|||||||
plr->RemainingNPCCount[i][j]--;
|
plr->RemainingNPCCount[i][j]--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// drop quest item
|
// drop quest item
|
||||||
if (task["m_iCSUItemNumNeeded"][j] != 0 && !isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j]) ) {
|
if (task["m_iCSUItemNumNeeded"][j] != 0 && !isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j]) ) {
|
||||||
bool drop = rolledQItem % 100 < task["m_iSTItemDropRate"][j];
|
bool drop = rolls[plr->tasks[i]] % 100 < task["m_iSTItemDropRate"][j];
|
||||||
if (drop) {
|
if (drop) {
|
||||||
// XXX: are CSUItemID and CSTItemID the same?
|
|
||||||
dropQuestItem(sock, plr->tasks[i], 1, task["m_iCSUItemID"][j], mobid);
|
dropQuestItem(sock, plr->tasks[i], 1, task["m_iCSUItemID"][j], mobid);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Workaround: The client has a bug where it only sends a TASK_END request
|
||||||
|
* for the first task of multiple that met their quest item requirements
|
||||||
|
* at the same time. We deal with this by sending TASK_END response packets
|
||||||
|
* proactively and then silently ignoring the extra TASK_END requests it
|
||||||
|
* sends afterwards.
|
||||||
|
*/
|
||||||
|
if (isQuestItemFull(sock, task["m_iCSUItemID"][j], task["m_iCSUItemNumNeeded"][j])) {
|
||||||
|
INITSTRUCT(sP_FE2CL_REP_PC_TASK_END_SUCC, end);
|
||||||
|
end.iTaskNum = plr->tasks[i];
|
||||||
|
|
||||||
|
if (!endTask(sock, plr->tasks[i]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
sock->sendPacket(end, P_FE2CL_REP_PC_TASK_END_SUCC);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// fail to drop (itemID == 0)
|
// fail to drop (itemID == 0)
|
||||||
dropQuestItem(sock, plr->tasks[i], 1, 0, mobid);
|
dropQuestItem(sock, plr->tasks[i], 1, 0, mobid);
|
||||||
|
@@ -47,7 +47,7 @@ namespace Missions {
|
|||||||
// checks if player doesn't have n/n quest items
|
// checks if player doesn't have n/n quest items
|
||||||
void updateFusionMatter(CNSocket* sock, int fusion);
|
void updateFusionMatter(CNSocket* sock, int fusion);
|
||||||
|
|
||||||
void mobKilled(CNSocket *sock, int mobid, int rolledQItem);
|
void mobKilled(CNSocket *sock, int mobid, std::map<int, int>& rolls);
|
||||||
|
|
||||||
void quitTask(CNSocket* sock, int32_t taskNum, bool manual);
|
void quitTask(CNSocket* sock, int32_t taskNum, bool manual);
|
||||||
|
|
||||||
|
@@ -246,8 +246,7 @@ static void handleWarp(CNSocket* sock, int32_t warpId) {
|
|||||||
Missions::failInstancedMissions(sock); // fail any instanced missions
|
Missions::failInstancedMissions(sock); // fail any instanced missions
|
||||||
sock->sendPacket(resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC);
|
sock->sendPacket(resp, P_FE2CL_REP_PC_WARP_USE_NPC_SUCC);
|
||||||
|
|
||||||
Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks
|
PlayerManager::updatePlayerPositionForWarp(sock, resp.iX, resp.iY, resp.iZ, INSTANCE_OVERWORLD);
|
||||||
PlayerManager::updatePlayerPosition(sock, resp.iX, resp.iY, resp.iZ, INSTANCE_OVERWORLD, plr->angle);
|
|
||||||
|
|
||||||
// remove the player's ongoing race, if any
|
// remove the player's ongoing race, if any
|
||||||
if (Racing::EPRaces.find(sock) != Racing::EPRaces.end())
|
if (Racing::EPRaces.find(sock) != Racing::EPRaces.end())
|
||||||
|
@@ -226,6 +226,9 @@ static void nanoEquipHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
if (nano->iNanoSlotNum > 2 || nano->iNanoSlotNum < 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (nano->iNanoID < 0 || nano->iNanoID >= NANO_COUNT)
|
||||||
|
return;
|
||||||
|
|
||||||
resp.iNanoID = nano->iNanoID;
|
resp.iNanoID = nano->iNanoID;
|
||||||
resp.iNanoSlotNum = nano->iNanoSlotNum;
|
resp.iNanoSlotNum = nano->iNanoSlotNum;
|
||||||
|
|
||||||
|
@@ -14,9 +14,7 @@
|
|||||||
struct Player : public Entity {
|
struct Player : public Entity {
|
||||||
int accountId = 0;
|
int accountId = 0;
|
||||||
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
|
int accountLevel = 0; // permission level (see CN_ACCOUNT_LEVEL enums)
|
||||||
int64_t SerialKey = 0;
|
|
||||||
int32_t iID = 0;
|
int32_t iID = 0;
|
||||||
uint64_t FEKey = 0;
|
|
||||||
|
|
||||||
int level = 0;
|
int level = 0;
|
||||||
int HP = 0;
|
int HP = 0;
|
||||||
|
@@ -28,17 +28,12 @@ using namespace PlayerManager;
|
|||||||
|
|
||||||
std::map<CNSocket*, Player*> PlayerManager::players;
|
std::map<CNSocket*, Player*> PlayerManager::players;
|
||||||
|
|
||||||
static void addPlayer(CNSocket* key, Player plr) {
|
static void addPlayer(CNSocket* key, Player *plr) {
|
||||||
Player *p = new Player();
|
players[key] = plr;
|
||||||
|
plr->chunkPos = Chunking::INVALID_CHUNK;
|
||||||
|
plr->lastHeartbeat = 0;
|
||||||
|
|
||||||
// copy object into heap memory
|
std::cout << getPlayerName(plr) << " has joined!" << std::endl;
|
||||||
*p = plr;
|
|
||||||
|
|
||||||
players[key] = p;
|
|
||||||
p->chunkPos = std::make_tuple(0, 0, 0); // TODO: maybe replace with specialized "no chunk" value
|
|
||||||
p->lastHeartbeat = 0;
|
|
||||||
|
|
||||||
std::cout << getPlayerName(p) << " has joined!" << std::endl;
|
|
||||||
std::cout << players.size() << " players" << std::endl;
|
std::cout << players.size() << " players" << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +92,19 @@ void PlayerManager::updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, ui
|
|||||||
Chunking::updateEntityChunk({sock}, oldChunk, newChunk);
|
Chunking::updateEntityChunk({sock}, oldChunk, newChunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Low-level helper function for correctly updating chunks when teleporting players.
|
||||||
|
*
|
||||||
|
* Use PlayerManager::sendPlayerTo() to actually teleport players.
|
||||||
|
*/
|
||||||
|
void PlayerManager::updatePlayerPositionForWarp(CNSocket* sock, int X, int Y, int Z, uint64_t inst) {
|
||||||
|
Player *plr = getPlayer(sock);
|
||||||
|
|
||||||
|
// force player to reload chunks
|
||||||
|
Chunking::updateEntityChunk({sock}, plr->chunkPos, Chunking::INVALID_CHUNK);
|
||||||
|
updatePlayerPosition(sock, X, Y, Z, inst, plr->angle);
|
||||||
|
}
|
||||||
|
|
||||||
void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I) {
|
void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I) {
|
||||||
Player* plr = getPlayer(sock);
|
Player* plr = getPlayer(sock);
|
||||||
plr->onMonkey = false;
|
plr->onMonkey = false;
|
||||||
@@ -148,8 +156,7 @@ void PlayerManager::sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I
|
|||||||
pkt2.iZ = Z;
|
pkt2.iZ = Z;
|
||||||
sock->sendPacket(pkt2, P_FE2CL_REP_PC_GOTO_SUCC);
|
sock->sendPacket(pkt2, P_FE2CL_REP_PC_GOTO_SUCC);
|
||||||
|
|
||||||
Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks
|
updatePlayerPositionForWarp(sock, X, Y, Z, I);
|
||||||
updatePlayerPosition(sock, X, Y, Z, I, plr->angle);
|
|
||||||
|
|
||||||
// post-warp: check if the source instance has no more players in it and delete it if so
|
// post-warp: check if the source instance has no more players in it and delete it if so
|
||||||
Chunking::destroyInstanceIfEmpty(fromInstance);
|
Chunking::destroyInstanceIfEmpty(fromInstance);
|
||||||
@@ -197,78 +204,88 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
auto enter = (sP_CL2FE_REQ_PC_ENTER*)data->buf;
|
auto enter = (sP_CL2FE_REQ_PC_ENTER*)data->buf;
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_ENTER_SUCC, response);
|
INITSTRUCT(sP_FE2CL_REP_PC_ENTER_SUCC, response);
|
||||||
|
|
||||||
// TODO: check if serialkey exists, if it doesn't send sP_FE2CL_REP_PC_ENTER_FAIL
|
LoginMetadata *lm = CNShared::getLoginMetadata(enter->iEnterSerialKey);
|
||||||
Player plr = CNSharedData::getPlayer(enter->iEnterSerialKey);
|
if (lm == nullptr) {
|
||||||
|
std::cout << "[WARN] Refusing invalid REQ_PC_ENTER" << std::endl;
|
||||||
|
|
||||||
plr.groupCnt = 1;
|
// send failure packet
|
||||||
plr.iIDGroup = plr.groupIDs[0] = plr.iID;
|
INITSTRUCT(sP_FE2CL_REP_PC_ENTER_FAIL, fail);
|
||||||
|
sock->sendPacket(fail, P_FE2CL_REP_PC_ENTER_FAIL);
|
||||||
|
|
||||||
DEBUGLOG(
|
// kill the connection
|
||||||
std::cout << "P_CL2FE_REQ_PC_ENTER:" << std::endl;
|
sock->kill();
|
||||||
std::cout << "\tID: " << AUTOU16TOU8(enter->szID) << std::endl;
|
// no need to call _killConnection(); Player isn't in shard yet
|
||||||
std::cout << "\tSerial: " << enter->iEnterSerialKey << std::endl;
|
|
||||||
std::cout << "\tTemp: " << enter->iTempValue << std::endl;
|
|
||||||
std::cout << "\tPC_UID: " << plr.PCStyle.iPC_UID << std::endl;
|
|
||||||
)
|
|
||||||
|
|
||||||
// check if account is already in use
|
return;
|
||||||
if (isAccountInUse(plr.accountId)) {
|
|
||||||
// kick the other player
|
|
||||||
exitDuplicate(plr.accountId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response.iID = plr.iID;
|
Player *plr = new Player();
|
||||||
|
Database::getPlayer(plr, lm->playerId);
|
||||||
|
|
||||||
|
// check if account is already in use
|
||||||
|
if (isAccountInUse(plr->accountId)) {
|
||||||
|
// kick the other player
|
||||||
|
exitDuplicate(plr->accountId);
|
||||||
|
|
||||||
|
// re-read the player from disk, in case it was just flushed
|
||||||
|
*plr = {};
|
||||||
|
Database::getPlayer(plr, lm->playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
plr->groupCnt = 1;
|
||||||
|
plr->iIDGroup = plr->groupIDs[0] = plr->iID;
|
||||||
|
|
||||||
|
response.iID = plr->iID;
|
||||||
response.uiSvrTime = getTime();
|
response.uiSvrTime = getTime();
|
||||||
response.PCLoadData2CL.iUserLevel = plr.accountLevel;
|
response.PCLoadData2CL.iUserLevel = plr->accountLevel;
|
||||||
response.PCLoadData2CL.iHP = plr.HP;
|
response.PCLoadData2CL.iHP = plr->HP;
|
||||||
response.PCLoadData2CL.iLevel = plr.level;
|
response.PCLoadData2CL.iLevel = plr->level;
|
||||||
response.PCLoadData2CL.iCandy = plr.money;
|
response.PCLoadData2CL.iCandy = plr->money;
|
||||||
response.PCLoadData2CL.iFusionMatter = plr.fusionmatter;
|
response.PCLoadData2CL.iFusionMatter = plr->fusionmatter;
|
||||||
response.PCLoadData2CL.iMentor = plr.mentor;
|
response.PCLoadData2CL.iMentor = plr->mentor;
|
||||||
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
|
response.PCLoadData2CL.iMentorCount = 1; // how many guides the player has had
|
||||||
response.PCLoadData2CL.iX = plr.x;
|
response.PCLoadData2CL.iX = plr->x;
|
||||||
response.PCLoadData2CL.iY = plr.y;
|
response.PCLoadData2CL.iY = plr->y;
|
||||||
response.PCLoadData2CL.iZ = plr.z;
|
response.PCLoadData2CL.iZ = plr->z;
|
||||||
response.PCLoadData2CL.iAngle = plr.angle;
|
response.PCLoadData2CL.iAngle = plr->angle;
|
||||||
response.PCLoadData2CL.iBatteryN = plr.batteryN;
|
response.PCLoadData2CL.iBatteryN = plr->batteryN;
|
||||||
response.PCLoadData2CL.iBatteryW = plr.batteryW;
|
response.PCLoadData2CL.iBatteryW = plr->batteryW;
|
||||||
response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login
|
response.PCLoadData2CL.iBuddyWarpTime = 60; // sets 60s warp cooldown on login
|
||||||
|
|
||||||
response.PCLoadData2CL.iWarpLocationFlag = plr.iWarpLocationFlag;
|
response.PCLoadData2CL.iWarpLocationFlag = plr->iWarpLocationFlag;
|
||||||
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr.aSkywayLocationFlag[0];
|
response.PCLoadData2CL.aWyvernLocationFlag[0] = plr->aSkywayLocationFlag[0];
|
||||||
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr.aSkywayLocationFlag[1];
|
response.PCLoadData2CL.aWyvernLocationFlag[1] = plr->aSkywayLocationFlag[1];
|
||||||
|
|
||||||
response.PCLoadData2CL.iActiveNanoSlotNum = -1;
|
response.PCLoadData2CL.iActiveNanoSlotNum = -1;
|
||||||
response.PCLoadData2CL.iFatigue = 50;
|
response.PCLoadData2CL.iFatigue = 50;
|
||||||
response.PCLoadData2CL.PCStyle = plr.PCStyle;
|
response.PCLoadData2CL.PCStyle = plr->PCStyle;
|
||||||
|
|
||||||
// client doesnt read this, it gets it from charinfo
|
// client doesnt read this, it gets it from charinfo
|
||||||
// response.PCLoadData2CL.PCStyle2 = plr.PCStyle2;
|
// response.PCLoadData2CL.PCStyle2 = plr->PCStyle2;
|
||||||
// inventory
|
// inventory
|
||||||
for (int i = 0; i < AEQUIP_COUNT; i++)
|
for (int i = 0; i < AEQUIP_COUNT; i++)
|
||||||
response.PCLoadData2CL.aEquip[i] = plr.Equip[i];
|
response.PCLoadData2CL.aEquip[i] = plr->Equip[i];
|
||||||
for (int i = 0; i < AINVEN_COUNT; i++)
|
for (int i = 0; i < AINVEN_COUNT; i++)
|
||||||
response.PCLoadData2CL.aInven[i] = plr.Inven[i];
|
response.PCLoadData2CL.aInven[i] = plr->Inven[i];
|
||||||
// quest inventory
|
// quest inventory
|
||||||
for (int i = 0; i < AQINVEN_COUNT; i++)
|
for (int i = 0; i < AQINVEN_COUNT; i++)
|
||||||
response.PCLoadData2CL.aQInven[i] = plr.QInven[i];
|
response.PCLoadData2CL.aQInven[i] = plr->QInven[i];
|
||||||
// nanos
|
// nanos
|
||||||
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
|
for (int i = 1; i < SIZEOF_NANO_BANK_SLOT; i++) {
|
||||||
response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i];
|
response.PCLoadData2CL.aNanoBank[i] = plr->Nanos[i];
|
||||||
//response.PCLoadData2CL.aNanoBank[i] = plr.Nanos[i] = {0};
|
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
response.PCLoadData2CL.aNanoSlots[i] = plr.equippedNanos[i];
|
response.PCLoadData2CL.aNanoSlots[i] = plr->equippedNanos[i];
|
||||||
}
|
}
|
||||||
// missions in progress
|
// missions in progress
|
||||||
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
for (int i = 0; i < ACTIVE_MISSION_COUNT; i++) {
|
||||||
if (plr.tasks[i] == 0)
|
if (plr->tasks[i] == 0)
|
||||||
break;
|
break;
|
||||||
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr.tasks[i];
|
response.PCLoadData2CL.aRunningQuest[i].m_aCurrTaskID = plr->tasks[i];
|
||||||
TaskData &task = *Missions::Tasks[plr.tasks[i]];
|
TaskData &task = *Missions::Tasks[plr->tasks[i]];
|
||||||
for (int j = 0; j < 3; j++) {
|
for (int j = 0; j < 3; j++) {
|
||||||
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
|
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCID[j] = (int)task["m_iCSUEnemyID"][j];
|
||||||
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr.RemainingNPCCount[i][j];
|
response.PCLoadData2CL.aRunningQuest[i].m_aKillNPCCount[j] = plr->RemainingNPCCount[i][j];
|
||||||
/*
|
/*
|
||||||
* client doesn't care about NeededItem ID and Count,
|
* client doesn't care about NeededItem ID and Count,
|
||||||
* it gets Count from Quest Inventory
|
* it gets Count from Quest Inventory
|
||||||
@@ -278,12 +295,12 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
response.PCLoadData2CL.iCurrentMissionID = plr.CurrentMissionID;
|
response.PCLoadData2CL.iCurrentMissionID = plr->CurrentMissionID;
|
||||||
|
|
||||||
// completed missions
|
// completed missions
|
||||||
// the packet requires 32 items, but the client only checks the first 16 (shrug)
|
// the packet requires 32 items, but the client only checks the first 16 (shrug)
|
||||||
for (int i = 0; i < 16; i++) {
|
for (int i = 0; i < 16; i++) {
|
||||||
response.PCLoadData2CL.aQuestFlag[i] = plr.aQuestFlag[i];
|
response.PCLoadData2CL.aQuestFlag[i] = plr->aQuestFlag[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Computress tips
|
// Computress tips
|
||||||
@@ -292,15 +309,14 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
|
response.PCLoadData2CL.iFirstUseFlag2 = UINT64_MAX;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
response.PCLoadData2CL.iFirstUseFlag1 = plr.iFirstUseFlag[0];
|
response.PCLoadData2CL.iFirstUseFlag1 = plr->iFirstUseFlag[0];
|
||||||
response.PCLoadData2CL.iFirstUseFlag2 = plr.iFirstUseFlag[1];
|
response.PCLoadData2CL.iFirstUseFlag2 = plr->iFirstUseFlag[1];
|
||||||
}
|
}
|
||||||
|
|
||||||
plr.SerialKey = enter->iEnterSerialKey;
|
plr->instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
|
||||||
plr.instanceID = INSTANCE_OVERWORLD; // the player should never be in an instance on enter
|
|
||||||
|
|
||||||
sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1));
|
sock->setEKey(CNSocketEncryption::createNewKey(response.uiSvrTime, response.iID + 1, response.PCLoadData2CL.iFusionMatter + 1));
|
||||||
sock->setFEKey(plr.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
|
||||||
|
|
||||||
sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC);
|
sock->sendPacket(response, P_FE2CL_REP_PC_ENTER_SUCC);
|
||||||
@@ -308,12 +324,14 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
// transmit MOTD after entering the game, so the client hopefully changes modes on time
|
// transmit MOTD after entering the game, so the client hopefully changes modes on time
|
||||||
Chat::sendServerMessage(sock, settings::MOTDSTRING);
|
Chat::sendServerMessage(sock, settings::MOTDSTRING);
|
||||||
|
|
||||||
|
// transfer ownership of Player object into the shard (still valid in this function though)
|
||||||
addPlayer(sock, plr);
|
addPlayer(sock, plr);
|
||||||
|
|
||||||
// check if there is an expiring vehicle
|
// check if there is an expiring vehicle
|
||||||
Items::checkItemExpire(sock, getPlayer(sock));
|
Items::checkItemExpire(sock, plr);
|
||||||
|
|
||||||
// set player equip stats
|
// set player equip stats
|
||||||
Items::setItemStats(getPlayer(sock));
|
Items::setItemStats(plr);
|
||||||
|
|
||||||
Missions::failInstancedMissions(sock);
|
Missions::failInstancedMissions(sock);
|
||||||
|
|
||||||
@@ -324,7 +342,10 @@ static void enterPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
for (auto& pair : players)
|
for (auto& pair : players)
|
||||||
if (pair.second->notify)
|
if (pair.second->notify)
|
||||||
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(&plr) + " has joined.");
|
Chat::sendServerMessage(pair.first, "[ADMIN]" + getPlayerName(plr) + " has joined.");
|
||||||
|
|
||||||
|
// deallocate lm
|
||||||
|
delete lm;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
void PlayerManager::sendToViewable(CNSocket* sock, void* buf, uint32_t type, size_t size) {
|
||||||
@@ -369,6 +390,9 @@ static void exitGame(CNSocket* sock, CNPacketData* data) {
|
|||||||
response.iExitCode = 1;
|
response.iExitCode = 1;
|
||||||
|
|
||||||
sock->sendPacket(response, P_FE2CL_REP_PC_EXIT_SUCC);
|
sock->sendPacket(response, P_FE2CL_REP_PC_EXIT_SUCC);
|
||||||
|
|
||||||
|
sock->kill();
|
||||||
|
CNShardServer::_killConnection(sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
||||||
@@ -385,14 +409,18 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) {
|
if (reviveData->iRegenType == 3 && plr->iConditionBitFlag & CSB_BIT_PHOENIX) {
|
||||||
// nano revive
|
// nano revive
|
||||||
plr->Nanos[plr->activeNano].iStamina = 0;
|
plr->Nanos[plr->activeNano].iStamina = 0;
|
||||||
plr->HP = PC_MAXHEALTH(plr->level);
|
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||||
Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
Nanos::applyBuff(sock, plr->Nanos[plr->activeNano].iSkillID, 2, 1, 0);
|
||||||
} else if (reviveData->iRegenType == 4) {
|
} else if (reviveData->iRegenType == 4) {
|
||||||
plr->HP = PC_MAXHEALTH(plr->level);
|
// revived by group member's nano
|
||||||
} else {
|
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||||
|
} else if (reviveData->iRegenType == 5) {
|
||||||
|
// warp away
|
||||||
move = true;
|
move = true;
|
||||||
if (reviveData->iRegenType != 5)
|
} else {
|
||||||
plr->HP = PC_MAXHEALTH(plr->level);
|
// plain respawn
|
||||||
|
move = true;
|
||||||
|
plr->HP = PC_MAXHEALTH(plr->level) / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
@@ -459,8 +487,7 @@ static void revivePlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
if (!move)
|
if (!move)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks
|
updatePlayerPositionForWarp(sock, x, y, z, plr->instanceID);
|
||||||
updatePlayerPosition(sock, x, y, z, plr->instanceID, plr->angle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
static void enterPlayerVehicle(CNSocket* sock, CNPacketData* data) {
|
||||||
@@ -687,4 +714,6 @@ void PlayerManager::init() {
|
|||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_OFF, exitPlayerVehicle);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_VEHICLE_OFF, exitPlayerVehicle);
|
||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_CHANGE_MENTOR, changePlayerGuide);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_CHANGE_MENTOR, changePlayerGuide);
|
||||||
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIRST_USE_FLAG_SET, setFirstUseFlag);
|
REGISTER_SHARD_PACKET(P_CL2FE_REQ_PC_FIRST_USE_FLAG_SET, setFirstUseFlag);
|
||||||
|
|
||||||
|
REGISTER_SHARD_TIMER(CNShared::pruneLoginMetadata, CNSHARED_PERIOD);
|
||||||
}
|
}
|
||||||
|
@@ -18,6 +18,7 @@ namespace PlayerManager {
|
|||||||
void removePlayer(CNSocket* key);
|
void removePlayer(CNSocket* key);
|
||||||
|
|
||||||
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, uint64_t I, int angle);
|
void updatePlayerPosition(CNSocket* sock, int X, int Y, int Z, uint64_t I, int angle);
|
||||||
|
void updatePlayerPositionForWarp(CNSocket* sock, int X, int Y, int Z, uint64_t inst);
|
||||||
|
|
||||||
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I);
|
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z, uint64_t I);
|
||||||
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z);
|
void sendPlayerTo(CNSocket* sock, int X, int Y, int Z);
|
||||||
|
@@ -97,31 +97,47 @@ static void racingEnd(CNSocket* sock, CNPacketData* data) {
|
|||||||
if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0)
|
if (EPData.find(mapNum) == EPData.end() || EPData[mapNum].EPID == 0)
|
||||||
return; // IZ not found
|
return; // IZ not found
|
||||||
|
|
||||||
uint64_t now = getTime() / 1000;
|
EPInfo& epInfo = EPData[mapNum];
|
||||||
|
EPRace& epRace = EPRaces[sock];
|
||||||
|
// if there are no divide-by-zero dangers, and at least one factor has been specified
|
||||||
|
// we switch over to OG scoring
|
||||||
|
bool useOGScoring = (epInfo.maxPods > 0) && (epInfo.maxTime > 0) && (
|
||||||
|
(epInfo.scaleFactor > 0.0) || (epInfo.podFactor > 0.0) || (epInfo.timeFactor > 0.0));
|
||||||
|
|
||||||
int timeDiff = now - EPRaces[sock].startTime;
|
uint64_t now = getTime() / 1000;
|
||||||
int score = 500 * EPRaces[sock].collectedRings.size() - 10 * timeDiff;
|
int timeDiff = now - epRace.startTime;
|
||||||
if (score < 0) score = 0; // lol
|
int podsCollected = epRace.collectedRings.size();
|
||||||
int fm = score * plr->level * (1.0f / 36) * 0.3f;
|
int score = 0, fm = 0;
|
||||||
|
|
||||||
|
if (useOGScoring) {
|
||||||
|
score = std::min(epInfo.maxScore, (int)std::exp(
|
||||||
|
(epInfo.podFactor * podsCollected) / epInfo.maxPods
|
||||||
|
- (epInfo.timeFactor * timeDiff) / epInfo.maxTime
|
||||||
|
+ epInfo.scaleFactor));
|
||||||
|
fm = (1.0 + std::exp(epInfo.scaleFactor - 1.0) * epInfo.podFactor * podsCollected) / epInfo.maxPods;
|
||||||
|
} else {
|
||||||
|
score = std::max(0, 500 * podsCollected - 10 * timeDiff);
|
||||||
|
fm = score * plr->level * (1.0f / 36) * 0.3f;
|
||||||
|
}
|
||||||
|
|
||||||
// we submit the ranking first...
|
// we submit the ranking first...
|
||||||
Database::RaceRanking postRanking = {};
|
Database::RaceRanking postRanking = {};
|
||||||
postRanking.EPID = EPData[mapNum].EPID;
|
postRanking.EPID = epInfo.EPID;
|
||||||
postRanking.PlayerID = plr->iID;
|
postRanking.PlayerID = plr->iID;
|
||||||
postRanking.RingCount = EPRaces[sock].collectedRings.size();
|
postRanking.RingCount = podsCollected;
|
||||||
postRanking.Score = score;
|
postRanking.Score = score;
|
||||||
postRanking.Time = timeDiff;
|
postRanking.Time = timeDiff;
|
||||||
postRanking.Timestamp = getTimestamp();
|
postRanking.Timestamp = getTimestamp();
|
||||||
Database::postRaceRanking(postRanking);
|
Database::postRaceRanking(postRanking);
|
||||||
|
|
||||||
// ...then we get the top ranking, which may or may not be what we just submitted
|
// ...then we get the top ranking, which may or may not be what we just submitted
|
||||||
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(EPData[mapNum].EPID, plr->iID);
|
Database::RaceRanking topRankingPlayer = Database::getTopRaceRanking(epInfo.EPID, plr->iID);
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_REP_EP_RACE_END_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_EP_RACE_END_SUCC, resp);
|
||||||
|
|
||||||
// get rank scores and rewards
|
// get rank scores and rewards
|
||||||
std::vector<int>* rankScores = &EPRewards[EPData[mapNum].EPID].first;
|
std::vector<int>* rankScores = &EPRewards[epInfo.EPID].first;
|
||||||
std::vector<int>* rankRewards = &EPRewards[EPData[mapNum].EPID].second;
|
std::vector<int>* rankRewards = &EPRewards[epInfo.EPID].second;
|
||||||
|
|
||||||
// top ranking
|
// top ranking
|
||||||
int topRank = 0;
|
int topRank = 0;
|
||||||
|
@@ -5,7 +5,11 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
|
||||||
struct EPInfo {
|
struct EPInfo {
|
||||||
int zoneX, zoneY, EPID, maxScore, maxTime;
|
// available through XDT (maxScore may be updated by drops)
|
||||||
|
int zoneX, zoneY, EPID, maxScore;
|
||||||
|
// (maybe) available through drops
|
||||||
|
int maxTime = 0, maxPods = 0;
|
||||||
|
double scaleFactor = 0.0, podFactor = 0.0, timeFactor = 0.0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct EPRace {
|
struct EPRace {
|
||||||
|
53
src/Rand.cpp
53
src/Rand.cpp
@@ -1,4 +1,5 @@
|
|||||||
#include "Rand.hpp"
|
#include "Rand.hpp"
|
||||||
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
std::unique_ptr<std::mt19937> Rand::generator;
|
std::unique_ptr<std::mt19937> Rand::generator;
|
||||||
|
|
||||||
@@ -33,6 +34,58 @@ float Rand::randFloat() {
|
|||||||
return Rand::randFloat(0.0f, 1.0f);
|
return Rand::randFloat(0.0f, 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define RANDBYTES 8
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Cryptographically secure RNG. Borrowed from bcrypt_gensalt().
|
||||||
|
*/
|
||||||
|
uint64_t Rand::cryptoRand() {
|
||||||
|
uint8_t buf[RANDBYTES];
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
HCRYPTPROV p;
|
||||||
|
|
||||||
|
// Acquire a crypt context for generating random bytes.
|
||||||
|
if (CryptAcquireContext(&p, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT) == FALSE) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CryptGenRandom(p, RANDBYTES, (BYTE*)buf) == FALSE) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CryptReleaseContext(p, 0) == FALSE) {
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int fd;
|
||||||
|
|
||||||
|
// Get random bytes on Unix/Linux.
|
||||||
|
fd = open("/dev/urandom", O_RDONLY);
|
||||||
|
if (fd < 0) {
|
||||||
|
perror("open");
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (read(fd, buf, RANDBYTES) < RANDBYTES) {
|
||||||
|
perror("read");
|
||||||
|
close(fd);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(fd);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return *(uint64_t*)buf;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
std::cout << "[FATAL] Failed to generate cryptographic random number" << std::endl;
|
||||||
|
terminate(0);
|
||||||
|
|
||||||
|
/* not reached */
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void Rand::init(uint64_t seed) {
|
void Rand::init(uint64_t seed) {
|
||||||
Rand::generator = std::make_unique<std::mt19937>(std::mt19937(seed));
|
Rand::generator = std::make_unique<std::mt19937>(std::mt19937(seed));
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,8 @@ namespace Rand {
|
|||||||
|
|
||||||
int32_t randWeighted(const std::vector<int32_t>& weights);
|
int32_t randWeighted(const std::vector<int32_t>& weights);
|
||||||
|
|
||||||
|
uint64_t cryptoRand();
|
||||||
|
|
||||||
float randFloat(float startInclusive, float endExclusive);
|
float randFloat(float startInclusive, float endExclusive);
|
||||||
float randFloat(float endExclusive);
|
float randFloat(float endExclusive);
|
||||||
float randFloat();
|
float randFloat();
|
||||||
|
@@ -37,6 +37,22 @@ public:
|
|||||||
const char *what() const throw() { return msg.c_str(); }
|
const char *what() const throw() { return msg.c_str(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We must refuse to run if an invalid NPC type is found in the JSONs, especially
|
||||||
|
* the gruntwork file. If we were to just skip loading invalid NPCs, they would get
|
||||||
|
* silently dropped from the gruntwork file, which would be confusing in situations
|
||||||
|
* where a gruntwork file for the wrong game build was accidentally loaded.
|
||||||
|
*/
|
||||||
|
static void ensureValidNPCType(int type, std::string filename) {
|
||||||
|
// last known NPC type
|
||||||
|
int npcLimit = NPCManager::NPCData.back()["m_iNpcNumber"];
|
||||||
|
|
||||||
|
if (type > npcLimit) {
|
||||||
|
std::cout << "[FATAL] " << filename << " contains an invalid NPC type: " << type << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a full and properly-paced path by interpolating between keyframes.
|
* Create a full and properly-paced path by interpolating between keyframes.
|
||||||
*/
|
*/
|
||||||
@@ -556,8 +572,35 @@ static void loadDrops(json& dropData) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EPInfo& epInfo = Racing::EPData[EPMap];
|
||||||
|
|
||||||
// time limit isn't stored in the XDT, so we include it in the reward table instead
|
// time limit isn't stored in the XDT, so we include it in the reward table instead
|
||||||
Racing::EPData[EPMap].maxTime = race["TimeLimit"];
|
epInfo.maxTime = (int)race["TimeLimit"];
|
||||||
|
|
||||||
|
// update max score (if present)
|
||||||
|
if (race.find("ScoreCap") != race.end()) {
|
||||||
|
epInfo.maxScore = (int)race["ScoreCap"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// update max pods (if present)
|
||||||
|
if (race.find("TotalPods") != race.end()) {
|
||||||
|
epInfo.maxPods = (int)race["TotalPods"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// update scale factor (if present)
|
||||||
|
if (race.find("ScaleFactor") != race.end()) {
|
||||||
|
epInfo.scaleFactor = (double)race["ScaleFactor"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// update pod factor (if present)
|
||||||
|
if (race.find("PodFactor") != race.end()) {
|
||||||
|
epInfo.podFactor = (double)race["PodFactor"];
|
||||||
|
}
|
||||||
|
|
||||||
|
// update time factor (if present)
|
||||||
|
if (race.find("TimeFactor") != race.end()) {
|
||||||
|
epInfo.timeFactor = (double)race["TimeFactor"];
|
||||||
|
}
|
||||||
|
|
||||||
// score cutoffs
|
// score cutoffs
|
||||||
std::vector<int> rankScores;
|
std::vector<int> rankScores;
|
||||||
@@ -662,6 +705,8 @@ static void loadEggs(json& eggData, int32_t* nextId) {
|
|||||||
* Load gruntwork output, if it exists
|
* Load gruntwork output, if it exists
|
||||||
*/
|
*/
|
||||||
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
||||||
|
if (gruntwork.is_null())
|
||||||
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto paths = gruntwork["paths"];
|
auto paths = gruntwork["paths"];
|
||||||
@@ -671,10 +716,10 @@ static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
|||||||
std::vector<int32_t> targetIDs;
|
std::vector<int32_t> targetIDs;
|
||||||
std::vector<int32_t> targetTypes; // target types are not exportable from gw, but load them anyway
|
std::vector<int32_t> targetTypes; // target types are not exportable from gw, but load them anyway
|
||||||
std::vector<Vec3> pathPoints;
|
std::vector<Vec3> pathPoints;
|
||||||
int speed = (int)path["iBaseSpeed"];
|
int speed = path.find("iBaseSpeed") == path.end() ? NPC_DEFAULT_SPEED : (int)path["iBaseSpeed"];
|
||||||
int taskID = (int)path["iTaskID"];
|
int taskID = path.find("iTaskID") == path.end() ? -1 : (int)path["iTaskID"];
|
||||||
bool relative = (bool)path["bRelative"];
|
bool relative = path.find("bRelative") == path.end() ? false : (bool)path["bRelative"];
|
||||||
bool loop = (bool)path["bLoop"];
|
bool loop = path.find("bLoop") == path.end() ? true : (bool)path["bLoop"]; // loop by default
|
||||||
|
|
||||||
// target IDs
|
// target IDs
|
||||||
for (json::iterator _tID = path["aNPCIDs"].begin(); _tID != path["aNPCIDs"].end(); _tID++)
|
for (json::iterator _tID = path["aNPCIDs"].begin(); _tID != path["aNPCIDs"].end(); _tID++)
|
||||||
@@ -711,8 +756,8 @@ static void loadGruntworkPre(json& gruntwork, int32_t* nextId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
||||||
|
if (gruntwork.is_null())
|
||||||
if (gruntwork.is_null()) return;
|
return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// skyway paths
|
// skyway paths
|
||||||
@@ -764,6 +809,8 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
int id = (*nextId)--;
|
int id = (*nextId)--;
|
||||||
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"];
|
uint64_t instanceID = mob.find("iMapNum") == mob.end() ? INSTANCE_OVERWORLD : (int)mob["iMapNum"];
|
||||||
|
|
||||||
|
ensureValidNPCType((int)mob["iNPCType"], settings::GRUNTWORKJSON);
|
||||||
|
|
||||||
if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
|
if (NPCManager::NPCData[(int)mob["iNPCType"]]["m_iTeam"] == 2) {
|
||||||
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
|
npc = new Mob(mob["iX"], mob["iY"], mob["iZ"], instanceID, mob["iNPCType"],
|
||||||
NPCManager::NPCData[(int)mob["iNPCType"]], id);
|
NPCManager::NPCData[(int)mob["iNPCType"]], id);
|
||||||
@@ -783,6 +830,9 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
auto groups = gruntwork["groups"];
|
auto groups = gruntwork["groups"];
|
||||||
for (auto _group = groups.begin(); _group != groups.end(); _group++) {
|
for (auto _group = groups.begin(); _group != groups.end(); _group++) {
|
||||||
auto leader = _group.value();
|
auto leader = _group.value();
|
||||||
|
|
||||||
|
ensureValidNPCType((int)leader["iNPCType"], settings::GRUNTWORKJSON);
|
||||||
|
|
||||||
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
||||||
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
||||||
|
|
||||||
@@ -803,6 +853,9 @@ static void loadGruntworkPost(json& gruntwork, int32_t* nextId) {
|
|||||||
int followerCount = 0;
|
int followerCount = 0;
|
||||||
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
||||||
auto follower = _fol.value();
|
auto follower = _fol.value();
|
||||||
|
|
||||||
|
ensureValidNPCType((int)follower["iNPCType"], settings::GRUNTWORKJSON);
|
||||||
|
|
||||||
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
||||||
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
||||||
|
|
||||||
@@ -859,10 +912,9 @@ static void loadNPCs(json& npcData) {
|
|||||||
npcID += NPC_ID_OFFSET;
|
npcID += NPC_ID_OFFSET;
|
||||||
int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
int instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
||||||
int type = (int)npc["iNPCType"];
|
int type = (int)npc["iNPCType"];
|
||||||
if (NPCManager::NPCData[type].is_null()) {
|
|
||||||
std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
|
ensureValidNPCType(type, settings::NPCJSON);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
#ifdef ACADEMY
|
#ifdef ACADEMY
|
||||||
// do not spawn NPCs in the future
|
// do not spawn NPCs in the future
|
||||||
if (npc["iX"] > 512000 && npc["iY"] < 256000)
|
if (npc["iX"] > 512000 && npc["iY"] < 256000)
|
||||||
@@ -904,10 +956,9 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
|||||||
int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer
|
int npcID = std::strtol(_npc.key().c_str(), nullptr, 10); // parse ID string to integer
|
||||||
npcID += MOB_ID_OFFSET;
|
npcID += MOB_ID_OFFSET;
|
||||||
int type = (int)npc["iNPCType"];
|
int type = (int)npc["iNPCType"];
|
||||||
if (NPCManager::NPCData[type].is_null()) {
|
|
||||||
std::cout << "[WARN] NPC type " << type << " not found; skipping (json#" << _npc.key() << ")" << std::endl;
|
ensureValidNPCType(type, settings::MOBJSON);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto td = NPCManager::NPCData[type];
|
auto td = NPCManager::NPCData[type];
|
||||||
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
uint64_t instanceID = npc.find("iMapNum") == npc.end() ? INSTANCE_OVERWORLD : (int)npc["iMapNum"];
|
||||||
|
|
||||||
@@ -935,7 +986,10 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
|||||||
for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) {
|
for (json::iterator _group = groupData.begin(); _group != groupData.end(); _group++) {
|
||||||
auto leader = _group.value();
|
auto leader = _group.value();
|
||||||
int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer
|
int leadID = std::strtol(_group.key().c_str(), nullptr, 10); // parse ID string to integer
|
||||||
|
|
||||||
leadID += MOB_GROUP_ID_OFFSET;
|
leadID += MOB_GROUP_ID_OFFSET;
|
||||||
|
ensureValidNPCType(leader["iNPCType"], settings::MOBJSON);
|
||||||
|
|
||||||
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
auto td = NPCManager::NPCData[(int)leader["iNPCType"]];
|
||||||
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
uint64_t instanceID = leader.find("iMapNum") == leader.end() ? INSTANCE_OVERWORLD : (int)leader["iMapNum"];
|
||||||
auto followers = leader["aFollowers"];
|
auto followers = leader["aFollowers"];
|
||||||
@@ -965,6 +1019,9 @@ static void loadMobs(json& npcData, int32_t* nextId) {
|
|||||||
int followerCount = 0;
|
int followerCount = 0;
|
||||||
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
for (json::iterator _fol = followers.begin(); _fol != followers.end(); _fol++) {
|
||||||
auto follower = _fol.value();
|
auto follower = _fol.value();
|
||||||
|
|
||||||
|
ensureValidNPCType(follower["iNPCType"], settings::MOBJSON);
|
||||||
|
|
||||||
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
auto tdFol = NPCManager::NPCData[(int)follower["iNPCType"]];
|
||||||
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
Mob* tmpFol = new Mob((int)leader["iX"] + (int)follower["iOffsetX"], (int)leader["iY"] + (int)follower["iOffsetY"], leader["iZ"], leader["iAngle"], instanceID, follower["iNPCType"], tdFol, *nextId);
|
||||||
|
|
||||||
@@ -1070,19 +1127,39 @@ void TableData::init() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// load JSON data into tables
|
// load JSON data into tables
|
||||||
std::ifstream fstream;
|
|
||||||
for (int i = 0; i < 7; i++) {
|
for (int i = 0; i < 7; i++) {
|
||||||
std::pair<json*, std::string>& table = tables[i];
|
std::pair<json*, std::string>& table = tables[i];
|
||||||
fstream.open(settings::TDATADIR + table.second); // open file
|
|
||||||
if (!fstream.fail()) {
|
// scope for fstream
|
||||||
fstream >> *table.first; // load file contents into table
|
{
|
||||||
} else {
|
std::ifstream fstream;
|
||||||
if (table.first != &gruntwork) { // gruntwork isn't critical
|
fstream.open(settings::TDATADIR + "/" + table.second); // open file
|
||||||
std::cerr << "[FATAL] Critical tdata file missing: " << settings::TDATADIR << table.second << std::endl;
|
|
||||||
|
// did we fail to open the file?
|
||||||
|
if (fstream.fail()) {
|
||||||
|
// gruntwork isn't critical
|
||||||
|
if (table.first == &gruntwork)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::cerr << "[FATAL] Critical tdata file missing: " << table.second << std::endl;
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is the file empty?
|
||||||
|
if (fstream.peek() == std::ifstream::traits_type::eof()) {
|
||||||
|
// tolerate empty gruntwork file
|
||||||
|
if (table.first == &gruntwork) {
|
||||||
|
std::cout << "[WARN] The gruntwork file is empty" << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cerr << "[FATAL] Critical tdata file is empty: " << table.second << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// load file contents into table
|
||||||
|
fstream >> *table.first;
|
||||||
}
|
}
|
||||||
fstream.close();
|
|
||||||
|
|
||||||
// patching: load each patch directory specified in the config file
|
// patching: load each patch directory specified in the config file
|
||||||
|
|
||||||
@@ -1097,11 +1174,11 @@ void TableData::init() {
|
|||||||
std::string patchModuleName = *it;
|
std::string patchModuleName = *it;
|
||||||
std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second;
|
std::string patchFile = settings::PATCHDIR + patchModuleName + "/" + table.second;
|
||||||
try {
|
try {
|
||||||
|
std::ifstream fstream;
|
||||||
fstream.open(patchFile);
|
fstream.open(patchFile);
|
||||||
fstream >> patch; // load into temporary json object
|
fstream >> patch; // load into temporary json object
|
||||||
std::cout << "[INFO] Patching " << patchFile << std::endl;
|
std::cout << "[INFO] Patching " << patchFile << std::endl;
|
||||||
patchJSON(table.first, &patch); // patch
|
patchJSON(table.first, &patch); // patch
|
||||||
fstream.close();
|
|
||||||
} catch (const std::exception& err) {
|
} catch (const std::exception& err) {
|
||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
@@ -1127,7 +1204,7 @@ void TableData::init() {
|
|||||||
* Write gruntwork output to file
|
* Write gruntwork output to file
|
||||||
*/
|
*/
|
||||||
void TableData::flush() {
|
void TableData::flush() {
|
||||||
std::ofstream file(settings::TDATADIR + settings::GRUNTWORKJSON);
|
std::ofstream file(settings::TDATADIR + "/" + settings::GRUNTWORKJSON);
|
||||||
json gruntwork;
|
json gruntwork;
|
||||||
|
|
||||||
for (auto& pair : RunningSkywayRoutes) {
|
for (auto& pair : RunningSkywayRoutes) {
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
#include "Trading.hpp"
|
#include "Trading.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
|
#include "db/Database.hpp"
|
||||||
|
|
||||||
using namespace Trading;
|
using namespace Trading;
|
||||||
|
|
||||||
@@ -269,6 +270,8 @@ static void tradeConfirm(CNSocket* sock, CNPacketData* data) {
|
|||||||
otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
otherSock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Database::commitTrade(plr, plr2);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) {
|
static void tradeConfirmCancel(CNSocket* sock, CNPacketData* data) {
|
||||||
|
@@ -165,9 +165,9 @@ static void transportWarpHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
if (target == nullptr)
|
if (target == nullptr)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// we warped; update position and chunks
|
// we warped; update position and chunks
|
||||||
Chunking::updateEntityChunk({sock}, plr->chunkPos, std::make_tuple(0, 0, 0)); // force player to reload chunks
|
PlayerManager::updatePlayerPositionForWarp(sock, target->x, target->y, target->z, INSTANCE_OVERWORLD);
|
||||||
PlayerManager::updatePlayerPosition(sock, target->x, target->y, target->z, INSTANCE_OVERWORLD, plr->angle);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Transport::testMssRoute(CNSocket *sock, std::vector<Vec3>* route) {
|
void Transport::testMssRoute(CNSocket *sock, std::vector<Vec3>* route) {
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
#include "Vendors.hpp"
|
#include "Vendors.hpp"
|
||||||
#include "Rand.hpp"
|
#include "Rand.hpp"
|
||||||
|
|
||||||
|
// 7 days
|
||||||
|
#define VEHICLE_EXPIRY_DURATION 604800
|
||||||
|
|
||||||
using namespace Vendors;
|
using namespace Vendors;
|
||||||
|
|
||||||
std::map<int32_t, std::vector<VendorListing>> Vendors::VendorTables;
|
std::map<int32_t, std::vector<VendorListing>> Vendors::VendorTables;
|
||||||
@@ -53,8 +56,8 @@ static void vendorBuy(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
// if vehicle
|
// if vehicle
|
||||||
if (req->Item.iType == 10) {
|
if (req->Item.iType == 10) {
|
||||||
// set time limit: current time + 7days
|
// set time limit: current time + expiry duration
|
||||||
req->Item.iTimeLimit = getTimestamp() + 604800;
|
req->Item.iTimeLimit = getTimestamp() + VEHICLE_EXPIRY_DURATION;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (slot != req->iInvenSlotNum) {
|
if (slot != req->iInvenSlotNum) {
|
||||||
@@ -224,12 +227,20 @@ static void vendorTable(CNSocket* sock, CNPacketData* data) {
|
|||||||
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_PC_VENDOR_TABLE_UPDATE_SUCC, resp);
|
||||||
|
|
||||||
for (int i = 0; i < (int)listings.size() && i < 20; i++) { // 20 is the max
|
for (int i = 0; i < (int)listings.size() && i < 20; i++) { // 20 is the max
|
||||||
sItemBase base;
|
sItemBase base = {};
|
||||||
base.iID = listings[i].id;
|
base.iID = listings[i].id;
|
||||||
base.iOpt = 0;
|
|
||||||
base.iTimeLimit = 0;
|
|
||||||
base.iType = listings[i].type;
|
base.iType = listings[i].type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set vehicle expiry value.
|
||||||
|
*
|
||||||
|
* Note: sItemBase.iTimeLimit in the context of vendor listings contains
|
||||||
|
* a duration, unlike in most other contexts where it contains the
|
||||||
|
* expiration timestamp.
|
||||||
|
*/
|
||||||
|
if (listings[i].type == 10)
|
||||||
|
base.iTimeLimit = VEHICLE_EXPIRY_DURATION;
|
||||||
|
|
||||||
sItemVendor vItem;
|
sItemVendor vItem;
|
||||||
vItem.item = base;
|
vItem.item = base;
|
||||||
vItem.iSortNum = listings[i].sort;
|
vItem.iSortNum = listings[i].sort;
|
||||||
|
@@ -41,7 +41,8 @@ int CNSocketEncryption::xorData(uint8_t* buffer, uint8_t* key, int size) {
|
|||||||
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
|
uint64_t CNSocketEncryption::createNewKey(uint64_t uTime, int32_t iv1, int32_t iv2) {
|
||||||
uint64_t num = (uint64_t)(iv1 + 1);
|
uint64_t num = (uint64_t)(iv1 + 1);
|
||||||
uint64_t num2 = (uint64_t)(iv2 + 1);
|
uint64_t num2 = (uint64_t)(iv2 + 1);
|
||||||
uint64_t dEKey = (uint64_t)(*(uint64_t*)&defaultKey[0]);
|
uint64_t dEKey;
|
||||||
|
memcpy(&dEKey, defaultKey, sizeof(dEKey));
|
||||||
return dEKey * (uTime * num * num2);
|
return dEKey * (uTime * num * num2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ CNPacketData::CNPacketData(void *b, uint32_t t, int l, int trnum, void *trs):
|
|||||||
// ========================================================[[ CNSocket ]]========================================================
|
// ========================================================[[ CNSocket ]]========================================================
|
||||||
|
|
||||||
CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) {
|
CNSocket::CNSocket(SOCKET s, struct sockaddr_in &addr, PacketHandler ph): sock(s), sockaddr(addr), pHandler(ph) {
|
||||||
EKey = (uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]);
|
memcpy(&EKey, CNSocketEncryption::defaultKey, sizeof(EKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CNSocket::sendData(uint8_t* data, int size) {
|
bool CNSocket::sendData(uint8_t* data, int size) {
|
||||||
@@ -109,7 +110,11 @@ bool CNSocket::isAlive() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CNSocket::kill() {
|
void CNSocket::kill() {
|
||||||
|
if (!alive)
|
||||||
|
return;
|
||||||
|
|
||||||
alive = false;
|
alive = false;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
shutdown(sock, SD_BOTH);
|
shutdown(sock, SD_BOTH);
|
||||||
closesocket(sock);
|
closesocket(sock);
|
||||||
@@ -241,9 +246,10 @@ void CNSocket::step() {
|
|||||||
if (readSize <= 0) {
|
if (readSize <= 0) {
|
||||||
// we aren't reading a packet yet, try to start looking for one
|
// we aren't reading a packet yet, try to start looking for one
|
||||||
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
|
int recved = recv(sock, (buffer_t*)readBuffer, sizeof(int32_t), 0);
|
||||||
if (recved == 0) {
|
if (recved >= 0 && recved < sizeof(int32_t)) {
|
||||||
// the socket was closed normally
|
// too little data for readSize or the socket was closed normally (when 0 bytes were read)
|
||||||
kill();
|
kill();
|
||||||
|
return;
|
||||||
} else if (!SOCKETERROR(recved)) {
|
} else if (!SOCKETERROR(recved)) {
|
||||||
// we got our packet size!!!!
|
// we got our packet size!!!!
|
||||||
readSize = *((int32_t*)readBuffer);
|
readSize = *((int32_t*)readBuffer);
|
||||||
@@ -264,11 +270,12 @@ void CNSocket::step() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (readSize > 0 && readBufferIndex < readSize) {
|
if (readSize > 0 && readBufferIndex < readSize) {
|
||||||
// read until the end of the packet! (or at least try too)
|
// read until the end of the packet (or at least try to)
|
||||||
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
|
int recved = recv(sock, (buffer_t*)(readBuffer + readBufferIndex), readSize - readBufferIndex, 0);
|
||||||
if (recved == 0) {
|
if (recved == 0) {
|
||||||
// the socket was closed normally
|
// the socket was closed normally
|
||||||
kill();
|
kill();
|
||||||
|
return;
|
||||||
} else if (!SOCKETERROR(recved))
|
} else if (!SOCKETERROR(recved))
|
||||||
readBufferIndex += recved;
|
readBufferIndex += recved;
|
||||||
else if (OF_ERRNO != OF_EWOULD) {
|
else if (OF_ERRNO != OF_EWOULD) {
|
||||||
@@ -411,9 +418,9 @@ void CNServer::addPollFD(SOCKET s) {
|
|||||||
fds.push_back({s, POLLIN});
|
fds.push_back({s, POLLIN});
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNServer::removePollFD(int i) {
|
void CNServer::removePollFD(int fd) {
|
||||||
auto it = fds.begin();
|
auto it = fds.begin();
|
||||||
while (it != fds.end() && it->fd != fds[i].fd)
|
while (it != fds.end() && it->fd != fd)
|
||||||
it++;
|
it++;
|
||||||
assert(it != fds.end());
|
assert(it != fds.end());
|
||||||
|
|
||||||
@@ -458,7 +465,7 @@ void CNServer::start() {
|
|||||||
if (!setSockNonblocking(sock, newConnectionSocket))
|
if (!setSockNonblocking(sock, newConnectionSocket))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
std::cout << "New connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
std::cout << "New " << serverType << " connection! " << inet_ntoa(address.sin_addr) << std::endl;
|
||||||
|
|
||||||
addPollFD(newConnectionSocket);
|
addPollFD(newConnectionSocket);
|
||||||
|
|
||||||
@@ -473,10 +480,15 @@ void CNServer::start() {
|
|||||||
} else {
|
} else {
|
||||||
std::lock_guard<std::mutex> lock(activeCrit); // protect operations on connections
|
std::lock_guard<std::mutex> lock(activeCrit); // protect operations on connections
|
||||||
|
|
||||||
|
// halt packet handling if server is shutting down
|
||||||
|
if (!active)
|
||||||
|
return;
|
||||||
|
|
||||||
// player sockets
|
// player sockets
|
||||||
if (connections.find(fds[i].fd) == connections.end()) {
|
if (connections.find(fds[i].fd) == connections.end()) {
|
||||||
std::cout << "[WARN] Event on non-existant socket?" << std::endl;
|
std::cout << "[FATAL] Event on non-existent socket: " << fds[i].fd << std::endl;
|
||||||
continue; // just to be safe
|
assert(0);
|
||||||
|
/* not reached */
|
||||||
}
|
}
|
||||||
|
|
||||||
CNSocket* cSock = connections[fds[i].fd];
|
CNSocket* cSock = connections[fds[i].fd];
|
||||||
@@ -485,22 +497,29 @@ void CNServer::start() {
|
|||||||
if (fds[i].revents & ~POLLIN)
|
if (fds[i].revents & ~POLLIN)
|
||||||
cSock->kill();
|
cSock->kill();
|
||||||
|
|
||||||
if (cSock->isAlive()) {
|
if (cSock->isAlive())
|
||||||
cSock->step();
|
cSock->step();
|
||||||
} else {
|
|
||||||
killConnection(cSock);
|
|
||||||
connections.erase(fds[i].fd);
|
|
||||||
delete cSock;
|
|
||||||
|
|
||||||
removePollFD(i);
|
|
||||||
|
|
||||||
// a new entry was moved to this position, so we check it again
|
|
||||||
i--;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onStep();
|
onStep();
|
||||||
|
|
||||||
|
// clean up dead connection sockets
|
||||||
|
auto it = connections.begin();
|
||||||
|
while (it != connections.end()) {
|
||||||
|
CNSocket *cSock = it->second;
|
||||||
|
|
||||||
|
if (!cSock->isAlive()) {
|
||||||
|
killConnection(cSock);
|
||||||
|
it = connections.erase(it);
|
||||||
|
|
||||||
|
removePollFD(cSock->sock);
|
||||||
|
|
||||||
|
delete cSock;
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -230,6 +230,7 @@ protected:
|
|||||||
const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots
|
const size_t STARTFDSCOUNT = 8; // number of initial PollFD slots
|
||||||
std::vector<PollFD> fds;
|
std::vector<PollFD> fds;
|
||||||
|
|
||||||
|
std::string serverType = "invalid";
|
||||||
SOCKET sock;
|
SOCKET sock;
|
||||||
uint16_t port;
|
uint16_t port;
|
||||||
socklen_t addressSize;
|
socklen_t addressSize;
|
||||||
|
@@ -1,27 +1,45 @@
|
|||||||
#include "core/CNShared.hpp"
|
#include "core/CNShared.hpp"
|
||||||
|
|
||||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
static std::unordered_map<int64_t, LoginMetadata*> logins;
|
||||||
#include "mingw/mingw.mutex.h"
|
static std::mutex mtx;
|
||||||
#else
|
|
||||||
#include <mutex>
|
|
||||||
#endif
|
|
||||||
std::map<int64_t, Player> CNSharedData::players;
|
|
||||||
std::mutex playerCrit;
|
|
||||||
|
|
||||||
void CNSharedData::setPlayer(int64_t sk, Player& plr) {
|
void CNShared::storeLoginMetadata(int64_t sk, LoginMetadata *lm) {
|
||||||
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
|
||||||
players[sk] = plr;
|
// take ownership of connection data
|
||||||
|
logins[sk] = lm;
|
||||||
}
|
}
|
||||||
|
|
||||||
Player CNSharedData::getPlayer(int64_t sk) {
|
LoginMetadata* CNShared::getLoginMetadata(int64_t sk) {
|
||||||
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
|
||||||
return players[sk];
|
// fail if the key isn't found
|
||||||
|
if (logins.find(sk) == logins.end())
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// transfer ownership of connection data to shard
|
||||||
|
LoginMetadata *lm = logins[sk];
|
||||||
|
logins.erase(sk);
|
||||||
|
|
||||||
|
return lm;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNSharedData::erasePlayer(int64_t sk) {
|
void CNShared::pruneLoginMetadata(CNServer *serv, time_t currTime) {
|
||||||
std::lock_guard<std::mutex> lock(playerCrit); // the lock will be removed when the function ends
|
std::lock_guard<std::mutex> lock(mtx);
|
||||||
|
|
||||||
players.erase(sk);
|
auto it = logins.begin();
|
||||||
|
while (it != logins.end()) {
|
||||||
|
auto& sk = it->first;
|
||||||
|
auto& lm = it->second;
|
||||||
|
|
||||||
|
if (currTime > lm->timestamp + CNSHARED_TIMEOUT) {
|
||||||
|
std::cout << "[WARN] Pruning hung connection attempt" << std::endl;
|
||||||
|
|
||||||
|
// deallocate object and remove map entry
|
||||||
|
delete logins[sk];
|
||||||
|
it = logins.erase(it); // skip the invalidated iterator
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -10,11 +10,20 @@
|
|||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
|
|
||||||
namespace CNSharedData {
|
/*
|
||||||
// serialkey corresponds to player data
|
* Connecions time out after 5 minutes, checked every 30 seconds.
|
||||||
extern std::map<int64_t, Player> players;
|
*/
|
||||||
|
#define CNSHARED_TIMEOUT 300000
|
||||||
|
#define CNSHARED_PERIOD 30000
|
||||||
|
|
||||||
void setPlayer(int64_t sk, Player& plr);
|
struct LoginMetadata {
|
||||||
Player getPlayer(int64_t sk);
|
uint64_t FEKey;
|
||||||
void erasePlayer(int64_t sk);
|
int32_t playerId;
|
||||||
|
time_t timestamp;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace CNShared {
|
||||||
|
void storeLoginMetadata(int64_t sk, LoginMetadata *lm);
|
||||||
|
LoginMetadata* getLoginMetadata(int64_t sk);
|
||||||
|
void pruneLoginMetadata(CNServer *serv, time_t currTime);
|
||||||
}
|
}
|
||||||
|
@@ -41,9 +41,6 @@
|
|||||||
#define ARRLEN(x) (sizeof(x)/sizeof(*x))
|
#define ARRLEN(x) (sizeof(x)/sizeof(*x))
|
||||||
#define AUTOU16TOU8(x) U16toU8(x, ARRLEN(x))
|
#define AUTOU16TOU8(x) U16toU8(x, ARRLEN(x))
|
||||||
|
|
||||||
// typedef for chunk position tuple
|
|
||||||
typedef std::tuple<int, int, uint64_t> ChunkPos;
|
|
||||||
|
|
||||||
// TODO: rewrite U16toU8 & U8toU16 to not use codecvt
|
// TODO: rewrite U16toU8 & U8toU16 to not use codecvt
|
||||||
|
|
||||||
std::string U16toU8(char16_t* src, size_t max);
|
std::string U16toU8(char16_t* src, size_t max);
|
||||||
|
@@ -41,6 +41,7 @@ namespace Database {
|
|||||||
uint64_t Timestamp;
|
uint64_t Timestamp;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
void init();
|
||||||
void open();
|
void open();
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
@@ -52,7 +53,8 @@ namespace Database {
|
|||||||
bool banPlayer(int playerId, std::string& reason);
|
bool banPlayer(int playerId, std::string& reason);
|
||||||
bool unbanPlayer(int playerId);
|
bool unbanPlayer(int playerId);
|
||||||
|
|
||||||
void updateSelected(int accountId, int playerId);
|
void updateSelected(int accountId, int slot);
|
||||||
|
void updateSelectedByPlayerId(int accountId, int playerId);
|
||||||
|
|
||||||
bool validateCharacter(int characterID, int userID);
|
bool validateCharacter(int characterID, int userID);
|
||||||
bool isNameFree(std::string firstName, std::string lastName);
|
bool isNameFree(std::string firstName, std::string lastName);
|
||||||
@@ -78,7 +80,9 @@ namespace Database {
|
|||||||
|
|
||||||
// getting players
|
// getting players
|
||||||
void getPlayer(Player* plr, int id);
|
void getPlayer(Player* plr, int id);
|
||||||
|
bool _updatePlayer(Player *player);
|
||||||
void updatePlayer(Player *player);
|
void updatePlayer(Player *player);
|
||||||
|
void commitTrade(Player *plr1, Player *plr2);
|
||||||
|
|
||||||
// buddies
|
// buddies
|
||||||
int getNumBuddies(Player* player);
|
int getNumBuddies(Player* player);
|
||||||
@@ -98,7 +102,7 @@ namespace Database {
|
|||||||
void deleteEmailAttachments(int playerID, int index, int slot);
|
void deleteEmailAttachments(int playerID, int index, int slot);
|
||||||
void deleteEmails(int playerID, int64_t* indices);
|
void deleteEmails(int playerID, int64_t* indices);
|
||||||
int getNextEmailIndex(int playerID);
|
int getNextEmailIndex(int playerID);
|
||||||
bool sendEmail(EmailData* data, std::vector<sItemBase> attachments);
|
bool sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender);
|
||||||
|
|
||||||
// racing
|
// racing
|
||||||
RaceRanking getTopRaceRanking(int epID, int playerID);
|
RaceRanking getTopRaceRanking(int epID, int playerID);
|
||||||
|
@@ -152,7 +152,8 @@ void Database::updateEmailContent(EmailData* data) {
|
|||||||
sqlite3_step(stmt);
|
sqlite3_step(stmt);
|
||||||
int attachmentsCount = sqlite3_column_int(stmt, 0);
|
int attachmentsCount = sqlite3_column_int(stmt, 0);
|
||||||
|
|
||||||
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0; // set attachment flag dynamically
|
// set attachment flag dynamically
|
||||||
|
data->ItemFlag = (data->Taros > 0 || attachmentsCount > 0) ? 1 : 0;
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
@@ -265,7 +266,7 @@ int Database::getNextEmailIndex(int playerID) {
|
|||||||
return (index > 0 ? index + 1 : 1);
|
return (index > 0 ? index + 1 : 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
|
bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments, Player *sender) {
|
||||||
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);
|
||||||
@@ -330,6 +331,13 @@ bool Database::sendEmail(EmailData* data, std::vector<sItemBase> attachments) {
|
|||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
if (!_updatePlayer(sender)) {
|
||||||
|
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@@ -99,7 +99,7 @@ static void checkMetaTable() {
|
|||||||
sqlite3_stmt* stmt;
|
sqlite3_stmt* stmt;
|
||||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
if (sqlite3_step(stmt) != SQLITE_ROW) {
|
if (sqlite3_step(stmt) != SQLITE_ROW) {
|
||||||
std::cout << "[FATAL] Failed to check meta table" << sqlite3_errmsg(db) << std::endl;
|
std::cout << "[FATAL] Failed to check meta table: " << sqlite3_errmsg(db) << std::endl;
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
@@ -236,7 +236,20 @@ static int getTableSize(std::string tableName) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Database::init() {
|
||||||
|
std::cout << "[INFO] Built with libsqlite " SQLITE_VERSION << std::endl;
|
||||||
|
|
||||||
|
if (sqlite3_libversion_number() != SQLITE_VERSION_NUMBER)
|
||||||
|
std::cout << "[INFO] Using libsqlite " << std::string(sqlite3_libversion()) << std::endl;
|
||||||
|
|
||||||
|
if (sqlite3_libversion_number() < MIN_SUPPORTED_SQLITE_NUMBER) {
|
||||||
|
std::cerr << "[FATAL] Runtime sqlite version too old. Minimum compatible version: " MIN_SUPPORTED_SQLITE << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Database::open() {
|
void Database::open() {
|
||||||
|
|
||||||
// XXX: move locks here
|
// XXX: move locks here
|
||||||
int rc = sqlite3_open(settings::DBPATH.c_str(), &db);
|
int rc = sqlite3_open(settings::DBPATH.c_str(), &db);
|
||||||
if (rc != SQLITE_OK) {
|
if (rc != SQLITE_OK) {
|
||||||
|
@@ -3,10 +3,13 @@
|
|||||||
#include "db/Database.hpp"
|
#include "db/Database.hpp"
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
#if defined(__MINGW32__) && !defined(_GLIBCXX_HAS_GTHREADS)
|
#define MIN_SUPPORTED_SQLITE_NUMBER 3033000
|
||||||
#include "mingw/mingw.mutex.h"
|
#define MIN_SUPPORTED_SQLITE "3.33.0"
|
||||||
#else
|
// we can't use this in #error, since it doesn't expand macros
|
||||||
#include <mutex>
|
|
||||||
|
// Compile-time libsqlite version check
|
||||||
|
#if SQLITE_VERSION_NUMBER < MIN_SUPPORTED_SQLITE_NUMBER
|
||||||
|
#error libsqlite version too old. Minimum compatible version: 3.33.0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern std::mutex dbCrit;
|
extern std::mutex dbCrit;
|
||||||
|
@@ -79,6 +79,29 @@ void Database::updateSelected(int accountId, int slot) {
|
|||||||
std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl;
|
std::cout << "[WARN] Database fail on updateSelected(): " << sqlite3_errmsg(db) << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Database::updateSelectedByPlayerId(int accountId, int32_t playerId) {
|
||||||
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
|
const char* sql = R"(
|
||||||
|
UPDATE Accounts SET
|
||||||
|
Selected = p.Slot,
|
||||||
|
LastLogin = (strftime('%s', 'now'))
|
||||||
|
FROM (SELECT Slot From Players WHERE PlayerId = ?) AS p
|
||||||
|
WHERE AccountID = ?;
|
||||||
|
)";
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
|
||||||
|
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
|
sqlite3_bind_int(stmt, 1, playerId);
|
||||||
|
sqlite3_bind_int(stmt, 2, accountId);
|
||||||
|
int rc = sqlite3_step(stmt);
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
if (rc != SQLITE_DONE)
|
||||||
|
std::cout << "[WARN] Database fail on updateSelectedByPlayerId(): " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
bool Database::validateCharacter(int characterID, int userID) {
|
bool Database::validateCharacter(int characterID, int userID) {
|
||||||
std::lock_guard<std::mutex> lock(dbCrit);
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
|
@@ -285,11 +285,13 @@ void Database::getPlayer(Player* plr, int id) {
|
|||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Database::updatePlayer(Player *player) {
|
/*
|
||||||
std::lock_guard<std::mutex> lock(dbCrit);
|
* Low-level function to save a player to DB.
|
||||||
|
* Must be run in a SQL transaction and with dbCrit locked.
|
||||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
* The caller manages the transacstion, so if this function returns false,
|
||||||
|
* the caller must roll it back.
|
||||||
|
*/
|
||||||
|
bool Database::_updatePlayer(Player *player) {
|
||||||
const char* sql = R"(
|
const char* sql = R"(
|
||||||
UPDATE Players
|
UPDATE Players
|
||||||
SET
|
SET
|
||||||
@@ -336,10 +338,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 21, player->iID);
|
sqlite3_bind_int(stmt, 21, player->iID);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
@@ -375,10 +375,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
rc = sqlite3_step(stmt);
|
rc = sqlite3_step(stmt);
|
||||||
|
|
||||||
if (rc != SQLITE_DONE) {
|
if (rc != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
@@ -395,10 +393,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit);
|
sqlite3_bind_int(stmt, 6, player->Inven[i].iTimeLimit);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
@@ -415,10 +411,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit);
|
sqlite3_bind_int(stmt, 6, player->Bank[i].iTimeLimit);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
@@ -451,10 +445,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 4, player->QInven[i].iID);
|
sqlite3_bind_int(stmt, 4, player->QInven[i].iID);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
@@ -487,10 +479,8 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina);
|
sqlite3_bind_int(stmt, 4, player->Nanos[i].iStamina);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
@@ -524,14 +514,47 @@ void Database::updatePlayer(Player *player) {
|
|||||||
sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]);
|
sqlite3_bind_int(stmt, 5, player->RemainingNPCCount[i][2]);
|
||||||
|
|
||||||
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
if (sqlite3_step(stmt) != SQLITE_DONE) {
|
||||||
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
|
||||||
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
sqlite3_reset(stmt);
|
sqlite3_reset(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::updatePlayer(Player *player) {
|
||||||
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
|
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
|
||||||
|
if (!_updatePlayer(player)) {
|
||||||
|
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Database::commitTrade(Player *plr1, Player *plr2) {
|
||||||
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
|
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
|
||||||
|
if (!_updatePlayer(plr1)) {
|
||||||
|
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_updatePlayer(plr2)) {
|
||||||
|
std::cout << "[WARN] Database: Failed to save player to database: " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
sqlite3_exec(db, "ROLLBACK TRANSACTION;", NULL, NULL, NULL);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_exec(db, "COMMIT;", NULL, NULL, NULL);
|
||||||
}
|
}
|
||||||
|
49
src/main.cpp
49
src/main.cpp
@@ -25,6 +25,7 @@
|
|||||||
#include "Rand.hpp"
|
#include "Rand.hpp"
|
||||||
|
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
#include "sandbox/Sandbox.hpp"
|
||||||
|
|
||||||
#include "../version.h"
|
#include "../version.h"
|
||||||
|
|
||||||
@@ -62,8 +63,20 @@ void terminate(int arg) {
|
|||||||
exit(0);
|
exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifdef _WIN32
|
||||||
|
static BOOL winTerminate(DWORD arg) {
|
||||||
|
terminate(0);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void initsignals() {
|
void initsignals() {
|
||||||
|
#ifdef _WIN32
|
||||||
|
if (!SetConsoleCtrlHandler(winTerminate, TRUE)) {
|
||||||
|
std::cerr << "[FATAL] Failed to set control handler" << std::endl;
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
#else
|
||||||
struct sigaction act;
|
struct sigaction act;
|
||||||
|
|
||||||
memset((void*)&act, 0, sizeof(act));
|
memset((void*)&act, 0, sizeof(act));
|
||||||
@@ -81,25 +94,29 @@ void initsignals() {
|
|||||||
perror("sigaction");
|
perror("sigaction");
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
|
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
|
||||||
|
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
WSADATA wsaData;
|
WSADATA wsaData;
|
||||||
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
|
if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
|
||||||
std::cerr << "OpenFusion: WSAStartup failed" << std::endl;
|
std::cerr << "OpenFusion: WSAStartup failed" << std::endl;
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
#else
|
|
||||||
initsignals();
|
|
||||||
#endif
|
#endif
|
||||||
Rand::init(getTime());
|
|
||||||
|
initsignals();
|
||||||
settings::init();
|
settings::init();
|
||||||
std::cout << "[INFO] OpenFusion v" GIT_VERSION << std::endl;
|
Database::init();
|
||||||
std::cout << "[INFO] Protocol version: " << PROTOCOL_VERSION << std::endl;
|
Rand::init(getTime());
|
||||||
std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
|
|
||||||
TableData::init();
|
TableData::init();
|
||||||
|
|
||||||
|
std::cout << "[INFO] Intializing Packet Managers..." << std::endl;
|
||||||
|
|
||||||
PlayerManager::init();
|
PlayerManager::init();
|
||||||
PlayerMovement::init();
|
PlayerMovement::init();
|
||||||
BuiltinCommands::init();
|
BuiltinCommands::init();
|
||||||
@@ -118,9 +135,10 @@ int main() {
|
|||||||
Email::init();
|
Email::init();
|
||||||
Groups::init();
|
Groups::init();
|
||||||
Racing::init();
|
Racing::init();
|
||||||
Database::open();
|
|
||||||
Trading::init();
|
Trading::init();
|
||||||
|
|
||||||
|
Database::open();
|
||||||
|
|
||||||
switch (settings::EVENTMODE) {
|
switch (settings::EVENTMODE) {
|
||||||
case 0: break; // no event
|
case 0: break; // no event
|
||||||
case 1: std::cout << "[INFO] Event active. Hey, Hey It's Knishmas!" << std::endl; break;
|
case 1: std::cout << "[INFO] Event active. Hey, Hey It's Knishmas!" << std::endl; break;
|
||||||
@@ -138,6 +156,8 @@ int main() {
|
|||||||
|
|
||||||
shardThread = new std::thread(startShard, (CNShardServer*)shardServer);
|
shardThread = new std::thread(startShard, (CNShardServer*)shardServer);
|
||||||
|
|
||||||
|
sandbox_start();
|
||||||
|
|
||||||
loginServer.start();
|
loginServer.start();
|
||||||
|
|
||||||
shardServer->kill();
|
shardServer->kill();
|
||||||
@@ -152,10 +172,15 @@ int main() {
|
|||||||
// helper functions
|
// helper functions
|
||||||
|
|
||||||
std::string U16toU8(char16_t* src, size_t max) {
|
std::string U16toU8(char16_t* src, size_t max) {
|
||||||
src[max-1] = '\0'; // force a NULL terminatorstd::string U16toU8(char16_t* src) {
|
src[max-1] = '\0'; // force a NULL terminator
|
||||||
try {
|
try {
|
||||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
|
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> convert;
|
||||||
return convert.to_bytes(src);
|
std::string ret = convert.to_bytes(src);
|
||||||
|
|
||||||
|
if (ret.size() >= max)
|
||||||
|
ret.resize(max-2);
|
||||||
|
|
||||||
|
return ret;
|
||||||
} catch(const std::exception& e) {
|
} catch(const std::exception& e) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
@@ -179,7 +204,7 @@ size_t U8toU16(std::string src, char16_t* des, size_t max) {
|
|||||||
time_t getTime() {
|
time_t getTime() {
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
|
|
||||||
milliseconds value = duration_cast<milliseconds>((time_point_cast<milliseconds>(high_resolution_clock::now())).time_since_epoch());
|
milliseconds value = duration_cast<milliseconds>((time_point_cast<milliseconds>(steady_clock::now())).time_since_epoch());
|
||||||
|
|
||||||
return (time_t)value.count();
|
return (time_t)value.count();
|
||||||
}
|
}
|
||||||
|
21
src/sandbox/Sandbox.hpp
Normal file
21
src/sandbox/Sandbox.hpp
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// use the sandbox on supported platforms, unless disabled
|
||||||
|
#if defined(__linux__) || defined(__OpenBSD__)
|
||||||
|
|
||||||
|
# if !defined(CONFIG_NOSANDBOX)
|
||||||
|
void sandbox_start();
|
||||||
|
# else
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
inline void sandbox_start() {
|
||||||
|
std::cout << "[WARN] Built without a sandbox" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
# endif // CONFIG_NOSANDBOX
|
||||||
|
|
||||||
|
#else
|
||||||
|
// stub for unsupported platforms
|
||||||
|
inline void sandbox_start() {}
|
||||||
|
#endif
|
45
src/sandbox/openbsd.cpp
Normal file
45
src/sandbox/openbsd.cpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#if defined(__OpenBSD__) && !defined(CONFIG_NOSANDBOX)
|
||||||
|
|
||||||
|
#include "core/Core.hpp"
|
||||||
|
#include "settings.hpp"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include <err.h>
|
||||||
|
|
||||||
|
static void eunveil(const char *path, const char *permissions) {
|
||||||
|
if (unveil(path, permissions) < 0)
|
||||||
|
err(1, "unveil");
|
||||||
|
}
|
||||||
|
|
||||||
|
void sandbox_start() {
|
||||||
|
/*
|
||||||
|
* There shouldn't ever be a reason to disable this one, but might as well
|
||||||
|
* be consistent with the Linux sandbox.
|
||||||
|
*/
|
||||||
|
if (!settings::SANDBOX) {
|
||||||
|
std::cout << "[WARN] Running without a sandbox" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[INFO] Starting pledge+unveil sandbox..." << std::endl;
|
||||||
|
|
||||||
|
if (pledge("stdio rpath wpath cpath inet flock unveil", NULL) < 0)
|
||||||
|
err(1, "pledge");
|
||||||
|
|
||||||
|
// database stuff
|
||||||
|
eunveil(settings::DBPATH.c_str(), "rwc");
|
||||||
|
eunveil((settings::DBPATH + "-journal").c_str(), "rwc");
|
||||||
|
eunveil((settings::DBPATH + "-wal").c_str(), "rwc");
|
||||||
|
|
||||||
|
// tabledata stuff
|
||||||
|
eunveil((settings::TDATADIR + "/" + settings::GRUNTWORKJSON).c_str(), "wc");
|
||||||
|
|
||||||
|
// for bcrypt_gensalt()
|
||||||
|
eunveil("/dev/urandom", "r");
|
||||||
|
|
||||||
|
eunveil(NULL, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
324
src/sandbox/seccomp.cpp
Normal file
324
src/sandbox/seccomp.cpp
Normal file
@@ -0,0 +1,324 @@
|
|||||||
|
#if defined(__linux__) && !defined(CONFIG_NOSANDBOX)
|
||||||
|
|
||||||
|
#include "core/Core.hpp" // mostly for ARRLEN
|
||||||
|
#include "settings.hpp"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include <sys/prctl.h>
|
||||||
|
#include <sys/ptrace.h>
|
||||||
|
#include <sys/mman.h> // for mmap() args
|
||||||
|
#include <sys/ioctl.h> // for ioctl() args
|
||||||
|
#include <termios.h> // for ioctl() args
|
||||||
|
|
||||||
|
#include <linux/unistd.h>
|
||||||
|
#include <linux/seccomp.h>
|
||||||
|
#include <linux/filter.h>
|
||||||
|
#include <linux/audit.h>
|
||||||
|
#include <linux/net.h> // for socketcall() args
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Macros adapted from https://outflux.net/teach-seccomp/
|
||||||
|
* Relevant license:
|
||||||
|
* https://source.chromium.org/chromium/chromium/src/+/master:LICENSE
|
||||||
|
*/
|
||||||
|
#define syscall_nr (offsetof(struct seccomp_data, nr))
|
||||||
|
#define arch_nr (offsetof(struct seccomp_data, arch))
|
||||||
|
|
||||||
|
#if defined(__i386__)
|
||||||
|
# define ARCH_NR AUDIT_ARCH_I386
|
||||||
|
#elif defined(__x86_64__)
|
||||||
|
# define ARCH_NR AUDIT_ARCH_X86_64
|
||||||
|
#elif defined(__arm__)
|
||||||
|
# define ARCH_NR AUDIT_ARCH_ARM
|
||||||
|
#elif defined(__aarch64__)
|
||||||
|
# define ARCH_NR AUDIT_ARCH_AARCH64
|
||||||
|
#else
|
||||||
|
# error "Seccomp-bpf sandbox unsupported on this architecture"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define VALIDATE_ARCHITECTURE \
|
||||||
|
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr), \
|
||||||
|
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0), \
|
||||||
|
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS)
|
||||||
|
|
||||||
|
#define EXAMINE_SYSCALL \
|
||||||
|
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr)
|
||||||
|
|
||||||
|
#define ALLOW_SYSCALL(name) \
|
||||||
|
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, __NR_##name, 0, 1), \
|
||||||
|
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW)
|
||||||
|
|
||||||
|
#define DENY_SYSCALL_ERRNO(name, _errno) \
|
||||||
|
BPF_JUMP(BPF_JMP+BPF_K+BPF_JEQ, __NR_##name, 0, 1), \
|
||||||
|
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ERRNO|(_errno))
|
||||||
|
|
||||||
|
#define KILL_PROCESS \
|
||||||
|
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL_PROCESS)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Macros adapted from openssh's sandbox-seccomp-filter.c
|
||||||
|
* Relevant license:
|
||||||
|
* https://github.com/openssh/openssh-portable/blob/master/LICENCE
|
||||||
|
*/
|
||||||
|
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||||
|
# define ARG_LO_OFFSET 0
|
||||||
|
# define ARG_HI_OFFSET sizeof(uint32_t)
|
||||||
|
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||||
|
# define ARG_LO_OFFSET sizeof(uint32_t)
|
||||||
|
# define ARG_HI_OFFSET 0
|
||||||
|
#else
|
||||||
|
#error "Unknown endianness"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ALLOW_SYSCALL_ARG(_nr, _arg_nr, _arg_val) \
|
||||||
|
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (__NR_##_nr), 0, 6), \
|
||||||
|
/* load and test syscall argument, low word */ \
|
||||||
|
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||||
|
offsetof(struct seccomp_data, args[(_arg_nr)]) + ARG_LO_OFFSET), \
|
||||||
|
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, \
|
||||||
|
((_arg_val) & 0xFFFFFFFF), 0, 3), \
|
||||||
|
/* load and test syscall argument, high word */ \
|
||||||
|
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||||
|
offsetof(struct seccomp_data, args[(_arg_nr)]) + ARG_HI_OFFSET), \
|
||||||
|
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, \
|
||||||
|
(((uint32_t)((uint64_t)(_arg_val) >> 32)) & 0xFFFFFFFF), 0, 1), \
|
||||||
|
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), \
|
||||||
|
/* reload syscall number; all rules expect it in accumulator */ \
|
||||||
|
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||||
|
offsetof(struct seccomp_data, nr))
|
||||||
|
|
||||||
|
/* Allow if syscall argument contains only values in mask */
|
||||||
|
#define ALLOW_SYSCALL_ARG_MASK(_nr, _arg_nr, _arg_mask) \
|
||||||
|
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, (__NR_##_nr), 0, 8), \
|
||||||
|
/* load, mask and test syscall argument, low word */ \
|
||||||
|
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||||
|
offsetof(struct seccomp_data, args[(_arg_nr)]) + ARG_LO_OFFSET), \
|
||||||
|
BPF_STMT(BPF_ALU+BPF_AND+BPF_K, ~((_arg_mask) & 0xFFFFFFFF)), \
|
||||||
|
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 4), \
|
||||||
|
/* load, mask and test syscall argument, high word */ \
|
||||||
|
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||||
|
offsetof(struct seccomp_data, args[(_arg_nr)]) + ARG_HI_OFFSET), \
|
||||||
|
BPF_STMT(BPF_ALU+BPF_AND+BPF_K, \
|
||||||
|
~(((uint32_t)((uint64_t)(_arg_mask) >> 32)) & 0xFFFFFFFF)), \
|
||||||
|
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, 0, 0, 1), \
|
||||||
|
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_ALLOW), \
|
||||||
|
/* reload syscall number; all rules expect it in accumulator */ \
|
||||||
|
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, \
|
||||||
|
offsetof(struct seccomp_data, nr))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This is a special case for AArch64 where this syscall apparently only
|
||||||
|
* exists in 32-bit compatibility mode, so we can't include the definition
|
||||||
|
* even though it gets called somewhere in libc.
|
||||||
|
*/
|
||||||
|
#if defined(__aarch64__) && !defined(__NR_fstatat64)
|
||||||
|
#define __NR_fstatat64 0x4f
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The main supported configuration is Linux on x86_64 with either glibc or
|
||||||
|
* musl-libc, with secondary support for x86, ARM and ARM64 (AAarch64) Linux.
|
||||||
|
*
|
||||||
|
* Syscalls marked with "maybe" don't seem to be used in the default
|
||||||
|
* configuration, but should probably be whitelisted anyway.
|
||||||
|
*
|
||||||
|
* Syscalls marked with comments like "musl-libc", "raspi" or "alt DB" were
|
||||||
|
* observed to be necessary on that particular configuration, but there are
|
||||||
|
* probably other configurations in which they are neccessary as well.
|
||||||
|
* ("alt DB" represents libsqlite compiled with different options.)
|
||||||
|
*
|
||||||
|
* Syscalls marked "vdso" aren't normally caught by seccomp because they are
|
||||||
|
* implemented in the vdso(7) in most configurations, but it's still prudent
|
||||||
|
* to whitelist them here.
|
||||||
|
*/
|
||||||
|
static sock_filter filter[] = {
|
||||||
|
VALIDATE_ARCHITECTURE,
|
||||||
|
EXAMINE_SYSCALL,
|
||||||
|
|
||||||
|
// memory management
|
||||||
|
#ifdef __NR_mmap
|
||||||
|
ALLOW_SYSCALL_ARG_MASK(mmap, 2, PROT_NONE|PROT_READ|PROT_WRITE),
|
||||||
|
#endif
|
||||||
|
ALLOW_SYSCALL(munmap),
|
||||||
|
ALLOW_SYSCALL_ARG_MASK(mprotect, 2, PROT_NONE|PROT_READ|PROT_WRITE),
|
||||||
|
ALLOW_SYSCALL(madvise),
|
||||||
|
ALLOW_SYSCALL(brk),
|
||||||
|
|
||||||
|
// basic file IO
|
||||||
|
#ifdef __NR_open
|
||||||
|
ALLOW_SYSCALL(open),
|
||||||
|
#endif
|
||||||
|
ALLOW_SYSCALL(openat),
|
||||||
|
ALLOW_SYSCALL(read),
|
||||||
|
ALLOW_SYSCALL(write),
|
||||||
|
ALLOW_SYSCALL(close),
|
||||||
|
#ifdef __NR_stat
|
||||||
|
ALLOW_SYSCALL(stat),
|
||||||
|
#endif
|
||||||
|
ALLOW_SYSCALL(fstat),
|
||||||
|
#ifdef __NR_newfstatat
|
||||||
|
ALLOW_SYSCALL(newfstatat),
|
||||||
|
#endif
|
||||||
|
ALLOW_SYSCALL(fsync), // maybe
|
||||||
|
#ifdef __NR_creat
|
||||||
|
ALLOW_SYSCALL(creat), // maybe; for DB journal
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_unlink
|
||||||
|
ALLOW_SYSCALL(unlink), // for DB journal
|
||||||
|
#endif
|
||||||
|
ALLOW_SYSCALL(lseek), // musl-libc; alt DB
|
||||||
|
ALLOW_SYSCALL(truncate), // for truncate-mode DB
|
||||||
|
ALLOW_SYSCALL(ftruncate), // for truncate-mode DB
|
||||||
|
ALLOW_SYSCALL(dup), // for perror(), apparently
|
||||||
|
|
||||||
|
// more IO
|
||||||
|
ALLOW_SYSCALL(pread64),
|
||||||
|
ALLOW_SYSCALL(pwrite64),
|
||||||
|
ALLOW_SYSCALL(fdatasync),
|
||||||
|
ALLOW_SYSCALL(writev), // musl-libc
|
||||||
|
ALLOW_SYSCALL(preadv), // maybe; alt-DB
|
||||||
|
ALLOW_SYSCALL(preadv2), // maybe
|
||||||
|
|
||||||
|
// misc syscalls called from libc
|
||||||
|
ALLOW_SYSCALL(getcwd),
|
||||||
|
ALLOW_SYSCALL(getpid),
|
||||||
|
ALLOW_SYSCALL(geteuid),
|
||||||
|
ALLOW_SYSCALL(gettid), // maybe
|
||||||
|
ALLOW_SYSCALL_ARG(ioctl, 1, TIOCGWINSZ), // musl-libc
|
||||||
|
ALLOW_SYSCALL_ARG(fcntl, 1, F_GETFL),
|
||||||
|
ALLOW_SYSCALL_ARG(fcntl, 1, F_SETFL),
|
||||||
|
ALLOW_SYSCALL_ARG(fcntl, 1, F_GETLK),
|
||||||
|
ALLOW_SYSCALL_ARG(fcntl, 1, F_SETLK),
|
||||||
|
ALLOW_SYSCALL_ARG(fcntl, 1, F_SETLKW), // maybe
|
||||||
|
ALLOW_SYSCALL(exit),
|
||||||
|
ALLOW_SYSCALL(exit_group),
|
||||||
|
ALLOW_SYSCALL(rt_sigprocmask), // musl-libc
|
||||||
|
ALLOW_SYSCALL(clock_nanosleep), // gets called very rarely
|
||||||
|
#ifdef __NR_rseq
|
||||||
|
ALLOW_SYSCALL(rseq),
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// to crash properly on SIGSEGV
|
||||||
|
DENY_SYSCALL_ERRNO(tgkill, EPERM),
|
||||||
|
DENY_SYSCALL_ERRNO(tkill, EPERM), // musl-libc
|
||||||
|
DENY_SYSCALL_ERRNO(rt_sigaction, EPERM),
|
||||||
|
|
||||||
|
// threading
|
||||||
|
ALLOW_SYSCALL(futex),
|
||||||
|
|
||||||
|
// networking
|
||||||
|
#ifdef __NR_poll
|
||||||
|
ALLOW_SYSCALL(poll),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_accept
|
||||||
|
ALLOW_SYSCALL(accept),
|
||||||
|
#endif
|
||||||
|
ALLOW_SYSCALL(setsockopt),
|
||||||
|
ALLOW_SYSCALL(sendto),
|
||||||
|
ALLOW_SYSCALL(recvfrom),
|
||||||
|
ALLOW_SYSCALL(shutdown),
|
||||||
|
|
||||||
|
// vdso
|
||||||
|
ALLOW_SYSCALL(clock_gettime),
|
||||||
|
ALLOW_SYSCALL(gettimeofday),
|
||||||
|
#ifdef __NR_time
|
||||||
|
ALLOW_SYSCALL(time),
|
||||||
|
#endif
|
||||||
|
ALLOW_SYSCALL(rt_sigreturn),
|
||||||
|
|
||||||
|
// i386
|
||||||
|
#ifdef __NR_socketcall
|
||||||
|
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_ACCEPT),
|
||||||
|
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_SETSOCKOPT),
|
||||||
|
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_SEND),
|
||||||
|
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_RECV),
|
||||||
|
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_SENDTO), // maybe
|
||||||
|
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_RECVFROM), // maybe
|
||||||
|
ALLOW_SYSCALL_ARG(socketcall, 0, SYS_SHUTDOWN),
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Raspberry Pi (ARM)
|
||||||
|
#ifdef __NR_set_robust_list
|
||||||
|
ALLOW_SYSCALL(set_robust_list),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_clock_gettime64
|
||||||
|
ALLOW_SYSCALL(clock_gettime64),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_mmap2
|
||||||
|
ALLOW_SYSCALL_ARG_MASK(mmap2, 2, PROT_NONE|PROT_READ|PROT_WRITE),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_fcntl64
|
||||||
|
ALLOW_SYSCALL(fcntl64),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_stat64
|
||||||
|
ALLOW_SYSCALL(stat64),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_send
|
||||||
|
ALLOW_SYSCALL(send),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_recv
|
||||||
|
ALLOW_SYSCALL(recv),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_fstat64
|
||||||
|
ALLOW_SYSCALL(fstat64),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_geteuid32
|
||||||
|
ALLOW_SYSCALL(geteuid32),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_truncate64
|
||||||
|
ALLOW_SYSCALL(truncate64),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_ftruncate64
|
||||||
|
ALLOW_SYSCALL(ftruncate64),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_sigreturn
|
||||||
|
ALLOW_SYSCALL(sigreturn), // vdso
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_clock_nanosleep_time64
|
||||||
|
ALLOW_SYSCALL(clock_nanosleep_time64), // maybe
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// AArch64 (ARM64)
|
||||||
|
#ifdef __NR_unlinkat
|
||||||
|
ALLOW_SYSCALL(unlinkat),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_fstatat64
|
||||||
|
ALLOW_SYSCALL(fstatat64),
|
||||||
|
#endif
|
||||||
|
#ifdef __NR_ppoll
|
||||||
|
ALLOW_SYSCALL(ppoll),
|
||||||
|
#endif
|
||||||
|
|
||||||
|
KILL_PROCESS
|
||||||
|
};
|
||||||
|
|
||||||
|
static sock_fprog prog = {
|
||||||
|
ARRLEN(filter), filter
|
||||||
|
};
|
||||||
|
|
||||||
|
// our own wrapper for the seccomp() syscall
|
||||||
|
int seccomp(unsigned int operation, unsigned int flags, void *args) {
|
||||||
|
return syscall(__NR_seccomp, operation, flags, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sandbox_start() {
|
||||||
|
if (!settings::SANDBOX) {
|
||||||
|
std::cout << "[WARN] Running without a sandbox" << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::cout << "[INFO] Starting seccomp-bpf sandbox..." << std::endl;
|
||||||
|
|
||||||
|
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) {
|
||||||
|
perror("prctl");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (seccomp(SECCOMP_SET_MODE_FILTER, SECCOMP_FILTER_FLAG_TSYNC, &prog) < 0) {
|
||||||
|
perror("seccomp");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@@ -11,6 +11,7 @@
|
|||||||
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
||||||
|
|
||||||
CNLoginServer::CNLoginServer(uint16_t p) {
|
CNLoginServer::CNLoginServer(uint16_t p) {
|
||||||
|
serverType = "login";
|
||||||
port = p;
|
port = p;
|
||||||
pHandler = &CNLoginServer::handlePacket;
|
pHandler = &CNLoginServer::handlePacket;
|
||||||
init();
|
init();
|
||||||
@@ -19,6 +20,17 @@ CNLoginServer::CNLoginServer(uint16_t p) {
|
|||||||
void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
void CNLoginServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||||
printPacket(data);
|
printPacket(data);
|
||||||
|
|
||||||
|
if (loginSessions.find(sock) == loginSessions.end() &&
|
||||||
|
data->type != P_CL2LS_REQ_LOGIN && data->type != P_CL2LS_REP_LIVE_CHECK) {
|
||||||
|
|
||||||
|
if (settings::VERBOSITY > 0) {
|
||||||
|
std::cerr << "OpenFusion: LOGIN PKT OUT-OF-SEQ. PacketType: " <<
|
||||||
|
Packets::p2str(data->type) << " (" << data->type << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (data->type) {
|
switch (data->type) {
|
||||||
case P_CL2LS_REQ_LOGIN: {
|
case P_CL2LS_REQ_LOGIN: {
|
||||||
login(sock, data);
|
login(sock, data);
|
||||||
@@ -133,9 +145,13 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
|
|||||||
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);
|
return newAccount(sock, userLogin, userPassword, login->iClientVerC);
|
||||||
|
|
||||||
|
return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock);
|
||||||
|
}
|
||||||
|
|
||||||
if (!CNLoginServer::isPasswordCorrect(findUser.Password, userPassword))
|
if (!CNLoginServer::isPasswordCorrect(findUser.Password, userPassword))
|
||||||
return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock);
|
return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock);
|
||||||
|
|
||||||
@@ -193,9 +209,12 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
|
|||||||
// send the resp in with original key
|
// send the resp in with original key
|
||||||
sock->sendPacket(resp, P_LS2CL_REP_LOGIN_SUCC);
|
sock->sendPacket(resp, P_LS2CL_REP_LOGIN_SUCC);
|
||||||
|
|
||||||
|
uint64_t defaultKey;
|
||||||
|
memcpy(&defaultKey, CNSocketEncryption::defaultKey, sizeof(defaultKey));
|
||||||
|
|
||||||
// update keys
|
// update keys
|
||||||
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
|
sock->setEKey(CNSocketEncryption::createNewKey(resp.uiSvrTime, resp.iCharCount + 1, resp.iSlotNum + 1));
|
||||||
sock->setFEKey(CNSocketEncryption::createNewKey((uint64_t)(*(uint64_t*)&CNSocketEncryption::defaultKey[0]), login->iClientVerC, 1));
|
sock->setFEKey(CNSocketEncryption::createNewKey(defaultKey, login->iClientVerC, 1));
|
||||||
|
|
||||||
DEBUGLOG(
|
DEBUGLOG(
|
||||||
std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl;
|
std::cout << "Login Server: Login success. Welcome " << userLogin << " [" << loginSessions[sock].userID << "]" << std::endl;
|
||||||
@@ -445,7 +464,7 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
|
|||||||
* the shard IP has been configured to an address the local machine can't
|
* the shard IP has been configured to an address the local machine can't
|
||||||
* reach itself from.
|
* reach itself from.
|
||||||
*/
|
*/
|
||||||
if (sock->sockaddr.sin_addr.s_addr == htonl(INADDR_LOOPBACK))
|
if (settings::LOCALHOSTWORKAROUND && sock->sockaddr.sin_addr.s_addr == htonl(INADDR_LOOPBACK))
|
||||||
shard_ip = "127.0.0.1";
|
shard_ip = "127.0.0.1";
|
||||||
|
|
||||||
memcpy(resp.g_FE_ServerIP, shard_ip, strlen(shard_ip));
|
memcpy(resp.g_FE_ServerIP, shard_ip, strlen(shard_ip));
|
||||||
@@ -453,21 +472,20 @@ void CNLoginServer::characterSelect(CNSocket* sock, CNPacketData* data) {
|
|||||||
resp.g_FE_ServerIP[strlen(shard_ip)] = '\0';
|
resp.g_FE_ServerIP[strlen(shard_ip)] = '\0';
|
||||||
resp.g_FE_ServerPort = settings::SHARDPORT;
|
resp.g_FE_ServerPort = settings::SHARDPORT;
|
||||||
|
|
||||||
// pass player to CNSharedData
|
LoginMetadata *lm = new LoginMetadata();
|
||||||
Player passPlayer = {};
|
lm->FEKey = sock->getFEKey();
|
||||||
Database::getPlayer(&passPlayer, selection->iPC_UID);
|
lm->timestamp = getTime();
|
||||||
// this should never happen but for extra safety
|
lm->playerId = selection->iPC_UID;
|
||||||
if (passPlayer.iID == 0)
|
|
||||||
return invalidCharacter(sock);
|
|
||||||
|
|
||||||
passPlayer.FEKey = sock->getFEKey();
|
resp.iEnterSerialKey = Rand::cryptoRand();
|
||||||
resp.iEnterSerialKey = passPlayer.iID;
|
|
||||||
CNSharedData::setPlayer(resp.iEnterSerialKey, passPlayer);
|
// transfer ownership of connection data to CNShared
|
||||||
|
CNShared::storeLoginMetadata(resp.iEnterSerialKey, lm);
|
||||||
|
|
||||||
sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC);
|
sock->sendPacket(resp, P_LS2CL_REP_SHARD_SELECT_SUCC);
|
||||||
|
|
||||||
// update current slot in DB
|
// update current slot in DB
|
||||||
Database::updateSelected(loginSessions[sock].userID, passPlayer.slot);
|
Database::updateSelectedByPlayerId(loginSessions[sock].userID, selection->iPC_UID);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) {
|
void CNLoginServer::finishTutorial(CNSocket* sock, CNPacketData* data) {
|
||||||
|
@@ -16,6 +16,7 @@ std::map<uint32_t, PacketHandler> CNShardServer::ShardPackets;
|
|||||||
std::list<TimerEvent> CNShardServer::Timers;
|
std::list<TimerEvent> CNShardServer::Timers;
|
||||||
|
|
||||||
CNShardServer::CNShardServer(uint16_t p) {
|
CNShardServer::CNShardServer(uint16_t p) {
|
||||||
|
serverType = "shard";
|
||||||
port = p;
|
port = p;
|
||||||
pHandler = &CNShardServer::handlePacket;
|
pHandler = &CNShardServer::handlePacket;
|
||||||
REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
|
REGISTER_SHARD_TIMER(keepAliveTimer, 4000);
|
||||||
@@ -29,11 +30,31 @@ CNShardServer::CNShardServer(uint16_t p) {
|
|||||||
void CNShardServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
void CNShardServer::handlePacket(CNSocket* sock, CNPacketData* data) {
|
||||||
printPacket(data);
|
printPacket(data);
|
||||||
|
|
||||||
if (ShardPackets.find(data->type) != ShardPackets.end())
|
// if it's a valid packet
|
||||||
ShardPackets[data->type](sock, data);
|
if (ShardPackets.find(data->type) != ShardPackets.end()) {
|
||||||
else if (settings::VERBOSITY > 0)
|
|
||||||
std::cerr << "OpenFusion: SHARD UNIMPLM ERR. PacketType: " << Packets::p2str(data->type) << " (" << data->type << ")" << std::endl;
|
|
||||||
|
|
||||||
|
// reject gameplay packets if not yet fully connected
|
||||||
|
if (PlayerManager::players.find(sock) == PlayerManager::players.end()
|
||||||
|
&& data->type != P_CL2FE_REQ_PC_ENTER && data->type != P_CL2FE_REP_LIVE_CHECK) {
|
||||||
|
|
||||||
|
if (settings::VERBOSITY > 0) {
|
||||||
|
std::cerr << "OpenFusion: SHARD PKT OUT-OF-SEQ. PacketType: " <<
|
||||||
|
Packets::p2str(data->type) << " (" << data->type << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// run the appropriate packet handler
|
||||||
|
ShardPackets[data->type](sock, data);
|
||||||
|
} else if (settings::VERBOSITY > 0) {
|
||||||
|
std::cerr << "OpenFusion: SHARD UNIMPLM ERR. PacketType: " << Packets::p2str(data->type) << " (" << data->type << ")" << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We must re-check if the player is still connected in case
|
||||||
|
* they were dropped when handling the packet.
|
||||||
|
*/
|
||||||
if (PlayerManager::players.find(sock) != PlayerManager::players.end())
|
if (PlayerManager::players.find(sock) != PlayerManager::players.end())
|
||||||
PlayerManager::players[sock]->lastHeartbeat = getTime();
|
PlayerManager::players[sock]->lastHeartbeat = getTime();
|
||||||
}
|
}
|
||||||
@@ -79,14 +100,7 @@ void CNShardServer::_killConnection(CNSocket* cns) {
|
|||||||
if (PlayerManager::players.find(cns) == PlayerManager::players.end())
|
if (PlayerManager::players.find(cns) == PlayerManager::players.end())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
Player* plr = PlayerManager::getPlayer(cns);
|
|
||||||
|
|
||||||
int64_t key = plr->SerialKey;
|
|
||||||
|
|
||||||
PlayerManager::removePlayer(cns); // removes the player from the list and saves it to DB
|
PlayerManager::removePlayer(cns); // removes the player from the list and saves it to DB
|
||||||
|
|
||||||
// remove from CNSharedData
|
|
||||||
CNSharedData::erasePlayer(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNShardServer::killConnection(CNSocket *cns) {
|
void CNShardServer::killConnection(CNSocket *cns) {
|
||||||
@@ -102,6 +116,10 @@ void CNShardServer::kill() {
|
|||||||
void CNShardServer::onStep() {
|
void CNShardServer::onStep() {
|
||||||
time_t currTime = getTime();
|
time_t currTime = getTime();
|
||||||
|
|
||||||
|
// do not evaluate timers if the server is shutting down
|
||||||
|
if (!active)
|
||||||
|
return;
|
||||||
|
|
||||||
for (TimerEvent& event : Timers) {
|
for (TimerEvent& event : Timers) {
|
||||||
if (event.scheduledEvent == 0) {
|
if (event.scheduledEvent == 0) {
|
||||||
// event hasn't been queued yet, go ahead and do that
|
// event hasn't been queued yet, go ahead and do that
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Chat.hpp"
|
#include "Chat.hpp"
|
||||||
|
#include "Email.hpp"
|
||||||
#include "servers/Monitor.hpp"
|
#include "servers/Monitor.hpp"
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
@@ -38,9 +39,42 @@ static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The longest protocol message is for an email.
|
||||||
|
*
|
||||||
|
* message type + two formatted character names + the other formatting chars
|
||||||
|
* + the email title + the email body = ~1154
|
||||||
|
*
|
||||||
|
* The body can grow to twice its usual size (512) in the pathological case
|
||||||
|
* where every character is a newline.
|
||||||
|
*
|
||||||
|
* Multi-byte Unicode characters aren't a factor, as they should've been
|
||||||
|
* stripped out by sanitizeText().
|
||||||
|
*/
|
||||||
|
#define BUFSIZE 2048
|
||||||
|
|
||||||
|
static int process_email(char *buff, std::string email) {
|
||||||
|
strncpy(buff, "email ", 6);
|
||||||
|
int i = 6;
|
||||||
|
|
||||||
|
for (char c : email) {
|
||||||
|
if (i == BUFSIZE-2)
|
||||||
|
break;
|
||||||
|
|
||||||
|
buff[i++] = c;
|
||||||
|
|
||||||
|
// indent each line to prevent "endemail" spoofing
|
||||||
|
if (c == '\n')
|
||||||
|
buff[i++] = '\t';
|
||||||
|
}
|
||||||
|
|
||||||
|
buff[i++] = '\n';
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
static void tick(CNServer *serv, time_t delta) {
|
static void tick(CNServer *serv, time_t delta) {
|
||||||
std::lock_guard<std::mutex> lock(sockLock);
|
std::lock_guard<std::mutex> lock(sockLock);
|
||||||
char buff[256];
|
char buff[BUFSIZE];
|
||||||
int n;
|
int n;
|
||||||
|
|
||||||
auto it = sockets.begin();
|
auto it = sockets.begin();
|
||||||
@@ -70,6 +104,17 @@ outer:
|
|||||||
goto outer;
|
goto outer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// emails
|
||||||
|
for (auto& str : Email::dump) {
|
||||||
|
n = process_email(buff, str);
|
||||||
|
|
||||||
|
if (!transmit(it, buff, n))
|
||||||
|
goto outer;
|
||||||
|
|
||||||
|
if (!transmit(it, (char*)"endemail\n", 9))
|
||||||
|
goto outer;
|
||||||
|
}
|
||||||
|
|
||||||
if (!transmit(it, (char*)"end\n", 4))
|
if (!transmit(it, (char*)"end\n", 4))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@@ -77,6 +122,7 @@ outer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
Chat::dump.clear();
|
Chat::dump.clear();
|
||||||
|
Email::dump.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {
|
bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {
|
||||||
|
@@ -7,16 +7,20 @@
|
|||||||
|
|
||||||
// defaults :)
|
// defaults :)
|
||||||
int settings::VERBOSITY = 1;
|
int settings::VERBOSITY = 1;
|
||||||
|
bool settings::SANDBOX = true;
|
||||||
|
|
||||||
int settings::LOGINPORT = 23000;
|
int settings::LOGINPORT = 23000;
|
||||||
bool settings::APPROVEALLNAMES = true;
|
bool settings::APPROVEALLNAMES = true;
|
||||||
|
bool settings::AUTOCREATEACCOUNTS = true;
|
||||||
int settings::DBSAVEINTERVAL = 240;
|
int settings::DBSAVEINTERVAL = 240;
|
||||||
|
|
||||||
int settings::SHARDPORT = 23001;
|
int settings::SHARDPORT = 23001;
|
||||||
std::string settings::SHARDSERVERIP = "127.0.0.1";
|
std::string settings::SHARDSERVERIP = "127.0.0.1";
|
||||||
|
bool settings::LOCALHOSTWORKAROUND = true;
|
||||||
time_t settings::TIMEOUT = 60000;
|
time_t settings::TIMEOUT = 60000;
|
||||||
int settings::VIEWDISTANCE = 25600;
|
int settings::VIEWDISTANCE = 25600;
|
||||||
bool settings::SIMULATEMOBS = true;
|
bool settings::SIMULATEMOBS = true;
|
||||||
|
bool settings::ANTICHEAT = true;
|
||||||
|
|
||||||
// default spawn point
|
// default spawn point
|
||||||
#ifndef ACADEMY
|
#ifndef ACADEMY
|
||||||
@@ -74,12 +78,15 @@ void settings::init() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
APPROVEALLNAMES = reader.GetBoolean("", "acceptallcustomnames", APPROVEALLNAMES);
|
|
||||||
VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY);
|
VERBOSITY = reader.GetInteger("", "verbosity", VERBOSITY);
|
||||||
|
SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX);
|
||||||
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
|
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
|
||||||
SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT);
|
APPROVEALLNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVEALLNAMES);
|
||||||
|
AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS);
|
||||||
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);
|
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);
|
||||||
SHARDSERVERIP = reader.Get("shard", "ip", "127.0.0.1");
|
SHARDPORT = reader.GetInteger("shard", "port", SHARDPORT);
|
||||||
|
SHARDSERVERIP = reader.Get("shard", "ip", SHARDSERVERIP);
|
||||||
|
LOCALHOSTWORKAROUND = reader.GetBoolean("shard", "localhostworkaround", LOCALHOSTWORKAROUND);
|
||||||
TIMEOUT = reader.GetInteger("shard", "timeout", TIMEOUT);
|
TIMEOUT = reader.GetInteger("shard", "timeout", TIMEOUT);
|
||||||
VIEWDISTANCE = reader.GetInteger("shard", "viewdistance", VIEWDISTANCE);
|
VIEWDISTANCE = reader.GetInteger("shard", "viewdistance", VIEWDISTANCE);
|
||||||
SIMULATEMOBS = reader.GetBoolean("shard", "simulatemobs", SIMULATEMOBS);
|
SIMULATEMOBS = reader.GetBoolean("shard", "simulatemobs", SIMULATEMOBS);
|
||||||
@@ -102,6 +109,7 @@ void settings::init() {
|
|||||||
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
|
ACCLEVEL = reader.GetInteger("shard", "accountlevel", ACCLEVEL);
|
||||||
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
|
EVENTMODE = reader.GetInteger("shard", "eventmode", EVENTMODE);
|
||||||
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);
|
DISABLEFIRSTUSEFLAG = reader.GetBoolean("shard", "disablefirstuseflag", DISABLEFIRSTUSEFLAG);
|
||||||
|
ANTICHEAT = reader.GetBoolean("shard", "anticheat", ANTICHEAT);
|
||||||
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
|
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
|
||||||
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
|
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
|
||||||
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);
|
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);
|
||||||
|
@@ -2,11 +2,15 @@
|
|||||||
|
|
||||||
namespace settings {
|
namespace settings {
|
||||||
extern int VERBOSITY;
|
extern int VERBOSITY;
|
||||||
|
extern bool SANDBOX;
|
||||||
extern int LOGINPORT;
|
extern int LOGINPORT;
|
||||||
extern bool APPROVEALLNAMES;
|
extern bool APPROVEALLNAMES;
|
||||||
|
extern bool AUTOCREATEACCOUNTS;
|
||||||
extern int DBSAVEINTERVAL;
|
extern int DBSAVEINTERVAL;
|
||||||
extern int SHARDPORT;
|
extern int SHARDPORT;
|
||||||
extern std::string SHARDSERVERIP;
|
extern std::string SHARDSERVERIP;
|
||||||
|
extern bool LOCALHOSTWORKAROUND;
|
||||||
|
extern bool ANTICHEAT;
|
||||||
extern time_t TIMEOUT;
|
extern time_t TIMEOUT;
|
||||||
extern int VIEWDISTANCE;
|
extern int VIEWDISTANCE;
|
||||||
extern bool SIMULATEMOBS;
|
extern bool SIMULATEMOBS;
|
||||||
|
2
tdata
2
tdata
Submodule tdata updated: 8230fb8649...cc65dbb402
2
vendor/bcrypt/crypt_blowfish.c
vendored
2
vendor/bcrypt/crypt_blowfish.c
vendored
@@ -58,7 +58,7 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef __i386__
|
#ifdef __i386__
|
||||||
#define BF_ASM 1
|
#define BF_ASM 0
|
||||||
#define BF_SCALE 1
|
#define BF_SCALE 1
|
||||||
#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__)
|
#elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__)
|
||||||
#define BF_ASM 0
|
#define BF_ASM 0
|
||||||
|
Reference in New Issue
Block a user