4 Commits

Author SHA1 Message Date
6a0d8ca436 [sandbox] Print error message on seccomp sandbox violation
Co-authored-by: cpunch <sethtstubbs@gmail.com>
2024-10-13 20:09:22 +02:00
0e32a8974f Add make target for building without Landlock 2024-10-13 03:30:53 +02:00
c196171034 [sandbox] Add backwards compatibility support for Landlock
* Support disabling Landlock at compile time or runtime if unsupported,
  without disabling seccomp
* Support older Landlock ABI versions
* Support an extra arbitrary RW path, inteded for the core dump dir
* Support database locations other than the working directory
2024-10-13 03:30:53 +02:00
8137921154 [sandbox] Initial Landlock support 2024-10-12 17:49:16 +02:00
47 changed files with 283 additions and 842 deletions

View File

@@ -8,7 +8,6 @@ on:
- .github/workflows/check-builds.yaml - .github/workflows/check-builds.yaml
- CMakeLists.txt - CMakeLists.txt
- Makefile - Makefile
- tdata
pull_request: pull_request:
types: [opened, reopened, synchronize, ready_for_review] types: [opened, reopened, synchronize, ready_for_review]
paths: paths:
@@ -20,7 +19,7 @@ on:
jobs: jobs:
ubuntu-build: ubuntu-build:
runs-on: ubuntu-22.04 runs-on: ubuntu-latest
steps: steps:
- name: Set environment - name: Set environment
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
@@ -30,7 +29,7 @@ jobs:
submodules: recursive submodules: recursive
fetch-depth: 0 fetch-depth: 0
- name: Install dependencies - name: Install dependencies
run: sudo apt install clang cmake snap libsqlite3-dev -y && sudo snap install powershell --classic run: sudo apt install clang cmake snap -y && sudo snap install powershell --classic
- name: Check compilation - name: Check compilation
run: | run: |
$versions = "104", "728", "1013" $versions = "104", "728", "1013"
@@ -49,7 +48,6 @@ jobs:
Rename-Item -Path "bin/fusion" -newName "$version-fusion" Rename-Item -Path "bin/fusion" -newName "$version-fusion"
Write-Output "Built version $version" Write-Output "Built version $version"
} }
Copy-Item -Path "tdata" -Destination "bin/tdata" -Recurse
Copy-Item -Path "sql" -Destination "bin/sql" -Recurse Copy-Item -Path "sql" -Destination "bin/sql" -Recurse
Copy-Item -Path "config.ini" -Destination "bin" Copy-Item -Path "config.ini" -Destination "bin"
shell: pwsh shell: pwsh
@@ -60,7 +58,7 @@ jobs:
path: bin path: bin
windows-build: windows-build:
runs-on: windows-2022 runs-on: windows-2019
steps: steps:
- name: Set environment - name: Set environment
run: $s = $env:GITHUB_SHA.subString(0, 7); echo "SHORT_SHA=$s" >> $env:GITHUB_ENV run: $s = $env:GITHUB_SHA.subString(0, 7); echo "SHORT_SHA=$s" >> $env:GITHUB_ENV
@@ -75,7 +73,7 @@ jobs:
$configurations = "Release" $configurations = "Release"
# "Debug" builds are disabled, since we don't really need them # "Debug" builds are disabled, since we don't really need them
$vsPath = "C:\Program Files\Microsoft Visual Studio\2022\Enterprise" $vsPath = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise"
Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" Import-Module "$vsPath\Common7\Tools\Microsoft.VisualStudio.DevShell.dll"
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation
@@ -102,7 +100,6 @@ jobs:
} }
Rename-Item -Path "bin/$configuration" -newName "$version-$configuration" Rename-Item -Path "bin/$configuration" -newName "$version-$configuration"
Write-Output "Built version $version $configuration" Write-Output "Built version $version $configuration"
Copy-Item -Path "tdata" -Destination "bin/$version-$configuration/tdata" -Recurse
Copy-Item -Path "sql" -Destination "bin/$version-$configuration/sql" -Recurse Copy-Item -Path "sql" -Destination "bin/$version-$configuration/sql" -Recurse
Copy-Item -Path "config.ini" -Destination "bin/$version-$configuration" Copy-Item -Path "config.ini" -Destination "bin/$version-$configuration"
} }
@@ -111,12 +108,12 @@ jobs:
- name: Upload build artifact - name: Upload build artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: 'windows-vs2022-bin-x64-${{ env.SHORT_SHA }}' name: 'windows-vs2019-bin-x64-${{ env.SHORT_SHA }}'
path: bin path: bin
copy-artifacts: copy-artifacts:
if: github.event_name != 'pull_request' && (github.ref_type == 'tag' || github.ref_name == 'master') if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
runs-on: ubuntu-latest runs-on: ubuntu-22.04
needs: [windows-build, ubuntu-build] needs: [windows-build, ubuntu-build]
env: env:
BOT_SSH_KEY: ${{ secrets.BOT_SSH_KEY }} BOT_SSH_KEY: ${{ secrets.BOT_SSH_KEY }}
@@ -124,6 +121,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: recursive
fetch-depth: 0 fetch-depth: 0
- run: | - run: |
GITDESC=$(git describe --tags) GITDESC=$(git describe --tags)

View File

@@ -11,6 +11,11 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
contents: read contents: read
strategy:
matrix:
platforms:
- linux/amd64
- linux/arm64
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Retrieve major version - name: Retrieve major version
@@ -26,13 +31,11 @@ jobs:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push the Docker image - name: Build and push the Docker image
uses: docker/build-push-action@v6 uses: docker/build-push-action@v5
with: with:
context: . context: .
file: ./Dockerfile file: ./Dockerfile
platforms: linux/amd64,linux/arm64 platforms: ${{ matrix.platforms }}
push: true push: true
tags: ${{ secrets.DOCKERHUB_REPOSITORY }}:${{ github.ref_name }},${{ secrets.DOCKERHUB_REPOSITORY }}:${{ steps.split.outputs._0 }},${{ secrets.DOCKERHUB_REPOSITORY }}:latest tags: ${{ secrets.DOCKERHUB_REPOSITORY }}:${{ github.ref_name }},${{ secrets.DOCKERHUB_REPOSITORY }}:${{ steps.split.outputs._0 }},${{ secrets.DOCKERHUB_REPOSITORY }}:latest

View File

@@ -1,41 +1,32 @@
# build # build
FROM alpine:3 as build FROM debian:stable-slim as build
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN apk update && apk upgrade && apk add \ RUN apt-get -y update && apt-get install -y \
linux-headers \
git \ git \
clang18 \ clang \
make \ make \
sqlite-dev libsqlite3-dev
COPY src ./src COPY src ./src
COPY vendor ./vendor COPY vendor ./vendor
COPY .git ./.git COPY .git ./.git
COPY Makefile CMakeLists.txt version.h.in ./ COPY Makefile CMakeLists.txt version.h.in ./
RUN sed -i 's/^CC=clang$/&-18/' Makefile RUN make -j8
RUN sed -i 's/^CXX=clang++$/&-18/' Makefile
RUN make nosandbox -j$(nproc)
# prod # prod
FROM alpine:3 FROM debian:stable-slim
WORKDIR /usr/src/app WORKDIR /usr/src/app
RUN apk update && apk upgrade && apk add \ RUN apt-get -y update && apt-get install -y \
libstdc++ \ libsqlite3-dev
sqlite-dev
COPY --from=build /usr/src/app/bin/fusion /bin/fusion COPY --from=build /usr/src/app/bin/fusion /bin/fusion
COPY sql ./sql COPY sql ./sql
CMD ["/bin/fusion"] CMD ["/bin/fusion"]
EXPOSE 23000/tcp LABEL Name=openfusion Version=0.0.2
EXPOSE 23001/tcp
EXPOSE 8003/tcp
LABEL Name=openfusion Version=2.0.0

View File

@@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2020-2025 OpenFusion Contributors Copyright (c) 2020-2024 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

View File

@@ -115,7 +115,6 @@ CXXHDR=\
src/settings.hpp\ src/settings.hpp\
src/Transport.hpp\ src/Transport.hpp\
src/TableData.hpp\ src/TableData.hpp\
src/Bucket.hpp\
src/Chunking.hpp\ src/Chunking.hpp\
src/Buddies.hpp\ src/Buddies.hpp\
src/Groups.hpp\ src/Groups.hpp\

View File

@@ -14,22 +14,22 @@ OpenFusion is a reverse-engineered server for FusionFall. It primarily targets v
### Getting Started ### Getting Started
#### Method A: Installer (Easiest) #### Method A: Installer (Easiest)
1. Download the launcher installer by clicking [here](https://github.com/OpenFusionProject/OpenFusionLauncher/releases/latest/download/OpenFusionLauncher-Windows-Installer.exe) - choose to run the file. 1. Download the client installer by clicking [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.6/OpenFusionClient-1.6-Installer.exe) - choose to run the file.
2. After a few moments, the launcher should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect. 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. 3. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
4. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 3. 4. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 3.
#### Method B: Standalone .zip file #### Method B: Standalone .zip file
1. Download the launcher from [here](https://github.com/OpenFusionProject/OpenFusionLauncher/releases/latest/download/OpenFusionLauncher-Windows-Portable.zip). 1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.6/OpenFusionClient-1.6.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 OpenFusionLauncher.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://openfusion.dev/docs/guides/running-on-linux/). Instructions for getting the client to run on Linux through Wine can be found [here](https://openfusion.dev/docs/guides/running-on-linux/).
### Hosting a server ### Hosting a server
1. Grab `OpenFusionServer-Windows-Original.zip` or `OpenFusionServer-Windows-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/latest). 1. Grab `OpenFusionServer-1.6-Original.zip` or `OpenFusionServer-1.6-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.6).
2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server. 2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server.
3. Add a new server to the client's list: 3. Add a new server to the client's list:
1. For Description, enter anything you want. This is what will show up in the server list. 1. For Description, enter anything you want. This is what will show up in the server list.

View File

@@ -12,16 +12,14 @@ sandbox=true
[login] [login]
# must be kept in sync with loginInfo.php # must be kept in sync with loginInfo.php
port=23000 port=23000
# will all name wheel names be approved instantly?
acceptallwheelnames=true
# will all custom names be approved instantly? # will all custom names be approved instantly?
acceptallcustomnames=true acceptallcustomnames=true
# should attempts to log into non-existent accounts # should attempts to log into non-existent accounts
# automatically create them? # automatically create them?
autocreateaccounts=true autocreateaccounts=true
# list of supported authentication methods (comma-separated) # list of supported authentication methods (comma-separated)
# password = allow logging in with plaintext passwords # password = allow login type 1 with plaintext passwords
# cookie = allow logging in with one-shot auth cookies # cookie = allow login type 2 with one-shot auth cookies
authmethods=password authmethods=password
# how often should everything be flushed to the database? # how often should everything be flushed to the database?
# the default is 4 minutes # the default is 4 minutes
@@ -104,8 +102,5 @@ eventmode=0
enabled=false enabled=false
# the port to listen for connections on # the port to listen for connections on
port=8003 port=8003
# The local IP to listen on.
# Do not change this unless you know what you're doing.
listenip=127.0.0.1
# how often the listeners should be updated (in milliseconds) # how often the listeners should be updated (in milliseconds)
interval=5000 interval=5000

View File

@@ -1,9 +1,11 @@
version: '3.4'
services: services:
openfusion: openfusion:
image: openfusion
build: build:
context: . context: .
dockerfile: ./Dockerfile dockerfile: ./Dockerfile
image: openfusion/openfusion:latest
volumes: volumes:
- ./config.ini:/usr/src/app/config.ini - ./config.ini:/usr/src/app/config.ini
- ./database.db:/usr/src/app/database.db - ./database.db:/usr/src/app/database.db

View File

@@ -1,8 +0,0 @@
BEGIN TRANSACTION;
-- New Columns
ALTER TABLE Accounts ADD Email TEXT DEFAULT '' NOT NULL;
ALTER TABLE Accounts ADD LastPasswordReset INTEGER DEFAULT 0 NOT NULL;
-- Update DB Version
UPDATE Meta SET Value = 6 WHERE Key = 'DatabaseVersion';
UPDATE Meta SET Value = strftime('%s', 'now') WHERE Key = 'LastMigration';
COMMIT;

View File

@@ -1,16 +1,14 @@
CREATE TABLE IF NOT EXISTS Accounts ( CREATE TABLE IF NOT EXISTS Accounts (
AccountID INTEGER NOT NULL, AccountID INTEGER NOT NULL,
Login TEXT NOT NULL UNIQUE COLLATE NOCASE, Login TEXT NOT NULL UNIQUE COLLATE NOCASE,
Password TEXT NOT NULL, Password TEXT NOT NULL,
Selected INTEGER DEFAULT 1 NOT NULL, Selected INTEGER DEFAULT 1 NOT NULL,
AccountLevel INTEGER NOT NULL, AccountLevel INTEGER NOT NULL,
Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL, Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL, LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
BannedUntil INTEGER DEFAULT 0 NOT NULL, BannedUntil INTEGER DEFAULT 0 NOT NULL,
BannedSince INTEGER DEFAULT 0 NOT NULL, BannedSince INTEGER DEFAULT 0 NOT NULL,
BanReason TEXT DEFAULT '' NOT NULL, BanReason TEXT DEFAULT '' NOT NULL,
Email TEXT DEFAULT '' NOT NULL,
LastPasswordReset INTEGER DEFAULT 0 NOT NULL,
PRIMARY KEY(AccountID AUTOINCREMENT) PRIMARY KEY(AccountID AUTOINCREMENT)
); );

View File

@@ -334,8 +334,8 @@ void Abilities::useNanoSkill(CNSocket* sock, SkillData* skill, sNano& nano, std:
size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC); size_t resplen = sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC);
for(SkillResult& sr : results) for(SkillResult& sr : results)
resplen += sr.size; resplen += sr.size;
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
sP_FE2CL_NANO_SKILL_USE_SUCC* pkt = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf; sP_FE2CL_NANO_SKILL_USE_SUCC* pkt = (sP_FE2CL_NANO_SKILL_USE_SUCC*)respbuf;
pkt->iPC_ID = plr->iID; pkt->iPC_ID = plr->iID;
@@ -379,8 +379,8 @@ void Abilities::useNPCSkill(EntityRef npc, int skillID, std::vector<ICombatant*>
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT); size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT);
for(SkillResult& sr : results) for(SkillResult& sr : results)
resplen += sr.size; resplen += sr.size;
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
pkt->iNPC_ID = npc.id; pkt->iNPC_ID = npc.id;

View File

@@ -1,40 +0,0 @@
#pragma once
#include <array>
#include <optional>
#include <assert.h>
template<class T, size_t N>
class Bucket {
std::array<T, N> buf;
size_t sz;
public:
Bucket() {
sz = 0;
}
void add(const T& item) {
assert(sz < N);
buf[sz++] = item;
}
std::optional<T> get(size_t idx) const {
if (idx < sz) {
return buf[idx];
}
return std::nullopt;
}
size_t size() const {
return sz;
}
bool isFull() const {
return sz == N;
}
void clear() {
sz = 0;
}
};

View File

@@ -41,9 +41,9 @@ void Buddies::sendBuddyList(CNSocket* sock) {
// initialize response struct // initialize response struct
size_t resplen = sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC) + buddyCnt * sizeof(sBuddyBaseInfo); size_t resplen = sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC) + buddyCnt * sizeof(sBuddyBaseInfo);
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC* resp = (sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC*)respbuf; sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC* resp = (sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC*)respbuf;
sBuddyBaseInfo* respdata = (sBuddyBaseInfo*)(respbuf + sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC)); sBuddyBaseInfo* respdata = (sBuddyBaseInfo*)(respbuf + sizeof(sP_FE2CL_REP_PC_BUDDYLIST_INFO_SUCC));

View File

@@ -178,9 +178,9 @@ void Buffs::tickDrain(EntityRef self, Buff* buff, int mult) {
int dealt = combatant->takeDamage(buff->getLastSource(), damage); int dealt = combatant->takeDamage(buff->getLastSource(), damage);
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage); size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_Damage);
assert(resplen < CN_PACKET_BODY_SIZE); assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
pkt->iID = self.id; pkt->iID = self.id;

View File

@@ -1,7 +1,6 @@
#include "Chat.hpp" #include "Chat.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "servers/Monitor.hpp"
#include "Player.hpp" #include "Player.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
@@ -9,6 +8,8 @@
#include <assert.h> #include <assert.h>
std::vector<std::string> Chat::dump;
using namespace Chat; using namespace Chat;
static void chatHandler(CNSocket* sock, CNPacketData* data) { static void chatHandler(CNSocket* sock, CNPacketData* data) {
@@ -27,7 +28,7 @@ static void chatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[FreeChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; std::string logLine = "[FreeChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
// send to client // send to client
INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp);
@@ -50,7 +51,7 @@ static void menuChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
// send to client // send to client
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp);
@@ -102,14 +103,14 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) {
memcpy(msg.szAnnounceMsg, announcement->szAnnounceMsg, sizeof(msg.szAnnounceMsg)); memcpy(msg.szAnnounceMsg, announcement->szAnnounceMsg, sizeof(msg.szAnnounceMsg));
std::map<CNSocket*, Player*>::iterator it; std::map<CNSocket*, Player*>::iterator it;
// This value is completely arbitrary, but these make the most sense when you consider the architecture of the game
switch (announcement->iAreaType) { switch (announcement->iAreaType) {
case 0: // area (all players in viewable chunks) case 0: // area (all players in viewable chunks)
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE); sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE); PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
break; break;
case 1: // channel case 1: // shard
case 2: // shard case 2: // world
break; // not applicable to OpenFusion
case 3: // global (all players) case 3: // global (all players)
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) { for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) {
CNSocket* allSock = it->first; CNSocket* allSock = it->first;
@@ -119,12 +120,9 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) {
break; break;
} }
std::string logLine = std::to_string(announcement->iAreaType) + " " std::string logLine = "[Bcast " + std::to_string(announcement->iAreaType) + "] " + PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg);
+ std::to_string(announcement->iAnnounceType) + " " std::cout << logLine << std::endl;
+ std::to_string(announcement->iDuringTime) + " " dump.push_back("**" + logLine + "**");
+ PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg);
std::cout << "Broadcast " << logLine << std::endl;
Monitor::bcasts.push_back(logLine);
} }
// Buddy freechatting // Buddy freechatting
@@ -157,7 +155,7 @@ static void buddyChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat)); U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
@@ -187,7 +185,7 @@ static void buddyMenuChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[BuddyMenuChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; std::string logLine = "[BuddyMenuChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat)); U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
@@ -220,7 +218,7 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[TradeChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat; std::string logLine = "[TradeChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
resp.iEmoteCode = pacdat->iEmoteCode; resp.iEmoteCode = pacdat->iEmoteCode;
sock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT); sock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT);
@@ -243,7 +241,7 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[GroupChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; std::string logLine = "[GroupChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
// send to client // send to client
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp);
@@ -266,7 +264,7 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat; std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
std::cout << logLine << std::endl; std::cout << logLine << std::endl;
Monitor::chats.push_back(logLine); dump.push_back(logLine);
// send to client // send to client
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp); INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);

View File

@@ -8,6 +8,7 @@
#include <vector> #include <vector>
namespace Chat { namespace Chat {
extern std::vector<std::string> dump;
void init(); void init();
void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD

View File

@@ -1,9 +1,7 @@
#include "Chunking.hpp" #include "Chunking.hpp"
#include "Player.hpp"
#include "MobAI.hpp" #include "MobAI.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Bucket.hpp"
#include <assert.h> #include <assert.h>
@@ -13,12 +11,6 @@ using namespace Chunking;
* The initial chunkPos value before a player is placed into the world. * The initial chunkPos value before a player is placed into the world.
*/ */
const ChunkPos Chunking::INVALID_CHUNK = {}; const ChunkPos Chunking::INVALID_CHUNK = {};
constexpr size_t MAX_PC_PER_AROUND = (CN_PACKET_BODY_SIZE - sizeof(int32_t)) / sizeof(sPCAppearanceData);
constexpr size_t MAX_NPC_PER_AROUND = (CN_PACKET_BODY_SIZE - sizeof(int32_t)) / sizeof(sNPCAppearanceData);
constexpr size_t MAX_SHINY_PER_AROUND = (CN_PACKET_BODY_SIZE - sizeof(int32_t)) / sizeof(sShinyAppearanceData);
constexpr size_t MAX_TRANSPORTATION_PER_AROUND = (CN_PACKET_BODY_SIZE - sizeof(int32_t)) / sizeof(sTransportationAppearanceData);
constexpr size_t MAX_IDS_PER_AROUND_DEL = (CN_PACKET_BODY_SIZE - sizeof(int32_t)) / sizeof(int32_t);
constexpr size_t MAX_TRANSPORTATION_IDS_PER_AROUND_DEL = MAX_IDS_PER_AROUND_DEL - 1; // 1 less for eTT
std::map<ChunkPos, Chunk*> Chunking::chunks; std::map<ChunkPos, Chunk*> Chunking::chunks;
@@ -83,80 +75,11 @@ void Chunking::untrackEntity(ChunkPos chunkPos, const EntityRef ref) {
deleteChunk(chunkPos); deleteChunk(chunkPos);
} }
template<class T, size_t N>
static void sendAroundPackets(const EntityRef recipient, std::vector<Bucket<T, N>>& buckets, uint32_t packetId) {
assert(recipient.kind == EntityKind::PLAYER);
uint8_t pktBuf[CN_PACKET_BODY_SIZE];
for (const auto& bucket : buckets) {
memset(pktBuf, 0, CN_PACKET_BODY_SIZE);
int count = bucket.size();
*((int32_t*)pktBuf) = count;
T* data = (T*)(pktBuf + sizeof(int32_t));
for (size_t i = 0; i < count; i++) {
data[i] = bucket.get(i).value();
}
recipient.sock->sendPacket(pktBuf, packetId, sizeof(int32_t) + (count * sizeof(T)));
}
}
template<size_t N>
static void sendAroundDelPackets(const EntityRef recipient, std::vector<Bucket<int32_t, N>>& buckets, uint32_t packetId) {
assert(recipient.kind == EntityKind::PLAYER);
uint8_t pktBuf[CN_PACKET_BODY_SIZE];
for (const auto& bucket : buckets) {
memset(pktBuf, 0, CN_PACKET_BODY_SIZE);
int count = bucket.size();
assert(count <= N);
size_t baseSize;
if (packetId == P_FE2CL_AROUND_DEL_TRANSPORTATION) {
sP_FE2CL_AROUND_DEL_TRANSPORTATION* pkt = (sP_FE2CL_AROUND_DEL_TRANSPORTATION*)pktBuf;
pkt->eTT = 3;
pkt->iCnt = count;
baseSize = sizeof(sP_FE2CL_AROUND_DEL_TRANSPORTATION);
} else {
*((int32_t*)pktBuf) = count;
baseSize = sizeof(int32_t);
}
int32_t* ids = (int32_t*)(pktBuf + baseSize);
for (size_t i = 0; i < count; i++) {
ids[i] = bucket.get(i).value();
}
recipient.sock->sendPacket(pktBuf, packetId, baseSize + (count * sizeof(int32_t)));
}
}
template<class T, size_t N>
static void bufferAppearanceData(std::vector<Bucket<T, N>>& buckets, const T& data) {
if (buckets.empty())
buckets.push_back({});
auto& bucket = buckets[buckets.size() - 1];
bucket.add(data);
if (bucket.isFull())
buckets.push_back({});
}
template<size_t N>
static void bufferIdForDisappearance(std::vector<Bucket<int32_t, N>>& buckets, int32_t id) {
if (buckets.empty())
buckets.push_back({});
auto& bucket = buckets[buckets.size() - 1];
bucket.add(id);
if (bucket.isFull())
buckets.push_back({});
}
void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) { void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) {
Entity *ent = ref.getEntity(); Entity *ent = ref.getEntity();
bool alive = ent->isExtant(); bool alive = ent->isExtant();
std::vector<Bucket<sPCAppearanceData, MAX_PC_PER_AROUND>> pcAppearances; // TODO: maybe optimize this, potentially using AROUND packets?
std::vector<Bucket<sNPCAppearanceData, MAX_NPC_PER_AROUND>> npcAppearances;
std::vector<Bucket<sShinyAppearanceData, MAX_SHINY_PER_AROUND>> shinyAppearances;
std::vector<Bucket<sTransportationAppearanceData, MAX_TRANSPORTATION_PER_AROUND>> transportationAppearances;
for (Chunk *chunk : chnks) { for (Chunk *chunk : chnks) {
for (const EntityRef otherRef : chunk->entities) { for (const EntityRef otherRef : chunk->entities) {
// skip oneself // skip oneself
@@ -172,38 +95,7 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) {
// notify this *player* of the existence of all visible Entities // notify this *player* of the existence of all visible Entities
if (ref.kind == EntityKind::PLAYER && other->isExtant()) { if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
sPCAppearanceData pcData; other->enterIntoViewOf(ref.sock);
sNPCAppearanceData npcData;
sShinyAppearanceData eggData;
sTransportationAppearanceData busData;
switch(otherRef.kind) {
case EntityKind::PLAYER:
pcData = dynamic_cast<Player*>(other)->getAppearanceData();
bufferAppearanceData(pcAppearances, pcData);
break;
case EntityKind::SIMPLE_NPC:
npcData = dynamic_cast<BaseNPC*>(other)->getAppearanceData();
bufferAppearanceData(npcAppearances, npcData);
break;
case EntityKind::COMBAT_NPC:
npcData = dynamic_cast<CombatNPC*>(other)->getAppearanceData();
bufferAppearanceData(npcAppearances, npcData);
break;
case EntityKind::MOB:
npcData = dynamic_cast<Mob*>(other)->getAppearanceData();
bufferAppearanceData(npcAppearances, npcData);
break;
case EntityKind::EGG:
eggData = dynamic_cast<Egg*>(other)->getShinyAppearanceData();
bufferAppearanceData(shinyAppearances, eggData);
break;
case EntityKind::BUS:
busData = dynamic_cast<Bus*>(other)->getTransportationAppearanceData();
bufferAppearanceData(transportationAppearances, busData);
break;
default:
break;
}
} }
// for mobs, increment playersInView // for mobs, increment playersInView
@@ -213,27 +105,13 @@ void Chunking::addEntityToChunks(std::set<Chunk*> chnks, const EntityRef ref) {
((Mob*)other)->playersInView++; ((Mob*)other)->playersInView++;
} }
} }
if (ref.kind == EntityKind::PLAYER) {
if (!pcAppearances.empty())
sendAroundPackets(ref, pcAppearances, P_FE2CL_PC_AROUND);
if (!npcAppearances.empty())
sendAroundPackets(ref, npcAppearances, P_FE2CL_NPC_AROUND);
if (!shinyAppearances.empty())
sendAroundPackets(ref, shinyAppearances, P_FE2CL_SHINY_AROUND);
if (!transportationAppearances.empty())
sendAroundPackets(ref, transportationAppearances, P_FE2CL_TRANSPORTATION_AROUND);
}
} }
void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref) { void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef ref) {
Entity *ent = ref.getEntity(); Entity *ent = ref.getEntity();
bool alive = ent->isExtant(); bool alive = ent->isExtant();
std::vector<Bucket<int32_t, MAX_IDS_PER_AROUND_DEL>> pcDisappearances; // TODO: same as above
std::vector<Bucket<int32_t, MAX_IDS_PER_AROUND_DEL>> npcDisappearances;
std::vector<Bucket<int32_t, MAX_IDS_PER_AROUND_DEL>> shinyDisappearances;
std::vector<Bucket<int32_t, MAX_TRANSPORTATION_IDS_PER_AROUND_DEL>> transportationDisappearances;
for (Chunk *chunk : chnks) { for (Chunk *chunk : chnks) {
for (const EntityRef otherRef : chunk->entities) { for (const EntityRef otherRef : chunk->entities) {
// skip oneself // skip oneself
@@ -249,29 +127,7 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef re
// notify this *player* of the departure of all visible Entities // notify this *player* of the departure of all visible Entities
if (ref.kind == EntityKind::PLAYER && other->isExtant()) { if (ref.kind == EntityKind::PLAYER && other->isExtant()) {
int32_t id; other->disappearFromViewOf(ref.sock);
switch(otherRef.kind) {
case EntityKind::PLAYER:
id = dynamic_cast<Player*>(other)->iID;
bufferIdForDisappearance(pcDisappearances, id);
break;
case EntityKind::SIMPLE_NPC:
case EntityKind::COMBAT_NPC:
case EntityKind::MOB:
id = dynamic_cast<BaseNPC*>(other)->id;
bufferIdForDisappearance(npcDisappearances, id);
break;
case EntityKind::EGG:
id = dynamic_cast<Egg*>(other)->id;
bufferIdForDisappearance(shinyDisappearances, id);
break;
case EntityKind::BUS:
id = dynamic_cast<Bus*>(other)->id;
bufferIdForDisappearance(transportationDisappearances, id);
break;
default:
break;
}
} }
// for mobs, decrement playersInView // for mobs, decrement playersInView
@@ -281,17 +137,6 @@ void Chunking::removeEntityFromChunks(std::set<Chunk*> chnks, const EntityRef re
((Mob*)other)->playersInView--; ((Mob*)other)->playersInView--;
} }
} }
if (ref.kind == EntityKind::PLAYER) {
if (!pcDisappearances.empty())
sendAroundDelPackets(ref, pcDisappearances, P_FE2CL_AROUND_DEL_PC);
if (!npcDisappearances.empty())
sendAroundDelPackets(ref, npcDisappearances, P_FE2CL_AROUND_DEL_NPC);
if (!shinyDisappearances.empty())
sendAroundDelPackets(ref, shinyDisappearances, P_FE2CL_AROUND_DEL_SHINY);
if (!transportationDisappearances.empty())
sendAroundDelPackets(ref, transportationDisappearances, P_FE2CL_AROUND_DEL_TRANSPORTATION);
}
} }
static void emptyChunk(ChunkPos chunkPos) { static void emptyChunk(ChunkPos chunkPos) {

View File

@@ -539,9 +539,9 @@ static void dealGooDamage(CNSocket *sock) {
return; // ignore completely return; // ignore completely
size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage); size_t resplen = sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK) + sizeof(sSkillResult_DotDamage);
assert(resplen < CN_PACKET_BODY_SIZE); assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf; sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK *pkt = (sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK*)respbuf;
sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK)); sSkillResult_DotDamage *dmg = (sSkillResult_DotDamage*)(respbuf + sizeof(sP_FE2CL_CHAR_TIME_BUFF_TIME_TICK));
@@ -633,9 +633,9 @@ static void pcAttackChars(CNSocket *sock, CNPacketData *data) {
// initialize response struct // initialize response struct
size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC) + pkt->iTargetCnt * sizeof(sAttackResult); size_t resplen = sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC) + pkt->iTargetCnt * sizeof(sAttackResult);
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
sP_FE2CL_PC_ATTACK_CHARs_SUCC *resp = (sP_FE2CL_PC_ATTACK_CHARs_SUCC*)respbuf; sP_FE2CL_PC_ATTACK_CHARs_SUCC *resp = (sP_FE2CL_PC_ATTACK_CHARs_SUCC*)respbuf;
sAttackResult *respdata = (sAttackResult*)(respbuf+sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC)); sAttackResult *respdata = (sAttackResult*)(respbuf+sizeof(sP_FE2CL_PC_ATTACK_CHARs_SUCC));
@@ -847,9 +847,9 @@ static void projectileHit(CNSocket* sock, CNPacketData* data) {
*/ */
size_t resplen = sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT) + pkt->iTargetCnt * sizeof(sAttackResult); size_t resplen = sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT) + pkt->iTargetCnt * sizeof(sAttackResult);
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
sP_FE2CL_PC_GRENADE_STYLE_HIT* resp = (sP_FE2CL_PC_GRENADE_STYLE_HIT*)respbuf; sP_FE2CL_PC_GRENADE_STYLE_HIT* resp = (sP_FE2CL_PC_GRENADE_STYLE_HIT*)respbuf;
sAttackResult* respdata = (sAttackResult*)(respbuf + sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT)); sAttackResult* respdata = (sAttackResult*)(respbuf + sizeof(sP_FE2CL_PC_GRENADE_STYLE_HIT));

View File

@@ -87,8 +87,8 @@ void Eggs::eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration) {
// initialize response struct // initialize response struct
size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size; size_t resplen = sizeof(sP_FE2CL_NPC_SKILL_HIT) + result.size;
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf; sP_FE2CL_NPC_SKILL_HIT* pkt = (sP_FE2CL_NPC_SKILL_HIT*)respbuf;
pkt->iNPC_ID = eggId; pkt->iNPC_ID = eggId;
@@ -126,6 +126,15 @@ static void eggStep(CNServer* serv, time_t currTime) {
} }
void Eggs::npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg) {
egg->iX = x;
egg->iY = y;
egg->iZ = z;
// client doesn't care about egg->iMapNum
egg->iShinyType = npc->iNPCType;
egg->iShiny_ID = npc->iNPC_ID;
}
static void eggPickup(CNSocket* sock, CNPacketData* data) { static void eggPickup(CNSocket* sock, CNPacketData* data) {
auto pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf; auto pickup = (sP_CL2FE_REQ_SHINY_PICKUP*)data->buf;
Player* plr = PlayerManager::getPlayer(sock); Player* plr = PlayerManager::getPlayer(sock);
@@ -183,7 +192,7 @@ static void eggPickup(CNSocket* sock, CNPacketData* data) {
// drop // drop
if (type->dropCrateId != 0) { if (type->dropCrateId != 0) {
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
assert(resplen < CN_PACKET_BODY_SIZE); assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
// we know it's only one trailing struct, so we can skip full validation // we know it's only one trailing struct, so we can skip full validation
uint8_t respbuf[resplen]; // not a variable length array, don't worry uint8_t respbuf[resplen]; // not a variable length array, don't worry

View File

@@ -15,4 +15,5 @@ namespace Eggs {
void init(); void init();
void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration); void eggBuffPlayer(CNSocket* sock, int skillId, int eggId, int duration);
void npcDataToEggData(int x, int y, int z, sNPCAppearanceData* npc, sShinyAppearanceData* egg);
} }

View File

@@ -3,7 +3,6 @@
#include "core/Core.hpp" #include "core/Core.hpp"
#include "db/Database.hpp" #include "db/Database.hpp"
#include "servers/CNShardServer.hpp" #include "servers/CNShardServer.hpp"
#include "servers/Monitor.hpp"
#include "PlayerManager.hpp" #include "PlayerManager.hpp"
#include "Items.hpp" #include "Items.hpp"
@@ -11,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);
@@ -323,7 +324,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody; std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody;
std::cout << logEmail << std::endl; std::cout << logEmail << std::endl;
Monitor::emails.push_back(logEmail); dump.push_back(logEmail);
// notification to recipient if online // notification to recipient if online
CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID); CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID);

View File

@@ -4,5 +4,7 @@
#include <string> #include <string>
namespace Email { namespace Email {
extern std::vector<std::string> dump;
void init(); void init();
} }

View File

@@ -70,7 +70,11 @@ void Bus::enterIntoViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, pkt); INITSTRUCT(sP_FE2CL_TRANSPORTATION_ENTER, pkt);
// TODO: Potentially decouple this from BaseNPC? // TODO: Potentially decouple this from BaseNPC?
pkt.AppearanceData = getTransportationAppearanceData(); pkt.AppearanceData = {
3, id, type,
x, y, z
};
sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_ENTER); sock->sendPacket(pkt, P_FE2CL_TRANSPORTATION_ENTER);
} }
@@ -78,22 +82,12 @@ void Egg::enterIntoViewOf(CNSocket *sock) {
INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt); INITSTRUCT(sP_FE2CL_SHINY_ENTER, pkt);
// TODO: Potentially decouple this from BaseNPC? // TODO: Potentially decouple this from BaseNPC?
pkt.ShinyAppearanceData = getShinyAppearanceData(); pkt.ShinyAppearanceData = {
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
}
sTransportationAppearanceData Bus::getTransportationAppearanceData() {
return sTransportationAppearanceData {
3, id, type,
x, y, z
};
}
sShinyAppearanceData Egg::getShinyAppearanceData() {
return sShinyAppearanceData {
id, type, 0, // client doesn't care about map num id, type, 0, // client doesn't care about map num
x, y, z x, y, z
}; };
sock->sendPacket(pkt, P_FE2CL_SHINY_ENTER);
} }
sNano* Player::getActiveNano() { sNano* Player::getActiveNano() {

View File

@@ -161,8 +161,6 @@ struct Egg : public BaseNPC {
virtual void enterIntoViewOf(CNSocket *sock) override; virtual void enterIntoViewOf(CNSocket *sock) override;
virtual void disappearFromViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override;
sShinyAppearanceData getShinyAppearanceData();
}; };
struct Bus : public BaseNPC { struct Bus : public BaseNPC {
@@ -174,6 +172,4 @@ struct Bus : public BaseNPC {
virtual void enterIntoViewOf(CNSocket *sock) override; virtual void enterIntoViewOf(CNSocket *sock) override;
virtual void disappearFromViewOf(CNSocket *sock) override; virtual void disappearFromViewOf(CNSocket *sock) override;
sTransportationAppearanceData getTransportationAppearanceData();
}; };

View File

@@ -87,8 +87,8 @@ void Groups::addToGroup(Group* group, EntityRef member) {
size_t pcCount = pcs.size(); size_t pcCount = pcs.size();
size_t npcCount = npcs.size(); size_t npcCount = npcs.size();
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf; sP_FE2CL_PC_GROUP_JOIN* pkt = (sP_FE2CL_PC_GROUP_JOIN*)respbuf;
pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID; pkt->iID_NewMember = PlayerManager::getPlayer(member.sock)->iID;
@@ -143,8 +143,8 @@ bool Groups::removeFromGroup(Group* group, EntityRef member) {
size_t pcCount = pcs.size(); size_t pcCount = pcs.size();
size_t npcCount = npcs.size(); size_t npcCount = npcs.size();
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf; sP_FE2CL_PC_GROUP_LEAVE* pkt = (sP_FE2CL_PC_GROUP_LEAVE*)respbuf;
pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID; pkt->iID_LeaveMember = PlayerManager::getPlayer(member.sock)->iID;
@@ -288,8 +288,8 @@ void Groups::groupTickInfo(CNSocket* sock) {
size_t pcCount = pcs.size(); size_t pcCount = pcs.size();
size_t npcCount = npcs.size(); size_t npcCount = npcs.size();
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, CN_PACKET_BUFFER_SIZE);
sP_FE2CL_PC_GROUP_MEMBER_INFO* pkt = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf; sP_FE2CL_PC_GROUP_MEMBER_INFO* pkt = (sP_FE2CL_PC_GROUP_MEMBER_INFO*)respbuf;
pkt->iID = plr->iID; pkt->iID = plr->iID;

View File

@@ -46,7 +46,7 @@ static void nanoCapsuleHandler(CNSocket* sock, int slot, sItemBase *chest) {
// in order to remove capsule form inventory, we have to send item reward packet with empty item // in order to remove capsule form inventory, we have to send item reward packet with empty item
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
assert(resplen < CN_PACKET_BODY_SIZE); assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
// we know it's only one trailing struct, so we can skip full validation // we know it's only one trailing struct, so we can skip full validation
uint8_t respbuf[resplen]; // not a variable length array, don't worry uint8_t respbuf[resplen]; // not a variable length array, don't worry
@@ -475,8 +475,8 @@ static void itemUseHandler(CNSocket* sock, CNPacketData* data) {
if (gumball.iOpt == 0) if (gumball.iOpt == 0)
gumball = {}; gumball = {};
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
sP_FE2CL_REP_PC_ITEM_USE_SUCC *resp = (sP_FE2CL_REP_PC_ITEM_USE_SUCC*)respbuf; sP_FE2CL_REP_PC_ITEM_USE_SUCC *resp = (sP_FE2CL_REP_PC_ITEM_USE_SUCC*)respbuf;
sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC)); sSkillResult_Buff *respdata = (sSkillResult_Buff*)(respbuf+sizeof(sP_FE2CL_NANO_SKILL_USE_SUCC));
@@ -556,7 +556,7 @@ static void chestOpenHandler(CNSocket *sock, CNPacketData *data) {
// item giving packet // item giving packet
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
assert(resplen < CN_PACKET_BODY_SIZE); assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
// we know it's only one trailing struct, so we can skip full validation // we know it's only one trailing struct, so we can skip full validation
uint8_t respbuf[resplen]; // not a variable length array, don't worry uint8_t respbuf[resplen]; // not a variable length array, don't worry
@@ -645,7 +645,7 @@ void Items::checkItemExpire(CNSocket* sock, Player* player) {
*/ */
const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL); const size_t resplen = sizeof(sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM) + sizeof(sTimeLimitItemDeleteInfo2CL);
assert(resplen < CN_PACKET_BODY_SIZE); assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
// we know it's only one trailing struct, so we can skip full validation // we know it's only one trailing struct, so we can skip full validation
uint8_t respbuf[resplen]; // not a variable length array, don't worry uint8_t respbuf[resplen]; // not a variable length array, don't worry
auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf; auto packet = (sP_FE2CL_PC_DELETE_TIME_LIMIT_ITEM*)respbuf;
@@ -715,7 +715,7 @@ static void giveSingleDrop(CNSocket *sock, Mob* mob, int mobDropId, const DropRo
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
assert(resplen < CN_PACKET_BODY_SIZE); assert(resplen < CN_PACKET_BUFFER_SIZE - 8);
// we know it's only one trailing struct, so we can skip full validation // we know it's only one trailing struct, so we can skip full validation
uint8_t respbuf[resplen]; // not a variable length array, don't worry uint8_t respbuf[resplen]; // not a variable length array, don't worry

View File

@@ -64,7 +64,7 @@ static bool isQuestItemFull(CNSocket* sock, int itemId, int itemCount) {
static void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid) { static void dropQuestItem(CNSocket *sock, int task, int count, int id, int mobid) {
std::cout << "Altered item id " << id << " by " << count << " for task id " << task << std::endl; std::cout << "Altered item id " << id << " by " << count << " for task id " << task << std::endl;
const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward); const size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + sizeof(sItemReward);
assert(resplen < CN_PACKET_BODY_SIZE); assert(resplen < CN_PACKET_BUFFER_SIZE);
// we know it's only one trailing struct, so we can skip full validation // we know it's only one trailing struct, so we can skip full validation
Player *plr = PlayerManager::getPlayer(sock); Player *plr = PlayerManager::getPlayer(sock);
@@ -152,14 +152,14 @@ static int giveMissionReward(CNSocket *sock, int task, int choice=0) {
plr->Inven[slots[i]] = { 999, 999, 999, 0 }; // temp item; overwritten later plr->Inven[slots[i]] = { 999, 999, 999, 0 }; // temp item; overwritten later
} }
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + nrewards * sizeof(sItemReward); size_t resplen = sizeof(sP_FE2CL_REP_REWARD_ITEM) + nrewards * sizeof(sItemReward);
assert(resplen < CN_PACKET_BODY_SIZE); assert(resplen < CN_PACKET_BUFFER_SIZE);
sP_FE2CL_REP_REWARD_ITEM *resp = (sP_FE2CL_REP_REWARD_ITEM *)respbuf; sP_FE2CL_REP_REWARD_ITEM *resp = (sP_FE2CL_REP_REWARD_ITEM *)respbuf;
sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM)); sItemReward *item = (sItemReward *)(respbuf + sizeof(sP_FE2CL_REP_REWARD_ITEM));
// don't forget to zero the buffer! // don't forget to zero the buffer!
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
// update player // update player
plr->money += reward->money; plr->money += reward->money;

View File

@@ -238,8 +238,8 @@ static void dealCorruption(Mob *mob, std::vector<int> targetData, int skillID, i
return; return;
} }
uint8_t respbuf[CN_PACKET_BODY_SIZE]; uint8_t respbuf[CN_PACKET_BUFFER_SIZE];
memset(respbuf, 0, CN_PACKET_BODY_SIZE); memset(respbuf, 0, resplen);
sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf; sP_FE2CL_NPC_SKILL_CORRUPTION_HIT *resp = (sP_FE2CL_NPC_SKILL_CORRUPTION_HIT*)respbuf;
sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT)); sCAttackResult *respdata = (sCAttackResult*)(respbuf+sizeof(sP_FE2CL_NPC_SKILL_CORRUPTION_HIT));
@@ -478,14 +478,6 @@ void MobAI::deadStep(CombatNPC* npc, time_t currTime) {
if (self->groupLeader == self->id) if (self->groupLeader == self->id)
roamingStep(self, currTime); roamingStep(self, currTime);
/*
* If the mob hasn't fully despanwed yet, don't try to respawn it. This protects
* against the edge case where mobs with a very short regenTime would try to respawn
* before they've faded away; and would respawn even if they were meant to be removed.
*/
if (!self->despawned)
return;
if (self->killedTime != 0 && currTime - self->killedTime < self->regenTime * 100) if (self->killedTime != 0 && currTime - self->killedTime < self->regenTime * 100)
return; return;

View File

@@ -396,14 +396,6 @@ static void heartbeatPlayer(CNSocket* sock, CNPacketData* data) {
static void exitGame(CNSocket* sock, CNPacketData* data) { static void exitGame(CNSocket* sock, CNPacketData* data) {
auto exitData = (sP_CL2FE_REQ_PC_EXIT*)data->buf; auto exitData = (sP_CL2FE_REQ_PC_EXIT*)data->buf;
// Refresh any auth cookie, in case "change character" was used
Player* plr = getPlayer(sock);
if (plr != nullptr) {
// 5 seconds should be enough to log in again
Database::refreshCookie(plr->accountId, 5);
}
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response); INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response);
response.iID = exitData->iID; response.iID = exitData->iID;
@@ -620,10 +612,6 @@ std::string PlayerManager::getPlayerName(Player *plr, bool id) {
if (plr == nullptr) if (plr == nullptr)
return "NOT IN GAME"; return "NOT IN GAME";
if (plr->PCStyle.iNameCheck != 1) {
return "Player " + std::to_string(plr->iID);
}
std::string ret = ""; std::string ret = "";
if (id && plr->accountLevel <= 30) if (id && plr->accountLevel <= 30)
ret += "(GM) "; ret += "(GM) ";

View File

@@ -1,7 +1,5 @@
#include "TableData.hpp" #include "TableData.hpp"
#include "servers/CNLoginServer.hpp"
#include "NPCManager.hpp" #include "NPCManager.hpp"
#include "Missions.hpp" #include "Missions.hpp"
#include "Items.hpp" #include "Items.hpp"
@@ -81,25 +79,6 @@ static void loadXDT(json& xdtData) {
NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"]; NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"];
try { try {
// load name wheel names
json firstNameData = xdtData["m_pNameTable"]["m_pFirstName"];
for (json::iterator _name = firstNameData.begin(); _name != firstNameData.end(); _name++) {
auto name = _name.value();
LoginServer::WheelFirstNames.push_back(name["m_pstrNameString"]);
}
json middleNameData = xdtData["m_pNameTable"]["m_pMiddleName"];
for (json::iterator _name = middleNameData.begin(); _name != middleNameData.end(); _name++) {
auto name = _name.value();
LoginServer::WheelMiddleNames.push_back(name["m_pstrNameString"]);
}
json lastNameData = xdtData["m_pNameTable"]["m_pLastName"];
for (json::iterator _name = lastNameData.begin(); _name != lastNameData.end(); _name++) {
auto name = _name.value();
LoginServer::WheelLastNames.push_back(name["m_pstrNameString"]);
}
// load warps // load warps
json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"]; json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"];

View File

@@ -388,21 +388,8 @@ NPCPath* Transport::findApplicablePath(int32_t id, int32_t type, int taskID) {
void Transport::constructPathNPC(int32_t id, NPCPath* path) { void Transport::constructPathNPC(int32_t id, NPCPath* path) {
BaseNPC* npc = NPCManager::NPCs[id]; BaseNPC* npc = NPCManager::NPCs[id];
if (npc->kind == EntityKind::MOB)
if (npc->kind == EntityKind::MOB) { ((Mob*)(npc))->staticPath = true;
auto mob = (Mob*)npc;
mob->staticPath = true;
Vec3 firstPoint = path->points.front();
// Ensure that the first point coincides with the mob's spawn point.
if (mob->spawnX != firstPoint.x || mob->spawnY != firstPoint.y) {
std::cout << "[FATAL] The first point of the route for mob " << mob->id << " (type " << mob->type
<< ") does not correspond with its spawn point." << std::endl;
exit(1);
}
}
npc->loopingPath = path->isLoop; npc->loopingPath = path->isLoop;
// Interpolate // Interpolate

View File

@@ -428,7 +428,7 @@ void CNServer::removePollFD(int fd) {
} }
void CNServer::start() { void CNServer::start() {
std::cout << "Starting " << serverType << " server at *:" << port << std::endl; std::cout << "Starting server at *:" << port << std::endl;
while (active) { while (active) {
// the timeout is to ensure shard timers are ticking // the timeout is to ensure shard timers are ticking
int n = poll(fds.data(), fds.size(), 50); int n = poll(fds.data(), fds.size(), 50);

View File

@@ -95,14 +95,14 @@ inline constexpr bool isOutboundPacketID(uint32_t id) {
// for outbound packets // for outbound packets
inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t plsize) { inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t plsize) {
// check for multiplication overflow // check for multiplication overflow
if (npayloads > 0 && (CN_PACKET_BODY_SIZE) / (size_t)npayloads < plsize) if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
return false; return false;
// it's safe to multiply // it's safe to multiply
size_t trailing = npayloads * plsize; size_t trailing = npayloads * plsize;
// does it fit in a packet? // does it fit in a packet?
if (base + trailing > CN_PACKET_BODY_SIZE) if (base + trailing > CN_PACKET_BUFFER_SIZE - 8)
return false; return false;
// everything is a-ok! // everything is a-ok!
@@ -112,14 +112,14 @@ inline constexpr bool validOutVarPacket(size_t base, size_t npayloads, size_t pl
// for inbound packets // for inbound packets
inline constexpr bool validInVarPacket(size_t base, size_t npayloads, size_t plsize, size_t datasize) { inline constexpr bool validInVarPacket(size_t base, size_t npayloads, size_t plsize, size_t datasize) {
// check for multiplication overflow // check for multiplication overflow
if (npayloads > 0 && CN_PACKET_BODY_SIZE / (size_t)npayloads < plsize) if (npayloads > 0 && (CN_PACKET_BUFFER_SIZE - 8) / (size_t)npayloads < plsize)
return false; return false;
// it's safe to multiply // it's safe to multiply
size_t trailing = npayloads * plsize; size_t trailing = npayloads * plsize;
// make sure size is exact // make sure size is exact
// datasize has already been validated against CN_PACKET_BODY_SIZE // datasize has already been validated against CN_PACKET_BUFFER_SIZE
if (datasize != base + trailing) if (datasize != base + trailing)
return false; return false;

View File

@@ -29,8 +29,8 @@
#define INITSTRUCT(T, x) T x; \ #define INITSTRUCT(T, x) T x; \
memset(&x, 0, sizeof(T)); memset(&x, 0, sizeof(T));
#define INITVARPACKET(_buf, _Pkt, _pkt, _Trailer, _trailer) uint8_t _buf[CN_PACKET_BODY_SIZE]; \ #define INITVARPACKET(_buf, _Pkt, _pkt, _Trailer, _trailer) uint8_t _buf[CN_PACKET_BUFFER_SIZE]; \
memset(&_buf, 0, CN_PACKET_BODY_SIZE); \ memset(&_buf, 0, CN_PACKET_BUFFER_SIZE); \
auto _pkt = (_Pkt*)_buf; \ auto _pkt = (_Pkt*)_buf; \
auto _trailer = (_Trailer*)(_pkt + 1); auto _trailer = (_Trailer*)(_pkt + 1);
@@ -49,7 +49,6 @@ std::string U16toU8(char16_t* src, size_t max);
size_t U8toU16(std::string src, char16_t* des, size_t max); // returns number of char16_t that was written at des size_t U8toU16(std::string src, char16_t* des, size_t max); // returns number of char16_t that was written at des
time_t getTime(); time_t getTime();
time_t getTimestamp(); time_t getTimestamp();
int timingSafeStrcmp(const char* a, const char* b);
void terminate(int); void terminate(int);
// The PROTOCOL_VERSION definition can be defined by the build system. // The PROTOCOL_VERSION definition can be defined by the build system.

View File

@@ -1,8 +1,6 @@
/* enum definitions from the client */ /* enum definitions from the client */
#pragma once #pragma once
#include "core/CNStructs.hpp"
// floats // floats
const float VALUE_BATTERY_EMPTY_PENALTY = 0.5f; const float VALUE_BATTERY_EMPTY_PENALTY = 0.5f;
const float CN_EP_RANK_1 = 0.8f; const float CN_EP_RANK_1 = 0.8f;
@@ -412,13 +410,7 @@ enum {
SEND_ANYCAST_NEW = 3, SEND_ANYCAST_NEW = 3,
SEND_BROADCAST = 4, SEND_BROADCAST = 4,
#if PROTOCOL_VERSION == 728
CN_PACKET_BUFFER_SIZE = 8192,
#elif PROTOCOL_VERSION == 1013
CN_PACKET_BUFFER_SIZE = 8192,
#else
CN_PACKET_BUFFER_SIZE = 4096, CN_PACKET_BUFFER_SIZE = 4096,
#endif
P_CL2LS_REQ_LOGIN = 0x12000001, // 301989889 P_CL2LS_REQ_LOGIN = 0x12000001, // 301989889
P_CL2LS_REQ_CHECK_CHAR_NAME = 0x12000002, // 301989890 P_CL2LS_REQ_CHECK_CHAR_NAME = 0x12000002, // 301989890
@@ -942,8 +934,3 @@ enum {
N_PACKETS = N_CL2LS + N_CL2FE + N_FE2CL + N_LS2CL N_PACKETS = N_CL2LS + N_CL2FE + N_FE2CL + N_LS2CL
}; };
/*
* Usable space in the packet buffer = CN_PACKET_BUFFER_SIZE - type - size
*/
constexpr size_t CN_PACKET_BODY_SIZE = CN_PACKET_BUFFER_SIZE - 2 * sizeof(int32_t);

View File

@@ -235,7 +235,7 @@ std::map<uint32_t, PacketDesc> Packets::packets = {
PACKET(P_FE2CL_REP_PC_EXIT_FAIL), PACKET(P_FE2CL_REP_PC_EXIT_FAIL),
PACKET(P_FE2CL_REP_PC_EXIT_SUCC), PACKET(P_FE2CL_REP_PC_EXIT_SUCC),
PACKET(P_FE2CL_PC_EXIT), PACKET(P_FE2CL_PC_EXIT),
VAR_PACKET(P_FE2CL_PC_AROUND, iPCCnt, sPCAppearanceData), PACKET(P_FE2CL_PC_AROUND),
PACKET(P_FE2CL_PC_MOVE), PACKET(P_FE2CL_PC_MOVE),
PACKET(P_FE2CL_PC_STOP), PACKET(P_FE2CL_PC_STOP),
PACKET(P_FE2CL_PC_JUMP), PACKET(P_FE2CL_PC_JUMP),
@@ -243,9 +243,9 @@ std::map<uint32_t, PacketDesc> Packets::packets = {
PACKET(P_FE2CL_NPC_EXIT), PACKET(P_FE2CL_NPC_EXIT),
PACKET(P_FE2CL_NPC_MOVE), PACKET(P_FE2CL_NPC_MOVE),
PACKET(P_FE2CL_NPC_NEW), PACKET(P_FE2CL_NPC_NEW),
VAR_PACKET(P_FE2CL_NPC_AROUND, iNPCCnt, sNPCAppearanceData), PACKET(P_FE2CL_NPC_AROUND),
VAR_PACKET(P_FE2CL_AROUND_DEL_PC, iPCCnt, int32_t), PACKET(P_FE2CL_AROUND_DEL_PC),
VAR_PACKET(P_FE2CL_AROUND_DEL_NPC, iNPCCnt, int32_t), PACKET(P_FE2CL_AROUND_DEL_NPC),
PACKET(P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC), PACKET(P_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC),
PACKET(P_FE2CL_REP_SEND_FREECHAT_MESSAGE_FAIL), PACKET(P_FE2CL_REP_SEND_FREECHAT_MESSAGE_FAIL),
VAR_PACKET(P_FE2CL_PC_ATTACK_NPCs_SUCC, iNPCCnt, sAttackResult), VAR_PACKET(P_FE2CL_PC_ATTACK_NPCs_SUCC, iNPCCnt, sAttackResult),
@@ -387,8 +387,8 @@ std::map<uint32_t, PacketDesc> Packets::packets = {
PACKET(P_FE2CL_TRANSPORTATION_EXIT), PACKET(P_FE2CL_TRANSPORTATION_EXIT),
PACKET(P_FE2CL_TRANSPORTATION_MOVE), PACKET(P_FE2CL_TRANSPORTATION_MOVE),
PACKET(P_FE2CL_TRANSPORTATION_NEW), PACKET(P_FE2CL_TRANSPORTATION_NEW),
VAR_PACKET(P_FE2CL_TRANSPORTATION_AROUND, iCnt, sTransportationAppearanceData), PACKET(P_FE2CL_TRANSPORTATION_AROUND),
VAR_PACKET(P_FE2CL_AROUND_DEL_TRANSPORTATION, iCnt, int32_t), PACKET(P_FE2CL_AROUND_DEL_TRANSPORTATION),
PACKET(P_FE2CL_REP_EP_RANK_LIST), PACKET(P_FE2CL_REP_EP_RANK_LIST),
PACKET(P_FE2CL_REP_EP_RANK_DETAIL), PACKET(P_FE2CL_REP_EP_RANK_DETAIL),
PACKET(P_FE2CL_REP_EP_RANK_PC_INFO), PACKET(P_FE2CL_REP_EP_RANK_PC_INFO),
@@ -404,8 +404,8 @@ std::map<uint32_t, PacketDesc> Packets::packets = {
PACKET(P_FE2CL_SHINY_ENTER), PACKET(P_FE2CL_SHINY_ENTER),
PACKET(P_FE2CL_SHINY_EXIT), PACKET(P_FE2CL_SHINY_EXIT),
PACKET(P_FE2CL_SHINY_NEW), PACKET(P_FE2CL_SHINY_NEW),
VAR_PACKET(P_FE2CL_SHINY_AROUND, iShinyCnt, sShinyAppearanceData), PACKET(P_FE2CL_SHINY_AROUND),
VAR_PACKET(P_FE2CL_AROUND_DEL_SHINY, iShinyCnt, int32_t), PACKET(P_FE2CL_AROUND_DEL_SHINY),
PACKET(P_FE2CL_REP_SHINY_PICKUP_FAIL), PACKET(P_FE2CL_REP_SHINY_PICKUP_FAIL),
PACKET(P_FE2CL_REP_SHINY_PICKUP_SUCC), PACKET(P_FE2CL_REP_SHINY_PICKUP_SUCC),
PACKET(P_FE2CL_PC_MOVETRANSPORTATION), PACKET(P_FE2CL_PC_MOVETRANSPORTATION),

View File

@@ -5,7 +5,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#define DATABASE_VERSION 6 #define DATABASE_VERSION 5
namespace Database { namespace Database {
@@ -56,7 +56,6 @@ namespace Database {
// return true if cookie is valid for the account. // return true if cookie is valid for the account.
// invalidates the stored cookie afterwards // invalidates the stored cookie afterwards
bool checkCookie(int accountId, const char *cookie); bool checkCookie(int accountId, const char *cookie);
void refreshCookie(int accountId, int durationSec);
// interface for the /ban command // interface for the /ban command
bool banPlayer(int playerId, std::string& reason); bool banPlayer(int playerId, std::string& reason);
@@ -69,7 +68,7 @@ namespace Database {
bool isNameFree(std::string firstName, std::string lastName); bool isNameFree(std::string firstName, std::string lastName);
bool isSlotFree(int accountId, int slotNum); bool isSlotFree(int accountId, int slotNum);
/// returns ID, 0 if something failed /// returns ID, 0 if something failed
int createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck); int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID);
/// returns true if query succeeded /// returns true if query succeeded
bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId); bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId);
/// returns true if query succeeded /// returns true if query succeeded
@@ -85,7 +84,7 @@ namespace Database {
}; };
void evaluateCustomName(int characterID, CustomName decision); void evaluateCustomName(int characterID, CustomName decision);
/// returns true if query succeeded /// returns true if query succeeded
bool changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck); bool changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId);
// getting players // getting players
void getPlayer(Player* plr, int id); void getPlayer(Player* plr, int id);

View File

@@ -1,9 +1,5 @@
#include "core/CNStructs.hpp"
#include "db/internal.hpp" #include "db/internal.hpp"
#include "servers/CNLoginServer.hpp"
#include "bcrypt/BCrypt.hpp" #include "bcrypt/BCrypt.hpp"
void Database::findAccount(Account* account, std::string login) { void Database::findAccount(Account* account, std::string login) {
@@ -134,44 +130,21 @@ bool Database::checkCookie(int accountId, const char *tryCookie) {
return false; return false;
} }
bool match = (timingSafeStrcmp(cookie, tryCookie) == 0); /*
* since cookies are immediately invalidated, we don't need to be concerned about
* timing-related side channel attacks, so strcmp is fine here
*/
bool match = (strcmp(cookie, tryCookie) == 0);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
/* sqlite3_prepare_v2(db, sql_invalidate, -1, &stmt, NULL);
* Only invalidate the cookie if it was correct. This prevents sqlite3_bind_int(stmt, 1, accountId);
* replay attacks without enabling DOS attacks on accounts. rc = sqlite3_step(stmt);
*/
if (match) {
sqlite3_prepare_v2(db, sql_invalidate, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, accountId);
rc = sqlite3_step(stmt);
sqlite3_finalize(stmt);
if (rc != SQLITE_DONE)
std::cout << "[WARN] Database fail on checkCookie(): " << sqlite3_errmsg(db) << std::endl;
}
return match;
}
void Database::refreshCookie(int accountId, int durationSec) {
std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"(
UPDATE Auth
SET Expires = ?
WHERE AccountID = ?;
)";
int expires = getTimestamp() + durationSec;
sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, expires);
sqlite3_bind_int(stmt, 2, accountId);
int rc = sqlite3_step(stmt);
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
if (rc != SQLITE_DONE) if (rc != SQLITE_DONE)
std::cout << "[WARN] Database fail on refreshCookie(): " << sqlite3_errmsg(db) << std::endl; std::cout << "[WARN] Database fail on checkCookie(): " << sqlite3_errmsg(db) << std::endl;
return match;
} }
void Database::updateSelected(int accountId, int slot) { void Database::updateSelected(int accountId, int slot) {
@@ -293,7 +266,7 @@ bool Database::isSlotFree(int accountId, int slotNum) {
return result; return result;
} }
int Database::createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck) { int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) {
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);
@@ -306,17 +279,22 @@ int Database::createCharacter(int slot, int accountId, const char* firstName, co
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
)"; )";
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
std::string firstName = AUTOU16TOU8(save->szFirstName);
std::string lastName = AUTOU16TOU8(save->szLastName);
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_int(stmt, 1, accountId); sqlite3_bind_int(stmt, 1, AccountID);
sqlite3_bind_int(stmt, 2, slot); sqlite3_bind_int(stmt, 2, save->iSlotNum);
sqlite3_bind_text(stmt, 3, firstName, -1, NULL); sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 4, lastName, -1, NULL); sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, NULL);
sqlite3_bind_int(stmt, 5, settings::SPAWN_X); sqlite3_bind_int(stmt, 5, settings::SPAWN_X);
sqlite3_bind_int(stmt, 6, settings::SPAWN_Y); sqlite3_bind_int(stmt, 6, settings::SPAWN_Y);
sqlite3_bind_int(stmt, 7, settings::SPAWN_Z); sqlite3_bind_int(stmt, 7, settings::SPAWN_Z);
sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE); sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE);
sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1)); sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1));
// if FNCode isn't 0, it's a wheel name
int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0;
sqlite3_bind_int(stmt, 10, nameCheck); sqlite3_bind_int(stmt, 10, nameCheck);
// blobs // blobs
@@ -645,7 +623,7 @@ void Database::evaluateCustomName(int characterID, CustomName decision) {
sqlite3_finalize(stmt); sqlite3_finalize(stmt);
} }
bool Database::changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck) { bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) {
std::lock_guard<std::mutex> lock(dbCrit); std::lock_guard<std::mutex> lock(dbCrit);
const char* sql = R"( const char* sql = R"(
@@ -659,10 +637,15 @@ bool Database::changeName(int playerId, int accountId, const char* firstName, co
sqlite3_stmt* stmt; sqlite3_stmt* stmt;
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL); sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
sqlite3_bind_text(stmt, 1, firstName, -1, NULL); std::string firstName = AUTOU16TOU8(save->szFirstName);
sqlite3_bind_text(stmt, 2, lastName, -1, NULL); std::string lastName = AUTOU16TOU8(save->szLastName);
sqlite3_bind_text(stmt, 1, firstName.c_str(), -1, NULL);
sqlite3_bind_text(stmt, 2, lastName.c_str(), -1, NULL);
// if FNCode isn't 0, it's a wheel name
int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0;
sqlite3_bind_int(stmt, 3, nameCheck); sqlite3_bind_int(stmt, 3, nameCheck);
sqlite3_bind_int(stmt, 4, playerId); sqlite3_bind_int(stmt, 4, save->iPCUID);
sqlite3_bind_int(stmt, 5, accountId); sqlite3_bind_int(stmt, 5, accountId);
int rc = sqlite3_step(stmt); int rc = sqlite3_step(stmt);

View File

@@ -222,17 +222,6 @@ time_t getTimestamp() {
return (time_t)value.count(); return (time_t)value.count();
} }
// timing safe strcmp implementation for e.g. cookie validation
int timingSafeStrcmp(const char* a, const char* b) {
int diff = 0;
while (*a && *b) {
diff |= *a++ ^ *b++;
}
diff |= *a;
diff |= *b;
return diff;
}
// convert integer timestamp (in s) to FF systime struct // convert integer timestamp (in s) to FF systime struct
sSYSTEMTIME timeStampToStruct(uint64_t time) { sSYSTEMTIME timeStampToStruct(uint64_t time) {

View File

@@ -391,7 +391,7 @@ static bool landlock_detect() {
exit(1); exit(1);
} }
std::cout << "[INFO] Detected Landlock ABI version: " << abi << std::endl; std::cout << "[INFO] Landlock ABI version: " << abi << std::endl;
switch (abi) { switch (abi) {
case 1: case 1:

View File

@@ -1,5 +1,4 @@
#include "servers/CNLoginServer.hpp" #include "servers/CNLoginServer.hpp"
#include "servers/Monitor.hpp"
#include "core/CNShared.hpp" #include "core/CNShared.hpp"
#include "db/Database.hpp" #include "db/Database.hpp"
@@ -13,12 +12,6 @@
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions; std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
namespace LoginServer {
std::vector<std::string> WheelFirstNames;
std::vector<std::string> WheelMiddleNames;
std::vector<std::string> WheelLastNames;
}
CNLoginServer::CNLoginServer(uint16_t p) { CNLoginServer::CNLoginServer(uint16_t p) {
serverType = "login"; serverType = "login";
port = p; port = p;
@@ -116,17 +109,11 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
std::string userLogin; std::string userLogin;
std::string userToken; // could be password or auth cookie std::string userToken; // could be password or auth cookie
/*
* In this context, "cookie auth" just means the credentials were sent
* in the szCookie fields instead of szID and szPassword.
*/
bool isCookieAuth = login->iLoginType == USE_COOKIE_FIELDS;
/* /*
* The std::string -> char* -> std::string maneuver should remove any * The std::string -> char* -> std::string maneuver should remove any
* trailing garbage after the null terminator. * trailing garbage after the null terminator.
*/ */
if (isCookieAuth) { if (login->iLoginType == (int32_t)LoginType::COOKIE) {
userLogin = std::string(AUTOU8(login->szCookie_TEGid).c_str()); userLogin = std::string(AUTOU8(login->szCookie_TEGid).c_str());
userToken = std::string(AUTOU8(login->szCookie_authid).c_str()); userToken = std::string(AUTOU8(login->szCookie_authid).c_str());
} else { } else {
@@ -134,13 +121,54 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
userToken = std::string(AUTOU16TOU8(login->szPassword).c_str()); userToken = std::string(AUTOU16TOU8(login->szPassword).c_str());
} }
if (!CNLoginServer::checkUsername(sock, userLogin)) { // check username regex
if (!CNLoginServer::isUsernameGood(userLogin)) {
// send a custom error message
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
std::string text = "Invalid login\n";
text += "Login has to be 4 - 32 characters long and can't contain special characters other than dash and underscore";
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
msg.iDuringTime = 10;
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
// we still have to send login fail to prevent softlock
return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); return loginFail(LoginError::LOGIN_ERROR, userLogin, sock);
} }
// we only interpret the token as a cookie if cookie login was used and it's allowed.
// otherwise we interpret it as a password, and this maintains compatibility with
// the auto-login trick used on older clients
bool isCookieAuth = login->iLoginType == (int32_t)LoginType::COOKIE
&& CNLoginServer::isLoginTypeAllowed(LoginType::COOKIE);
// password login checks
if (!isCookieAuth) { if (!isCookieAuth) {
// password was sent in plaintext // bail if password auth isn't allowed
if (!CNLoginServer::checkPassword(sock, userToken)) { if (!CNLoginServer::isLoginTypeAllowed(LoginType::PASSWORD)) {
// send a custom error message
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
std::string text = "Password login disabled\n";
text += "This server has disabled logging in with plaintext passwords.\n";
text += "Please contact an admin for assistance.";
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
msg.iDuringTime = 12;
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
// we still have to send login fail to prevent softlock
return loginFail(LoginError::LOGIN_ERROR, userLogin, sock);
}
// check regex
if (!CNLoginServer::isPasswordGood(userToken)) {
// send a custom error message
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
std::string text = "Invalid password\n";
text += "Password has to be 8 - 32 characters long";
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
msg.iDuringTime = 10;
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
// we still have to send login fail to prevent softlock
return loginFail(LoginError::LOGIN_ERROR, userLogin, sock); return loginFail(LoginError::LOGIN_ERROR, userLogin, sock);
} }
} }
@@ -150,25 +178,42 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
// account was not found // account was not found
if (findUser.AccountID == 0) { if (findUser.AccountID == 0) {
/* // don't auto-create an account if it's a cookie auth for whatever reason
* Don't auto-create accounts if it's a cookie login. if (settings::AUTOCREATEACCOUNTS && !isCookieAuth)
* It'll either be a bad cookie or a plaintext password sent by auto-login;
* either way, we only want to allow auto-creation if the user explicitly entered their credentials.
*/
if (settings::AUTOCREATEACCOUNTS && !isCookieAuth) {
return newAccount(sock, userLogin, userToken, login->iClientVerC); return newAccount(sock, userLogin, userToken, login->iClientVerC);
}
return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock); return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock);
} }
// make sure either a valid cookie or password was sent if (isCookieAuth) {
if (!CNLoginServer::checkToken(sock, findUser, userToken, isCookieAuth)) { const char *cookie = userToken.c_str();
return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock); if (!Database::checkCookie(findUser.AccountID, cookie))
return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock);
} else {
// simple password check
if (!CNLoginServer::isPasswordCorrect(findUser.Password, userToken))
return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock);
} }
if (CNLoginServer::checkBan(sock, findUser)) { // is the account banned
return; // don't send fail packet if (findUser.BannedUntil > getTimestamp()) {
// send a custom error message
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
// ceiling devision
int64_t remainingDays = (findUser.BannedUntil-getTimestamp()) / 86400 + ((findUser.BannedUntil - getTimestamp()) % 86400 != 0);
std::string text = "Your account has been banned. \nReason: ";
text += findUser.BanReason;
text += "\nBan expires in " + std::to_string(remainingDays) + " day";
if (remainingDays > 1)
text += "s";
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
msg.iDuringTime = 99999999;
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
// don't send fail packet
return;
} }
/* /*
@@ -292,29 +337,10 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp); INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp);
int errorCode = 0; int errorCode = 0;
if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
std::string firstName = AUTOU16TOU8(save->szFirstName); errorCode = 4;
std::string lastName = AUTOU16TOU8(save->szLastName); } else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
int nameCheck = 0; errorCode = 1;
// if FNCode isn't 0, it's a wheel name
if (save->iFNCode != 0) {
if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) {
errorCode = 4;
} else {
nameCheck = settings:: APPROVEWHEELNAMES ? 1 : 0;
}
} else {
// custom name
nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0;
}
if (errorCode == 0) {
if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) {
errorCode = 4;
} else if (!Database::isNameFree(firstName, lastName)) {
errorCode = 1;
}
} }
if (errorCode != 0) { if (errorCode != 0) {
@@ -332,19 +358,12 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum)) if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum))
return invalidCharacter(sock); return invalidCharacter(sock);
resp.iPC_UID = Database::createCharacter(save->iSlotNum, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck); resp.iPC_UID = Database::createCharacter(save, loginSessions[sock].userID);
// if query somehow failed // if query somehow failed
if (resp.iPC_UID == 0) { if (resp.iPC_UID == 0) {
std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl; std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl;
return invalidCharacter(sock); return invalidCharacter(sock);
} }
// fire name check event if needed
if (nameCheck != 1) {
std::string namereq = std::to_string(resp.iPC_UID) + " " + firstName + " " + lastName;
Monitor::namereqs.push_back(namereq);
}
resp.iSlotNum = save->iSlotNum; resp.iSlotNum = save->iSlotNum;
resp.iGender = save->iGender; resp.iGender = save->iGender;
@@ -360,9 +379,7 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
DEBUGLOG( DEBUGLOG(
std::cout << "Login Server: new character created" << std::endl; std::cout << "Login Server: new character created" << std::endl;
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl; std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
std::cout << "\tName: " << firstName << " " << lastName; std::cout << "\tName: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl;
if (nameCheck != 1) std::cout << " (pending approval)";
std::cout << std::endl;
) )
} }
@@ -529,30 +546,11 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
auto save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf; auto save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf;
int errorCode = 0; int errorCode = 0;
if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
std::string firstName = AUTOU16TOU8(save->szFirstName); errorCode = 4;
std::string lastName = AUTOU16TOU8(save->szLastName);
int nameCheck = 0;
// if FNCode isn't 0, it's a wheel name
if (save->iFNCode != 0) {
if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) {
errorCode = 4;
} else {
nameCheck = settings::APPROVEWHEELNAMES ? 1 : 0;
}
} else {
// custom name
nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0;
} }
else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
if (errorCode == 0) { errorCode = 1;
if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) {
errorCode = 4;
}
else if (!Database::isNameFree(firstName, lastName)) {
errorCode = 1;
}
} }
if (errorCode != 0) { if (errorCode != 0) {
@@ -567,15 +565,9 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
return; return;
} }
if (!Database::changeName(save->iPCUID, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck)) if (!Database::changeName(save, loginSessions[sock].userID))
return invalidCharacter(sock); return invalidCharacter(sock);
// fire name check event if needed
if (nameCheck != 1) {
std::string namereq = std::to_string(save->iPCUID) + " " + firstName + " " + lastName;
Monitor::namereqs.push_back(namereq);
}
INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp); INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp);
resp.iPC_UID = save->iPCUID; resp.iPC_UID = save->iPCUID;
memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName)); memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName));
@@ -587,10 +579,8 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
sock->sendPacket(resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC); sock->sendPacket(resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC);
DEBUGLOG( DEBUGLOG(
std::cout << "Login Server: Name change request for character [" << save->iPCUID << "]" << std::endl; std::cout << "Login Server: Name check success for character [" << save->iPCUID << "]" << std::endl;
std::cout << "\tNew name: " << firstName << " " << lastName; std::cout << "\tNew name: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl;
if (nameCheck != 1) std::cout << " (pending approval)";
std::cout << std::endl;
) )
} }
@@ -669,12 +659,12 @@ bool CNLoginServer::exitDuplicate(int accountId) {
return false; return false;
} }
bool CNLoginServer::isUsernameGood(std::string& login) { bool CNLoginServer::isUsernameGood(std::string login) {
const std::regex loginRegex("[a-zA-Z0-9_-]{4,32}"); const std::regex loginRegex("[a-zA-Z0-9_-]{4,32}");
return (std::regex_match(login, loginRegex)); return (std::regex_match(login, loginRegex));
} }
bool CNLoginServer::isPasswordGood(std::string& password) { bool CNLoginServer::isPasswordGood(std::string password) {
const std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}"); const std::regex passwordRegex("[a-zA-Z0-9!@#$%^&*()_+]{8,32}");
return (std::regex_match(password, passwordRegex)); return (std::regex_match(password, passwordRegex));
} }
@@ -683,42 +673,6 @@ bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tr
return BCrypt::validatePassword(tryPassword, actualPassword); return BCrypt::validatePassword(tryPassword, actualPassword);
} }
bool CNLoginServer::isNameWheelNameGood(int fnCode, int mnCode, int lnCode, std::string& firstName, std::string& lastName) {
if (fnCode >= LoginServer::WheelFirstNames.size()
|| mnCode >= LoginServer::WheelMiddleNames.size()
|| lnCode >= LoginServer::WheelLastNames.size()) {
std::cout << "[WARN] Login Server: Invalid name codes received: " << fnCode << " " << mnCode << " " << lnCode << std::endl;
return false;
}
// client sends 1 if not selected for these. they point to a single blank space. why.
// just change them to 0, which points to an empty string; keeps the code much cleaner
if (mnCode == 1) mnCode = 0;
if (lnCode == 1) lnCode = 0;
std::string firstNameFromWheel = LoginServer::WheelFirstNames[fnCode];
std::string middleNamePart = LoginServer::WheelMiddleNames[mnCode];
std::string lastNamePart = LoginServer::WheelLastNames[lnCode];
if (mnCode != 0 && middleNamePart[middleNamePart.size() - 1] != ' ') {
// If there's a middle name, we need to lowercase the last name
std::transform(lastNamePart.begin(), lastNamePart.end(), lastNamePart.begin(), ::tolower);
}
std::string lastNameFromWheel = middleNamePart + lastNamePart;
if (firstNameFromWheel.empty() || lastNameFromWheel.empty()) {
std::cout << "[WARN] Login Server: Invalid wheel name combo: " << fnCode << " " << mnCode << " " << lnCode << std::endl;
return false;
}
if (firstName != firstNameFromWheel || lastName != lastNameFromWheel) {
std::cout << "[WARN] Login Server: Name wheel mismatch. Expected " << firstNameFromWheel << " " << lastNameFromWheel << ", got " << firstName << " " << lastName << std::endl;
return false;
}
return true;
}
bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) { bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastname) {
//Allow alphanumeric and dot characters in names(disallows dot and space characters at the beginning of a name) //Allow alphanumeric and dot characters in names(disallows dot and space characters at the beginning of a name)
std::regex firstnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)"); std::regex firstnamecheck(R"(((?! )(?!\.)[a-zA-Z0-9]*\.{0,1}(?!\.+ +)[a-zA-Z0-9]* {0,1}(?! +))*$)");
@@ -726,107 +680,16 @@ bool CNLoginServer::isCharacterNameGood(std::string Firstname, std::string Lastn
return (std::regex_match(Firstname, firstnamecheck) && std::regex_match(Lastname, lastnamecheck)); return (std::regex_match(Firstname, firstnamecheck) && std::regex_match(Lastname, lastnamecheck));
} }
bool CNLoginServer::isAuthMethodAllowed(AuthMethod authMethod) { bool CNLoginServer::isLoginTypeAllowed(LoginType loginType) {
// the config file specifies "comma-separated" but tbh we don't care // the config file specifies "comma-separated" but tbh we don't care
switch (authMethod) { switch (loginType) {
case AuthMethod::PASSWORD: case LoginType::PASSWORD:
return settings::AUTHMETHODS.find("password") != std::string::npos; return settings::AUTHMETHODS.find("password") != std::string::npos;
case AuthMethod::COOKIE: case LoginType::COOKIE:
return settings::AUTHMETHODS.find("cookie") != std::string::npos; return settings::AUTHMETHODS.find("cookie") != std::string::npos;
default: default:
break; break;
} }
return false; return false;
} }
bool CNLoginServer::checkPassword(CNSocket* sock, std::string& password) {
// check password auth allowed
if (!CNLoginServer::isAuthMethodAllowed(AuthMethod::PASSWORD)) {
// send a custom error message
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
std::string text = "Password login disabled\n";
text += "This server has disabled logging in with plaintext passwords.\n";
text += "Please contact an admin for assistance.";
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
msg.iDuringTime = 12;
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
return false;
}
// check regex
if (!CNLoginServer::isPasswordGood(password)) {
// send a custom error message
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
std::string text = "Invalid password\n";
text += "Password has to be 8 - 32 characters long";
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
msg.iDuringTime = 10;
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
return false;
}
return true;
}
bool CNLoginServer::checkUsername(CNSocket* sock, std::string& username) {
// check username regex
if (!CNLoginServer::isUsernameGood(username)) {
// send a custom error message
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
std::string text = "Invalid login\n";
text += "Login has to be 4 - 32 characters long and can't contain special characters other than dash and underscore";
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
msg.iDuringTime = 10;
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
return false;
}
return true;
}
bool CNLoginServer::checkToken(CNSocket* sock, Database::Account& account, std::string& token, bool isCookieAuth) {
// check for valid cookie first
if (isCookieAuth && CNLoginServer::isAuthMethodAllowed(AuthMethod::COOKIE)) {
const char *cookie = token.c_str();
if (Database::checkCookie(account.AccountID, cookie)) {
return true;
}
}
// cookie check disabled or failed; check to see if it's a plaintext password
if (CNLoginServer::isAuthMethodAllowed(AuthMethod::PASSWORD)
&& CNLoginServer::isPasswordCorrect(account.Password, token)) {
return true;
}
return false;
}
bool CNLoginServer::checkBan(CNSocket* sock, Database::Account& account) {
// check if the account is banned
if (account.BannedUntil > getTimestamp()) {
// send a custom error message
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
// ceiling devision
int64_t remainingDays = (account.BannedUntil-getTimestamp()) / 86400 + ((account.BannedUntil - getTimestamp()) % 86400 != 0);
std::string text = "Your account has been banned. \nReason: ";
text += account.BanReason;
text += "\nBan expires in " + std::to_string(remainingDays) + " day";
if (remainingDays > 1)
text += "s";
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
msg.iDuringTime = 99999999;
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
return true;
}
return false;
}
#pragma endregion #pragma endregion

View File

@@ -1,18 +1,11 @@
#pragma once #pragma once
#include "core/Core.hpp" #include "core/Core.hpp"
#include "db/Database.hpp"
#include "Player.hpp" #include "Player.hpp"
#include <map> #include <map>
namespace LoginServer {
extern std::vector<std::string> WheelFirstNames;
extern std::vector<std::string> WheelMiddleNames;
extern std::vector<std::string> WheelLastNames;
}
struct CNLoginData { struct CNLoginData {
int userID; int userID;
time_t lastHeartbeat; time_t lastHeartbeat;
@@ -30,9 +23,7 @@ enum class LoginError {
UPDATED_EUALA_REQUIRED = 9 UPDATED_EUALA_REQUIRED = 9
}; };
#define USE_COOKIE_FIELDS 2 enum class LoginType {
enum class AuthMethod {
PASSWORD = 1, PASSWORD = 1,
COOKIE = 2 COOKIE = 2
}; };
@@ -53,18 +44,12 @@ private:
static void changeName(CNSocket* sock, CNPacketData* data); static void changeName(CNSocket* sock, CNPacketData* data);
static void duplicateExit(CNSocket* sock, CNPacketData* data); static void duplicateExit(CNSocket* sock, CNPacketData* data);
static bool isUsernameGood(std::string& login); static bool isUsernameGood(std::string login);
static bool isPasswordGood(std::string& password); static bool isPasswordGood(std::string password);
static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword); static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword);
static bool isAccountInUse(int accountId); static bool isAccountInUse(int accountId);
static bool isNameWheelNameGood(int fnCode, int mnCode, int lnCode, std::string& firstName, std::string& lastName);
static bool isCharacterNameGood(std::string Firstname, std::string Lastname); static bool isCharacterNameGood(std::string Firstname, std::string Lastname);
static bool isAuthMethodAllowed(AuthMethod authMethod); static bool isLoginTypeAllowed(LoginType loginType);
static bool checkUsername(CNSocket* sock, std::string& username);
static bool checkPassword(CNSocket* sock, std::string& password);
static bool checkToken(CNSocket* sock, Database::Account& account, std::string& token, bool isCookieAuth);
static bool checkBan(CNSocket* sock, Database::Account& account);
static void newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC); static void newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC);
// returns true if success // returns true if success
static bool exitDuplicate(int accountId); static bool exitDuplicate(int accountId);

View File

@@ -14,13 +14,6 @@ static std::mutex sockLock; // guards socket list
static std::list<SOCKET> sockets; static std::list<SOCKET> sockets;
static sockaddr_in address; static sockaddr_in address;
std::vector<std::string> Monitor::chats;
std::vector<std::string> Monitor::bcasts;
std::vector<std::string> Monitor::emails;
std::vector<std::string> Monitor::namereqs;
using namespace Monitor;
static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) { static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) {
int n = 0; int n = 0;
int sock = *it; int sock = *it;
@@ -106,23 +99,15 @@ outer:
} }
// chat // chat
for (auto& str : chats) { for (auto& str : Chat::dump) {
n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str()); n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str());
if (!transmit(it, buff, n)) if (!transmit(it, buff, n))
goto outer; goto outer;
} }
// announcements
for (auto& str : bcasts) {
n = std::snprintf(buff, sizeof(buff), "bcast %s\n", str.c_str());
if (!transmit(it, buff, n))
goto outer;
}
// emails // emails
for (auto& str : emails) { for (auto& str : Email::dump) {
n = process_email(buff, str); n = process_email(buff, str);
if (!transmit(it, buff, n)) if (!transmit(it, buff, n))
@@ -132,24 +117,14 @@ outer:
goto outer; goto outer;
} }
// name requests
for (auto& str : namereqs) {
n = std::snprintf(buff, sizeof(buff), "namereq %s\n", str.c_str());
if (!transmit(it, buff, n))
goto outer;
}
if (!transmit(it, (char*)"end\n", 4)) if (!transmit(it, (char*)"end\n", 4))
continue; continue;
it++; it++;
} }
chats.clear(); Chat::dump.clear();
bcasts.clear(); Email::dump.clear();
emails.clear();
namereqs.clear();
} }
bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) { bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {
@@ -205,14 +180,9 @@ SOCKET Monitor::init() {
} }
address.sin_family = AF_INET; address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(settings::MONITORPORT); address.sin_port = htons(settings::MONITORPORT);
if (!inet_pton(AF_INET, settings::MONITORLISTENIP.c_str(), &address.sin_addr)) {
std::cout << "Failed to set monitor listen address" << std::endl;
printSocketError("inet_pton");
exit(1);
}
if (SOCKETERROR(bind(listener, (struct sockaddr*)&address, sizeof(address)))) { if (SOCKETERROR(bind(listener, (struct sockaddr*)&address, sizeof(address)))) {
std::cout << "Failed to bind to monitor port" << std::endl; std::cout << "Failed to bind to monitor port" << std::endl;
printSocketError("bind"); printSocketError("bind");
@@ -236,7 +206,7 @@ SOCKET Monitor::init() {
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
std::cout << "Monitor listening on " << settings::MONITORLISTENIP << ":" << settings::MONITORPORT << std::endl; std::cout << "Monitor listening on *:" << settings::MONITORPORT << std::endl;
REGISTER_SHARD_TIMER(tick, settings::MONITORINTERVAL); REGISTER_SHARD_TIMER(tick, settings::MONITORINTERVAL);

View File

@@ -3,11 +3,6 @@
#include "core/Core.hpp" #include "core/Core.hpp"
namespace Monitor { namespace Monitor {
extern std::vector<std::string> chats;
extern std::vector<std::string> bcasts;
extern std::vector<std::string> emails;
extern std::vector<std::string> namereqs;
SOCKET init(); SOCKET init();
bool acceptConnection(SOCKET, uint16_t); bool acceptConnection(SOCKET, uint16_t);
}; };

View File

@@ -12,8 +12,7 @@ bool settings::SANDBOX = true;
std::string settings::SANDBOXEXTRAPATH = ""; std::string settings::SANDBOXEXTRAPATH = "";
int settings::LOGINPORT = 23000; int settings::LOGINPORT = 23000;
bool settings::APPROVEWHEELNAMES = true; bool settings::APPROVEALLNAMES = true;
bool settings::APPROVECUSTOMNAMES = true;
bool settings::AUTOCREATEACCOUNTS = true; bool settings::AUTOCREATEACCOUNTS = true;
std::string settings::AUTHMETHODS = "password"; std::string settings::AUTHMETHODS = "password";
int settings::DBSAVEINTERVAL = 240; int settings::DBSAVEINTERVAL = 240;
@@ -65,7 +64,6 @@ bool settings::DISABLEFIRSTUSEFLAG = true;
// monitor settings // monitor settings
bool settings::MONITORENABLED = false; bool settings::MONITORENABLED = false;
int settings::MONITORPORT = 8003; int settings::MONITORPORT = 8003;
std::string settings::MONITORLISTENIP = "127.0.0.1";
int settings::MONITORINTERVAL = 5000; int settings::MONITORINTERVAL = 5000;
// event mode settings // event mode settings
@@ -90,8 +88,7 @@ void settings::init() {
SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX); SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX);
SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH); SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH);
LOGINPORT = reader.GetInteger("login", "port", LOGINPORT); LOGINPORT = reader.GetInteger("login", "port", LOGINPORT);
APPROVEWHEELNAMES = reader.GetBoolean("login", "acceptallwheelnames", APPROVEWHEELNAMES); APPROVEALLNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVEALLNAMES);
APPROVECUSTOMNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVECUSTOMNAMES);
AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS); AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS);
AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS); AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS);
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL); DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);
@@ -124,6 +121,5 @@ void settings::init() {
IZRACESCORECAPPED = reader.GetBoolean("shard", "izracescorecapped", IZRACESCORECAPPED); IZRACESCORECAPPED = reader.GetBoolean("shard", "izracescorecapped", IZRACESCORECAPPED);
MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED); MONITORENABLED = reader.GetBoolean("monitor", "enabled", MONITORENABLED);
MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT); MONITORPORT = reader.GetInteger("monitor", "port", MONITORPORT);
MONITORLISTENIP = reader.Get("monitor", "listenip", MONITORLISTENIP);
MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL); MONITORINTERVAL = reader.GetInteger("monitor", "interval", MONITORINTERVAL);
} }

View File

@@ -1,16 +1,13 @@
#pragma once #pragma once
#include <stdint.h>
#include <string> #include <string>
#include <time.h>
namespace settings { namespace settings {
extern int VERBOSITY; extern int VERBOSITY;
extern bool SANDBOX; extern bool SANDBOX;
extern std::string SANDBOXEXTRAPATH; extern std::string SANDBOXEXTRAPATH;
extern int LOGINPORT; extern int LOGINPORT;
extern bool APPROVEWHEELNAMES; extern bool APPROVEALLNAMES;
extern bool APPROVECUSTOMNAMES;
extern bool AUTOCREATEACCOUNTS; extern bool AUTOCREATEACCOUNTS;
extern std::string AUTHMETHODS; extern std::string AUTHMETHODS;
extern int DBSAVEINTERVAL; extern int DBSAVEINTERVAL;
@@ -41,7 +38,6 @@ namespace settings {
extern int EVENTMODE; extern int EVENTMODE;
extern bool MONITORENABLED; extern bool MONITORENABLED;
extern int MONITORPORT; extern int MONITORPORT;
extern std::string MONITORLISTENIP;
extern int MONITORINTERVAL; extern int MONITORINTERVAL;
extern bool DISABLEFIRSTUSEFLAG; extern bool DISABLEFIRSTUSEFLAG;
extern bool IZRACESCORECAPPED; extern bool IZRACESCORECAPPED;

View File

@@ -1,52 +0,0 @@
# Run this first if needed:
# Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
param (
# height of largest column without top bar
[Parameter(Mandatory=$true)]
[string]$protocolVersion
)
$ErrorActionPreference = 'Stop'
# check for vscmd
if ([string]::IsNullOrEmpty($env:VSCMD_VER)) {
Write-Host 'Must be run inside of VS Developer Powershell'
exit 1
}
# check for git
try {
$git_version = git --version
} catch {
Write-Host 'git not installed'
exit 1
}
# setup vcpkg
if (Test-Path -Path 'vcpkg\') {
Write-Host 'vcpkg already setup'
} else {
Write-Host 'Setting up vcpkg...'
git clone "https://github.com/microsoft/vcpkg.git"
}
if (-not (Test-Path -Path 'vcpkg\vcpkg.exe')) {
Write-Host 'Bootstrapping vcpkg...'
Start-Process -Wait -NoNewWindow -FilePath 'vcpkg\bootstrap-vcpkg.bat' -ArgumentList '-disableMetrics'
}
$env:VCPKG_ROOT='' # ignore msvc's vcpkg root, it doesn't work
$vcpkg = (Resolve-Path -Path '.\vcpkg')
Write-Host "vcpkg installed to $vcpkg"
Start-Process -Wait -NoNewWindow -FilePath 'vcpkg\vcpkg.exe' -ArgumentList 'install sqlite3:x64-windows'
# setup cmake project
if (Test-Path -Path 'build\') {
Write-Host 'cmake project already setup';
} else {
Write-Host 'Setting up cmake project...'
Start-Process -Wait -NoNewWindow -FilePath 'cmake' -ArgumentList "-B build -DPROTOCOL_VERSION=$protocolVersion -DCMAKE_TOOLCHAIN_FILE=$vcpkg\scripts\buildsystems\vcpkg.cmake"
}
Write-Host 'Done!'
$sln = (Resolve-Path -Path '.\build\OpenFusion.sln')
Write-Host "Solution file is at $sln"