mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-11-22 17:30:10 +00:00
Compare commits
15 Commits
d5dd05d14b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0078be8e9a | ||
|
b617456aa1
|
|||
|
935ee1bf6f
|
|||
|
43a2504357
|
|||
|
ca196bf620
|
|||
|
6b9ae4c325
|
|||
|
d06c324aa3
|
|||
|
052196d1cd
|
|||
|
e84f6505b8
|
|||
|
b483bf7190
|
|||
|
6ff51685a8
|
|||
|
b4ed31d4fb
|
|||
| 36e0667ed2 | |||
|
ed9fe61faf
|
|||
| 55cf3f7102 |
14
.github/workflows/check-builds.yaml
vendored
14
.github/workflows/check-builds.yaml
vendored
@@ -8,6 +8,7 @@ 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:
|
||||||
@@ -19,7 +20,7 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ubuntu-build:
|
ubuntu-build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-22.04
|
||||||
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
|
||||||
@@ -48,6 +49,7 @@ 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
|
||||||
@@ -58,7 +60,7 @@ jobs:
|
|||||||
path: bin
|
path: bin
|
||||||
|
|
||||||
windows-build:
|
windows-build:
|
||||||
runs-on: windows-2019
|
runs-on: windows-2022
|
||||||
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
|
||||||
@@ -73,7 +75,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 (x86)\Microsoft Visual Studio\2019\Enterprise"
|
$vsPath = "C:\Program Files\Microsoft Visual Studio\2022\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
|
||||||
@@ -100,6 +102,7 @@ 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"
|
||||||
}
|
}
|
||||||
@@ -108,11 +111,11 @@ jobs:
|
|||||||
- name: Upload build artifact
|
- name: Upload build artifact
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: 'windows-vs2019-bin-x64-${{ env.SHORT_SHA }}'
|
name: 'windows-vs2022-bin-x64-${{ env.SHORT_SHA }}'
|
||||||
path: bin
|
path: bin
|
||||||
|
|
||||||
copy-artifacts:
|
copy-artifacts:
|
||||||
if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'
|
if: github.event_name != 'pull_request' && (github.ref_type == 'tag' || github.ref_name == 'master')
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [windows-build, ubuntu-build]
|
needs: [windows-build, ubuntu-build]
|
||||||
env:
|
env:
|
||||||
@@ -121,7 +124,6 @@ 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)
|
||||||
|
|||||||
@@ -38,4 +38,4 @@ EXPOSE 23000/tcp
|
|||||||
EXPOSE 23001/tcp
|
EXPOSE 23001/tcp
|
||||||
EXPOSE 8003/tcp
|
EXPOSE 8003/tcp
|
||||||
|
|
||||||
LABEL Name=openfusion Version=1.6.0
|
LABEL Name=openfusion Version=2.0.0
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2020-2024 OpenFusion Contributors
|
Copyright (c) 2020-2025 OpenFusion Contributors
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
10
README.md
10
README.md
@@ -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 client installer by clicking [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.6/OpenFusionClient-1.6-Installer.exe) - choose to run the file.
|
1. Download the launcher installer by clicking [here](https://github.com/OpenFusionProject/OpenFusionLauncher/releases/latest/download/OpenFusionLauncher-Windows-Installer.exe) - choose to run the file.
|
||||||
2. After a few moments, the client should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
2. After a few moments, the launcher should open: you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
||||||
3. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
3. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
||||||
4. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 3.
|
4. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 3.
|
||||||
|
|
||||||
#### Method B: Standalone .zip file
|
#### Method B: Standalone .zip file
|
||||||
1. Download the client from [here](https://github.com/OpenFusionProject/OpenFusion/releases/download/1.6/OpenFusionClient-1.6.zip).
|
1. Download the launcher from [here](https://github.com/OpenFusionProject/OpenFusionLauncher/releases/latest/download/OpenFusionLauncher-Windows-Portable.zip).
|
||||||
2. Extract it to a folder of your choice. Note: if you are upgrading from an older version, it is preferable to start with a fresh folder rather than overwriting a previous install.
|
2. Extract it to a folder of your choice. Note: if you are upgrading from an older version, it is preferable to start with a fresh folder rather than overwriting a previous install.
|
||||||
3. Run OpenFusionClient.exe - you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
3. Run OpenFusionLauncher.exe - you will be given a choice between two public servers by default. Select the one you wish to play and click connect.
|
||||||
4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
4. To create an account, simply enter the details you wish to use at the login screen then click Log In. Do *not* click register, as this will just lead to a blank screen.
|
||||||
5. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 4.
|
5. Make a new character, and enjoy the game! Your progress will be saved automatically, and you can resume playing by entering the login details you used in step 4.
|
||||||
|
|
||||||
Instructions for getting the client to run on Linux through Wine can be found [here](https://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-1.6-Original.zip` or `OpenFusionServer-1.6-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/tag/1.6).
|
1. Grab `OpenFusionServer-Windows-Original.zip` or `OpenFusionServer-Windows-Academy.zip` from [here](https://github.com/OpenFusionProject/OpenFusion/releases/latest).
|
||||||
2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server.
|
2. Extract it to a folder of your choice, then run `winfusion.exe` (Windows) or `fusion` (Linux) to start the server.
|
||||||
3. Add a new server to the client's list:
|
3. Add a new server to the client's list:
|
||||||
1. For Description, enter anything you want. This is what will show up in the server list.
|
1. For Description, enter anything you want. This is what will show up in the server list.
|
||||||
|
|||||||
@@ -12,14 +12,16 @@ 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 login type 1 with plaintext passwords
|
# password = allow logging in with plaintext passwords
|
||||||
# cookie = allow login type 2 with one-shot auth cookies
|
# cookie = allow logging in 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
|
||||||
|
|||||||
8
sql/migration5.sql
Normal file
8
sql/migration5.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
BEGIN TRANSACTION;
|
||||||
|
-- New Columns
|
||||||
|
ALTER TABLE Accounts ADD Email TEXT DEFAULT '' NOT NULL;
|
||||||
|
ALTER TABLE Accounts ADD LastPasswordReset INTEGER DEFAULT 0 NOT NULL;
|
||||||
|
-- Update DB Version
|
||||||
|
UPDATE Meta SET Value = 6 WHERE Key = 'DatabaseVersion';
|
||||||
|
UPDATE Meta SET Value = strftime('%s', 'now') WHERE Key = 'LastMigration';
|
||||||
|
COMMIT;
|
||||||
@@ -1,14 +1,16 @@
|
|||||||
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)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
32
src/Chat.cpp
32
src/Chat.cpp
@@ -1,6 +1,7 @@
|
|||||||
#include "Chat.hpp"
|
#include "Chat.hpp"
|
||||||
|
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
#include "servers/Monitor.hpp"
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
@@ -8,8 +9,6 @@
|
|||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
std::vector<std::string> Chat::dump;
|
|
||||||
|
|
||||||
using namespace Chat;
|
using namespace Chat;
|
||||||
|
|
||||||
static void chatHandler(CNSocket* sock, CNPacketData* data) {
|
static void chatHandler(CNSocket* sock, CNPacketData* data) {
|
||||||
@@ -28,7 +27,7 @@ static void chatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
std::string logLine = "[FreeChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
std::string logLine = "[FreeChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||||
|
|
||||||
std::cout << logLine << std::endl;
|
std::cout << logLine << std::endl;
|
||||||
dump.push_back(logLine);
|
Monitor::chats.push_back(logLine);
|
||||||
|
|
||||||
// send to client
|
// send to client
|
||||||
INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_SEND_FREECHAT_MESSAGE_SUCC, resp);
|
||||||
@@ -51,7 +50,7 @@ static void menuChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
std::string logLine = "[MenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||||
|
|
||||||
std::cout << logLine << std::endl;
|
std::cout << logLine << std::endl;
|
||||||
dump.push_back(logLine);
|
Monitor::chats.push_back(logLine);
|
||||||
|
|
||||||
// send to client
|
// send to client
|
||||||
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_SEND_MENUCHAT_MESSAGE_SUCC, resp);
|
||||||
@@ -103,14 +102,14 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
memcpy(msg.szAnnounceMsg, announcement->szAnnounceMsg, sizeof(msg.szAnnounceMsg));
|
memcpy(msg.szAnnounceMsg, announcement->szAnnounceMsg, sizeof(msg.szAnnounceMsg));
|
||||||
std::map<CNSocket*, Player*>::iterator it;
|
std::map<CNSocket*, Player*>::iterator it;
|
||||||
|
|
||||||
|
// This value is completely arbitrary, but these make the most sense when you consider the architecture of the game
|
||||||
switch (announcement->iAreaType) {
|
switch (announcement->iAreaType) {
|
||||||
case 0: // area (all players in viewable chunks)
|
case 0: // area (all players in viewable chunks)
|
||||||
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||||
PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||||
break;
|
break;
|
||||||
case 1: // shard
|
case 1: // channel
|
||||||
case 2: // world
|
case 2: // shard
|
||||||
break; // not applicable to OpenFusion
|
|
||||||
case 3: // global (all players)
|
case 3: // global (all players)
|
||||||
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) {
|
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) {
|
||||||
CNSocket* allSock = it->first;
|
CNSocket* allSock = it->first;
|
||||||
@@ -120,9 +119,12 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string logLine = "[Bcast " + std::to_string(announcement->iAreaType) + "] " + PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg);
|
std::string logLine = std::to_string(announcement->iAreaType) + " "
|
||||||
std::cout << logLine << std::endl;
|
+ std::to_string(announcement->iAnnounceType) + " "
|
||||||
dump.push_back("**" + logLine + "**");
|
+ std::to_string(announcement->iDuringTime) + " "
|
||||||
|
+ PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg);
|
||||||
|
std::cout << "Broadcast " << logLine << std::endl;
|
||||||
|
Monitor::bcasts.push_back(logLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Buddy freechatting
|
// Buddy freechatting
|
||||||
@@ -155,7 +157,7 @@ static void buddyChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
||||||
std::cout << logLine << std::endl;
|
std::cout << logLine << std::endl;
|
||||||
dump.push_back(logLine);
|
Monitor::chats.push_back(logLine);
|
||||||
|
|
||||||
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||||
|
|
||||||
@@ -185,7 +187,7 @@ static void buddyMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
std::string logLine = "[BuddyMenuChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
std::string logLine = "[BuddyMenuChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
||||||
|
|
||||||
std::cout << logLine << std::endl;
|
std::cout << logLine << std::endl;
|
||||||
dump.push_back(logLine);
|
Monitor::chats.push_back(logLine);
|
||||||
|
|
||||||
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
U8toU16(fullChat, (char16_t*)&resp.szFreeChat, sizeof(resp.szFreeChat));
|
||||||
|
|
||||||
@@ -218,7 +220,7 @@ static void tradeChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
std::string logLine = "[TradeChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
std::string logLine = "[TradeChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
||||||
|
|
||||||
std::cout << logLine << std::endl;
|
std::cout << logLine << std::endl;
|
||||||
dump.push_back(logLine);
|
Monitor::chats.push_back(logLine);
|
||||||
|
|
||||||
resp.iEmoteCode = pacdat->iEmoteCode;
|
resp.iEmoteCode = pacdat->iEmoteCode;
|
||||||
sock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT);
|
sock->sendPacket(resp, P_FE2CL_REP_PC_TRADE_EMOTES_CHAT);
|
||||||
@@ -241,7 +243,7 @@ static void groupChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
std::string logLine = "[GroupChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
std::string logLine = "[GroupChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||||
std::cout << logLine << std::endl;
|
std::cout << logLine << std::endl;
|
||||||
dump.push_back(logLine);
|
Monitor::chats.push_back(logLine);
|
||||||
|
|
||||||
// send to client
|
// send to client
|
||||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_FREECHAT_MESSAGE_SUCC, resp);
|
||||||
@@ -264,7 +266,7 @@ static void groupMenuChatHandler(CNSocket* sock, CNPacketData* data) {
|
|||||||
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
std::string logLine = "[GroupMenuChat] " + PlayerManager::getPlayerName(plr, true) + ": " + fullChat;
|
||||||
|
|
||||||
std::cout << logLine << std::endl;
|
std::cout << logLine << std::endl;
|
||||||
dump.push_back(logLine);
|
Monitor::chats.push_back(logLine);
|
||||||
|
|
||||||
// send to client
|
// send to client
|
||||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);
|
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Chat {
|
namespace Chat {
|
||||||
extern std::vector<std::string> dump;
|
|
||||||
void init();
|
void init();
|
||||||
|
|
||||||
void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD
|
void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
#include "db/Database.hpp"
|
#include "db/Database.hpp"
|
||||||
#include "servers/CNShardServer.hpp"
|
#include "servers/CNShardServer.hpp"
|
||||||
|
#include "servers/Monitor.hpp"
|
||||||
|
|
||||||
#include "PlayerManager.hpp"
|
#include "PlayerManager.hpp"
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
@@ -10,8 +11,6 @@
|
|||||||
|
|
||||||
using namespace Email;
|
using namespace Email;
|
||||||
|
|
||||||
std::vector<std::string> Email::dump;
|
|
||||||
|
|
||||||
// New email notification
|
// New email notification
|
||||||
static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) {
|
static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) {
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp);
|
INITSTRUCT(sP_FE2CL_REP_PC_NEW_EMAIL, resp);
|
||||||
@@ -324,7 +323,7 @@ static void emailSend(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody;
|
std::string logEmail = "[Email] " + PlayerManager::getPlayerName(plr, true) + " (to " + PlayerManager::getPlayerName(&otherPlr, true) + "): <" + email.SubjectLine + ">\n" + email.MsgBody;
|
||||||
std::cout << logEmail << std::endl;
|
std::cout << logEmail << std::endl;
|
||||||
dump.push_back(logEmail);
|
Monitor::emails.push_back(logEmail);
|
||||||
|
|
||||||
// notification to recipient if online
|
// notification to recipient if online
|
||||||
CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID);
|
CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID);
|
||||||
|
|||||||
@@ -4,7 +4,5 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace Email {
|
namespace Email {
|
||||||
extern std::vector<std::string> dump;
|
|
||||||
|
|
||||||
void init();
|
void init();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -396,6 +396,14 @@ static void heartbeatPlayer(CNSocket* sock, CNPacketData* data) {
|
|||||||
|
|
||||||
static void exitGame(CNSocket* sock, CNPacketData* data) {
|
static void exitGame(CNSocket* sock, CNPacketData* data) {
|
||||||
auto exitData = (sP_CL2FE_REQ_PC_EXIT*)data->buf;
|
auto exitData = (sP_CL2FE_REQ_PC_EXIT*)data->buf;
|
||||||
|
|
||||||
|
// Refresh any auth cookie, in case "change character" was used
|
||||||
|
Player* plr = getPlayer(sock);
|
||||||
|
if (plr != nullptr) {
|
||||||
|
// 5 seconds should be enough to log in again
|
||||||
|
Database::refreshCookie(plr->accountId, 5);
|
||||||
|
}
|
||||||
|
|
||||||
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response);
|
INITSTRUCT(sP_FE2CL_REP_PC_EXIT_SUCC, response);
|
||||||
|
|
||||||
response.iID = exitData->iID;
|
response.iID = exitData->iID;
|
||||||
@@ -612,6 +620,10 @@ std::string PlayerManager::getPlayerName(Player *plr, bool id) {
|
|||||||
if (plr == nullptr)
|
if (plr == nullptr)
|
||||||
return "NOT IN GAME";
|
return "NOT IN GAME";
|
||||||
|
|
||||||
|
if (plr->PCStyle.iNameCheck != 1) {
|
||||||
|
return "Player " + std::to_string(plr->iID);
|
||||||
|
}
|
||||||
|
|
||||||
std::string ret = "";
|
std::string ret = "";
|
||||||
if (id && plr->accountLevel <= 30)
|
if (id && plr->accountLevel <= 30)
|
||||||
ret += "(GM) ";
|
ret += "(GM) ";
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "TableData.hpp"
|
#include "TableData.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNLoginServer.hpp"
|
||||||
|
|
||||||
#include "NPCManager.hpp"
|
#include "NPCManager.hpp"
|
||||||
#include "Missions.hpp"
|
#include "Missions.hpp"
|
||||||
#include "Items.hpp"
|
#include "Items.hpp"
|
||||||
@@ -79,6 +81,25 @@ static void loadXDT(json& xdtData) {
|
|||||||
NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"];
|
NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// load name wheel names
|
||||||
|
json firstNameData = xdtData["m_pNameTable"]["m_pFirstName"];
|
||||||
|
for (json::iterator _name = firstNameData.begin(); _name != firstNameData.end(); _name++) {
|
||||||
|
auto name = _name.value();
|
||||||
|
LoginServer::WheelFirstNames.push_back(name["m_pstrNameString"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
json middleNameData = xdtData["m_pNameTable"]["m_pMiddleName"];
|
||||||
|
for (json::iterator _name = middleNameData.begin(); _name != middleNameData.end(); _name++) {
|
||||||
|
auto name = _name.value();
|
||||||
|
LoginServer::WheelMiddleNames.push_back(name["m_pstrNameString"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
json lastNameData = xdtData["m_pNameTable"]["m_pLastName"];
|
||||||
|
for (json::iterator _name = lastNameData.begin(); _name != lastNameData.end(); _name++) {
|
||||||
|
auto name = _name.value();
|
||||||
|
LoginServer::WheelLastNames.push_back(name["m_pstrNameString"]);
|
||||||
|
}
|
||||||
|
|
||||||
// load warps
|
// load warps
|
||||||
json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"];
|
json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"];
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ std::string U16toU8(char16_t* src, size_t max);
|
|||||||
size_t U8toU16(std::string src, char16_t* des, size_t max); // returns number of char16_t that was written at des
|
size_t U8toU16(std::string src, char16_t* des, size_t max); // returns number of char16_t that was written at des
|
||||||
time_t getTime();
|
time_t getTime();
|
||||||
time_t getTimestamp();
|
time_t getTimestamp();
|
||||||
|
int timingSafeStrcmp(const char* a, const char* b);
|
||||||
void terminate(int);
|
void terminate(int);
|
||||||
|
|
||||||
// The PROTOCOL_VERSION definition can be defined by the build system.
|
// The PROTOCOL_VERSION definition can be defined by the build system.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#define DATABASE_VERSION 5
|
#define DATABASE_VERSION 6
|
||||||
|
|
||||||
namespace Database {
|
namespace Database {
|
||||||
|
|
||||||
@@ -56,6 +56,7 @@ 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);
|
||||||
@@ -68,7 +69,7 @@ namespace Database {
|
|||||||
bool isNameFree(std::string firstName, std::string lastName);
|
bool isNameFree(std::string firstName, std::string lastName);
|
||||||
bool isSlotFree(int accountId, int slotNum);
|
bool isSlotFree(int accountId, int slotNum);
|
||||||
/// returns ID, 0 if something failed
|
/// returns ID, 0 if something failed
|
||||||
int createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID);
|
int createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck);
|
||||||
/// returns true if query succeeded
|
/// returns true if query succeeded
|
||||||
bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId);
|
bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId);
|
||||||
/// returns true if query succeeded
|
/// returns true if query succeeded
|
||||||
@@ -84,7 +85,7 @@ namespace Database {
|
|||||||
};
|
};
|
||||||
void evaluateCustomName(int characterID, CustomName decision);
|
void evaluateCustomName(int characterID, CustomName decision);
|
||||||
/// returns true if query succeeded
|
/// returns true if query succeeded
|
||||||
bool changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId);
|
bool changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck);
|
||||||
|
|
||||||
// getting players
|
// getting players
|
||||||
void getPlayer(Player* plr, int id);
|
void getPlayer(Player* plr, int id);
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
#include "core/CNStructs.hpp"
|
||||||
|
|
||||||
#include "db/internal.hpp"
|
#include "db/internal.hpp"
|
||||||
|
|
||||||
|
#include "servers/CNLoginServer.hpp"
|
||||||
|
|
||||||
#include "bcrypt/BCrypt.hpp"
|
#include "bcrypt/BCrypt.hpp"
|
||||||
|
|
||||||
void Database::findAccount(Account* account, std::string login) {
|
void Database::findAccount(Account* account, std::string login) {
|
||||||
@@ -130,23 +134,46 @@ 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);
|
/*
|
||||||
sqlite3_bind_int(stmt, 1, accountId);
|
* Only invalidate the cookie if it was correct. This prevents
|
||||||
rc = sqlite3_step(stmt);
|
* replay attacks without enabling DOS attacks on accounts.
|
||||||
sqlite3_finalize(stmt);
|
*/
|
||||||
if (rc != SQLITE_DONE)
|
if (match) {
|
||||||
std::cout << "[WARN] Database fail on checkCookie(): " << sqlite3_errmsg(db) << std::endl;
|
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;
|
return match;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Database::refreshCookie(int accountId, int durationSec) {
|
||||||
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
|
const char* sql = R"(
|
||||||
|
UPDATE Auth
|
||||||
|
SET Expires = ?
|
||||||
|
WHERE AccountID = ?;
|
||||||
|
)";
|
||||||
|
|
||||||
|
int expires = getTimestamp() + durationSec;
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt;
|
||||||
|
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
|
sqlite3_bind_int(stmt, 1, expires);
|
||||||
|
sqlite3_bind_int(stmt, 2, accountId);
|
||||||
|
int rc = sqlite3_step(stmt);
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
if (rc != SQLITE_DONE)
|
||||||
|
std::cout << "[WARN] Database fail on refreshCookie(): " << sqlite3_errmsg(db) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
void Database::updateSelected(int accountId, int slot) {
|
void Database::updateSelected(int accountId, int slot) {
|
||||||
std::lock_guard<std::mutex> lock(dbCrit);
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
@@ -266,7 +293,7 @@ bool Database::isSlotFree(int accountId, int slotNum) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID) {
|
int Database::createCharacter(int slot, int accountId, const char* firstName, const char* lastName, int nameCheck) {
|
||||||
std::lock_guard<std::mutex> lock(dbCrit);
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||||
@@ -279,22 +306,17 @@ int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID)
|
|||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
)";
|
)";
|
||||||
sqlite3_stmt* stmt;
|
sqlite3_stmt* stmt;
|
||||||
std::string firstName = AUTOU16TOU8(save->szFirstName);
|
|
||||||
std::string lastName = AUTOU16TOU8(save->szLastName);
|
|
||||||
|
|
||||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
sqlite3_bind_int(stmt, 1, AccountID);
|
sqlite3_bind_int(stmt, 1, accountId);
|
||||||
sqlite3_bind_int(stmt, 2, save->iSlotNum);
|
sqlite3_bind_int(stmt, 2, slot);
|
||||||
sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, NULL);
|
sqlite3_bind_text(stmt, 3, firstName, -1, NULL);
|
||||||
sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, NULL);
|
sqlite3_bind_text(stmt, 4, lastName, -1, NULL);
|
||||||
sqlite3_bind_int(stmt, 5, settings::SPAWN_X);
|
sqlite3_bind_int(stmt, 5, settings::SPAWN_X);
|
||||||
sqlite3_bind_int(stmt, 6, settings::SPAWN_Y);
|
sqlite3_bind_int(stmt, 6, settings::SPAWN_Y);
|
||||||
sqlite3_bind_int(stmt, 7, settings::SPAWN_Z);
|
sqlite3_bind_int(stmt, 7, settings::SPAWN_Z);
|
||||||
sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE);
|
sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE);
|
||||||
sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1));
|
sqlite3_bind_int(stmt, 9, PC_MAXHEALTH(1));
|
||||||
|
|
||||||
// if FNCode isn't 0, it's a wheel name
|
|
||||||
int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0;
|
|
||||||
sqlite3_bind_int(stmt, 10, nameCheck);
|
sqlite3_bind_int(stmt, 10, nameCheck);
|
||||||
|
|
||||||
// blobs
|
// blobs
|
||||||
@@ -623,7 +645,7 @@ void Database::evaluateCustomName(int characterID, CustomName decision) {
|
|||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) {
|
bool Database::changeName(int playerId, int accountId, const char* firstName, const char* lastName, int nameCheck) {
|
||||||
std::lock_guard<std::mutex> lock(dbCrit);
|
std::lock_guard<std::mutex> lock(dbCrit);
|
||||||
|
|
||||||
const char* sql = R"(
|
const char* sql = R"(
|
||||||
@@ -637,15 +659,10 @@ bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) {
|
|||||||
sqlite3_stmt* stmt;
|
sqlite3_stmt* stmt;
|
||||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||||
|
|
||||||
std::string firstName = AUTOU16TOU8(save->szFirstName);
|
sqlite3_bind_text(stmt, 1, firstName, -1, NULL);
|
||||||
std::string lastName = AUTOU16TOU8(save->szLastName);
|
sqlite3_bind_text(stmt, 2, lastName, -1, NULL);
|
||||||
|
|
||||||
sqlite3_bind_text(stmt, 1, firstName.c_str(), -1, NULL);
|
|
||||||
sqlite3_bind_text(stmt, 2, lastName.c_str(), -1, NULL);
|
|
||||||
// if FNCode isn't 0, it's a wheel name
|
|
||||||
int nameCheck = (settings::APPROVEALLNAMES || save->iFNCode) ? 1 : 0;
|
|
||||||
sqlite3_bind_int(stmt, 3, nameCheck);
|
sqlite3_bind_int(stmt, 3, nameCheck);
|
||||||
sqlite3_bind_int(stmt, 4, save->iPCUID);
|
sqlite3_bind_int(stmt, 4, playerId);
|
||||||
sqlite3_bind_int(stmt, 5, accountId);
|
sqlite3_bind_int(stmt, 5, accountId);
|
||||||
|
|
||||||
int rc = sqlite3_step(stmt);
|
int rc = sqlite3_step(stmt);
|
||||||
|
|||||||
11
src/main.cpp
11
src/main.cpp
@@ -222,6 +222,17 @@ time_t getTimestamp() {
|
|||||||
return (time_t)value.count();
|
return (time_t)value.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// timing safe strcmp implementation for e.g. cookie validation
|
||||||
|
int timingSafeStrcmp(const char* a, const char* b) {
|
||||||
|
int diff = 0;
|
||||||
|
while (*a && *b) {
|
||||||
|
diff |= *a++ ^ *b++;
|
||||||
|
}
|
||||||
|
diff |= *a;
|
||||||
|
diff |= *b;
|
||||||
|
return diff;
|
||||||
|
}
|
||||||
|
|
||||||
// convert integer timestamp (in s) to FF systime struct
|
// convert integer timestamp (in s) to FF systime struct
|
||||||
sSYSTEMTIME timeStampToStruct(uint64_t time) {
|
sSYSTEMTIME timeStampToStruct(uint64_t time) {
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "servers/CNLoginServer.hpp"
|
#include "servers/CNLoginServer.hpp"
|
||||||
|
#include "servers/Monitor.hpp"
|
||||||
|
|
||||||
#include "core/CNShared.hpp"
|
#include "core/CNShared.hpp"
|
||||||
#include "db/Database.hpp"
|
#include "db/Database.hpp"
|
||||||
@@ -12,6 +13,12 @@
|
|||||||
|
|
||||||
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
std::map<CNSocket*, CNLoginData> CNLoginServer::loginSessions;
|
||||||
|
|
||||||
|
namespace LoginServer {
|
||||||
|
std::vector<std::string> WheelFirstNames;
|
||||||
|
std::vector<std::string> WheelMiddleNames;
|
||||||
|
std::vector<std::string> WheelLastNames;
|
||||||
|
}
|
||||||
|
|
||||||
CNLoginServer::CNLoginServer(uint16_t p) {
|
CNLoginServer::CNLoginServer(uint16_t p) {
|
||||||
serverType = "login";
|
serverType = "login";
|
||||||
port = p;
|
port = p;
|
||||||
@@ -109,11 +116,17 @@ 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 (login->iLoginType == (int32_t)LoginType::COOKIE) {
|
if (isCookieAuth) {
|
||||||
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 {
|
||||||
@@ -121,99 +134,41 @@ void CNLoginServer::login(CNSocket* sock, CNPacketData* data) {
|
|||||||
userToken = std::string(AUTOU16TOU8(login->szPassword).c_str());
|
userToken = std::string(AUTOU16TOU8(login->szPassword).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
// check username regex
|
if (!CNLoginServer::checkUsername(sock, userLogin)) {
|
||||||
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) {
|
||||||
// bail if password auth isn't allowed
|
// password was sent in plaintext
|
||||||
if (!CNLoginServer::isLoginTypeAllowed(LoginType::PASSWORD)) {
|
if (!CNLoginServer::checkPassword(sock, userToken)) {
|
||||||
// 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Database::Account findUser = {};
|
Database::Account findUser = {};
|
||||||
Database::findAccount(&findUser, userLogin);
|
Database::findAccount(&findUser, userLogin);
|
||||||
|
|
||||||
// account was not found
|
// account was not found
|
||||||
if (findUser.AccountID == 0) {
|
if (findUser.AccountID == 0) {
|
||||||
// don't auto-create an account if it's a cookie auth for whatever reason
|
/*
|
||||||
if (settings::AUTOCREATEACCOUNTS && !isCookieAuth)
|
* Don't auto-create accounts if it's a cookie login.
|
||||||
|
* It'll either be a bad cookie or a plaintext password sent by auto-login;
|
||||||
|
* either way, we only want to allow auto-creation if the user explicitly entered their credentials.
|
||||||
|
*/
|
||||||
|
if (settings::AUTOCREATEACCOUNTS && !isCookieAuth) {
|
||||||
return newAccount(sock, userLogin, userToken, login->iClientVerC);
|
return newAccount(sock, userLogin, userToken, login->iClientVerC);
|
||||||
|
}
|
||||||
|
|
||||||
return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock);
|
return loginFail(LoginError::ID_DOESNT_EXIST, userLogin, sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isCookieAuth) {
|
// make sure either a valid cookie or password was sent
|
||||||
const char *cookie = userToken.c_str();
|
if (!CNLoginServer::checkToken(sock, findUser, userToken, isCookieAuth)) {
|
||||||
if (!Database::checkCookie(findUser.AccountID, cookie))
|
return loginFail(LoginError::ID_AND_PASSWORD_DO_NOT_MATCH, userLogin, sock);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// is the account banned
|
if (CNLoginServer::checkBan(sock, findUser)) {
|
||||||
if (findUser.BannedUntil > getTimestamp()) {
|
return; // don't send fail packet
|
||||||
// send a custom error message
|
|
||||||
INITSTRUCT(sP_FE2CL_GM_REP_PC_ANNOUNCE, msg);
|
|
||||||
|
|
||||||
// ceiling devision
|
|
||||||
int64_t remainingDays = (findUser.BannedUntil-getTimestamp()) / 86400 + ((findUser.BannedUntil - getTimestamp()) % 86400 != 0);
|
|
||||||
|
|
||||||
std::string text = "Your account has been banned. \nReason: ";
|
|
||||||
text += findUser.BanReason;
|
|
||||||
text += "\nBan expires in " + std::to_string(remainingDays) + " day";
|
|
||||||
if (remainingDays > 1)
|
|
||||||
text += "s";
|
|
||||||
|
|
||||||
U8toU16(text, msg.szAnnounceMsg, sizeof(msg.szAnnounceMsg));
|
|
||||||
msg.iDuringTime = 99999999;
|
|
||||||
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
|
||||||
// don't send fail packet
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -337,10 +292,29 @@ 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))) {
|
|
||||||
errorCode = 4;
|
std::string firstName = AUTOU16TOU8(save->szFirstName);
|
||||||
} else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
|
std::string lastName = AUTOU16TOU8(save->szLastName);
|
||||||
errorCode = 1;
|
int nameCheck = 0;
|
||||||
|
|
||||||
|
// if FNCode isn't 0, it's a wheel name
|
||||||
|
if (save->iFNCode != 0) {
|
||||||
|
if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) {
|
||||||
|
errorCode = 4;
|
||||||
|
} else {
|
||||||
|
nameCheck = settings:: APPROVEWHEELNAMES ? 1 : 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// custom name
|
||||||
|
nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorCode == 0) {
|
||||||
|
if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) {
|
||||||
|
errorCode = 4;
|
||||||
|
} else if (!Database::isNameFree(firstName, lastName)) {
|
||||||
|
errorCode = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorCode != 0) {
|
if (errorCode != 0) {
|
||||||
@@ -358,12 +332,19 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
|
|||||||
if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum))
|
if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum))
|
||||||
return invalidCharacter(sock);
|
return invalidCharacter(sock);
|
||||||
|
|
||||||
resp.iPC_UID = Database::createCharacter(save, loginSessions[sock].userID);
|
resp.iPC_UID = Database::createCharacter(save->iSlotNum, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck);
|
||||||
// if query somehow failed
|
// if query somehow failed
|
||||||
if (resp.iPC_UID == 0) {
|
if (resp.iPC_UID == 0) {
|
||||||
std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl;
|
std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl;
|
||||||
return invalidCharacter(sock);
|
return invalidCharacter(sock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fire name check event if needed
|
||||||
|
if (nameCheck != 1) {
|
||||||
|
std::string namereq = std::to_string(resp.iPC_UID) + " " + firstName + " " + lastName;
|
||||||
|
Monitor::namereqs.push_back(namereq);
|
||||||
|
}
|
||||||
|
|
||||||
resp.iSlotNum = save->iSlotNum;
|
resp.iSlotNum = save->iSlotNum;
|
||||||
resp.iGender = save->iGender;
|
resp.iGender = save->iGender;
|
||||||
|
|
||||||
@@ -379,7 +360,9 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
|
|||||||
DEBUGLOG(
|
DEBUGLOG(
|
||||||
std::cout << "Login Server: new character created" << std::endl;
|
std::cout << "Login Server: new character created" << std::endl;
|
||||||
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
|
std::cout << "\tSlot: " << (int)save->iSlotNum << std::endl;
|
||||||
std::cout << "\tName: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl;
|
std::cout << "\tName: " << firstName << " " << lastName;
|
||||||
|
if (nameCheck != 1) std::cout << " (pending approval)";
|
||||||
|
std::cout << std::endl;
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -546,11 +529,30 @@ 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))) {
|
|
||||||
errorCode = 4;
|
std::string firstName = AUTOU16TOU8(save->szFirstName);
|
||||||
|
std::string lastName = AUTOU16TOU8(save->szLastName);
|
||||||
|
int nameCheck = 0;
|
||||||
|
|
||||||
|
// if FNCode isn't 0, it's a wheel name
|
||||||
|
if (save->iFNCode != 0) {
|
||||||
|
if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) {
|
||||||
|
errorCode = 4;
|
||||||
|
} else {
|
||||||
|
nameCheck = settings::APPROVEWHEELNAMES ? 1 : 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// custom name
|
||||||
|
nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0;
|
||||||
}
|
}
|
||||||
else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
|
|
||||||
errorCode = 1;
|
if (errorCode == 0) {
|
||||||
|
if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) {
|
||||||
|
errorCode = 4;
|
||||||
|
}
|
||||||
|
else if (!Database::isNameFree(firstName, lastName)) {
|
||||||
|
errorCode = 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorCode != 0) {
|
if (errorCode != 0) {
|
||||||
@@ -565,9 +567,15 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Database::changeName(save, loginSessions[sock].userID))
|
if (!Database::changeName(save->iPCUID, loginSessions[sock].userID, firstName.c_str(), lastName.c_str(), nameCheck))
|
||||||
return invalidCharacter(sock);
|
return invalidCharacter(sock);
|
||||||
|
|
||||||
|
// fire name check event if needed
|
||||||
|
if (nameCheck != 1) {
|
||||||
|
std::string namereq = std::to_string(save->iPCUID) + " " + firstName + " " + lastName;
|
||||||
|
Monitor::namereqs.push_back(namereq);
|
||||||
|
}
|
||||||
|
|
||||||
INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp);
|
INITSTRUCT(sP_LS2CL_REP_CHANGE_CHAR_NAME_SUCC, resp);
|
||||||
resp.iPC_UID = save->iPCUID;
|
resp.iPC_UID = save->iPCUID;
|
||||||
memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName));
|
memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName));
|
||||||
@@ -579,8 +587,10 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
|
|||||||
sock->sendPacket(resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC);
|
sock->sendPacket(resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC);
|
||||||
|
|
||||||
DEBUGLOG(
|
DEBUGLOG(
|
||||||
std::cout << "Login Server: Name check success for character [" << save->iPCUID << "]" << std::endl;
|
std::cout << "Login Server: Name change request for character [" << save->iPCUID << "]" << std::endl;
|
||||||
std::cout << "\tNew name: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl;
|
std::cout << "\tNew name: " << firstName << " " << lastName;
|
||||||
|
if (nameCheck != 1) std::cout << " (pending approval)";
|
||||||
|
std::cout << std::endl;
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,12 +669,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));
|
||||||
}
|
}
|
||||||
@@ -673,6 +683,42 @@ 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}(?! +))*$)");
|
||||||
@@ -680,16 +726,107 @@ 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::isLoginTypeAllowed(LoginType loginType) {
|
bool CNLoginServer::isAuthMethodAllowed(AuthMethod authMethod) {
|
||||||
// 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 (loginType) {
|
switch (authMethod) {
|
||||||
case LoginType::PASSWORD:
|
case AuthMethod::PASSWORD:
|
||||||
return settings::AUTHMETHODS.find("password") != std::string::npos;
|
return settings::AUTHMETHODS.find("password") != std::string::npos;
|
||||||
case LoginType::COOKIE:
|
case AuthMethod::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
|
||||||
|
|||||||
@@ -1,11 +1,18 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
#include "db/Database.hpp"
|
||||||
|
|
||||||
#include "Player.hpp"
|
#include "Player.hpp"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
namespace LoginServer {
|
||||||
|
extern std::vector<std::string> WheelFirstNames;
|
||||||
|
extern std::vector<std::string> WheelMiddleNames;
|
||||||
|
extern std::vector<std::string> WheelLastNames;
|
||||||
|
}
|
||||||
|
|
||||||
struct CNLoginData {
|
struct CNLoginData {
|
||||||
int userID;
|
int userID;
|
||||||
time_t lastHeartbeat;
|
time_t lastHeartbeat;
|
||||||
@@ -23,7 +30,9 @@ enum class LoginError {
|
|||||||
UPDATED_EUALA_REQUIRED = 9
|
UPDATED_EUALA_REQUIRED = 9
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class LoginType {
|
#define USE_COOKIE_FIELDS 2
|
||||||
|
|
||||||
|
enum class AuthMethod {
|
||||||
PASSWORD = 1,
|
PASSWORD = 1,
|
||||||
COOKIE = 2
|
COOKIE = 2
|
||||||
};
|
};
|
||||||
@@ -44,12 +53,18 @@ private:
|
|||||||
static void changeName(CNSocket* sock, CNPacketData* data);
|
static void changeName(CNSocket* sock, CNPacketData* data);
|
||||||
static void duplicateExit(CNSocket* sock, CNPacketData* data);
|
static void duplicateExit(CNSocket* sock, CNPacketData* data);
|
||||||
|
|
||||||
static bool 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 isLoginTypeAllowed(LoginType loginType);
|
static bool isAuthMethodAllowed(AuthMethod authMethod);
|
||||||
|
static bool checkUsername(CNSocket* sock, std::string& username);
|
||||||
|
static bool checkPassword(CNSocket* sock, std::string& password);
|
||||||
|
static bool checkToken(CNSocket* sock, Database::Account& account, std::string& token, bool isCookieAuth);
|
||||||
|
static bool checkBan(CNSocket* sock, Database::Account& account);
|
||||||
|
|
||||||
static void newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC);
|
static void newAccount(CNSocket* sock, std::string userLogin, std::string userPassword, int32_t clientVerC);
|
||||||
// returns true if success
|
// returns true if success
|
||||||
static bool exitDuplicate(int accountId);
|
static bool exitDuplicate(int accountId);
|
||||||
|
|||||||
@@ -14,6 +14,13 @@ static std::mutex sockLock; // guards socket list
|
|||||||
static std::list<SOCKET> sockets;
|
static std::list<SOCKET> sockets;
|
||||||
static sockaddr_in address;
|
static sockaddr_in address;
|
||||||
|
|
||||||
|
std::vector<std::string> Monitor::chats;
|
||||||
|
std::vector<std::string> Monitor::bcasts;
|
||||||
|
std::vector<std::string> Monitor::emails;
|
||||||
|
std::vector<std::string> Monitor::namereqs;
|
||||||
|
|
||||||
|
using namespace Monitor;
|
||||||
|
|
||||||
static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) {
|
static bool transmit(std::list<SOCKET>::iterator& it, char *buff, int len) {
|
||||||
int n = 0;
|
int n = 0;
|
||||||
int sock = *it;
|
int sock = *it;
|
||||||
@@ -99,15 +106,23 @@ outer:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// chat
|
// chat
|
||||||
for (auto& str : Chat::dump) {
|
for (auto& str : chats) {
|
||||||
n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str());
|
n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str());
|
||||||
|
|
||||||
if (!transmit(it, buff, n))
|
if (!transmit(it, buff, n))
|
||||||
goto outer;
|
goto outer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// announcements
|
||||||
|
for (auto& str : bcasts) {
|
||||||
|
n = std::snprintf(buff, sizeof(buff), "bcast %s\n", str.c_str());
|
||||||
|
|
||||||
|
if (!transmit(it, buff, n))
|
||||||
|
goto outer;
|
||||||
|
}
|
||||||
|
|
||||||
// emails
|
// emails
|
||||||
for (auto& str : Email::dump) {
|
for (auto& str : emails) {
|
||||||
n = process_email(buff, str);
|
n = process_email(buff, str);
|
||||||
|
|
||||||
if (!transmit(it, buff, n))
|
if (!transmit(it, buff, n))
|
||||||
@@ -117,14 +132,24 @@ outer:
|
|||||||
goto outer;
|
goto outer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// name requests
|
||||||
|
for (auto& str : namereqs) {
|
||||||
|
n = std::snprintf(buff, sizeof(buff), "namereq %s\n", str.c_str());
|
||||||
|
|
||||||
|
if (!transmit(it, buff, n))
|
||||||
|
goto outer;
|
||||||
|
}
|
||||||
|
|
||||||
if (!transmit(it, (char*)"end\n", 4))
|
if (!transmit(it, (char*)"end\n", 4))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
|
|
||||||
Chat::dump.clear();
|
chats.clear();
|
||||||
Email::dump.clear();
|
bcasts.clear();
|
||||||
|
emails.clear();
|
||||||
|
namereqs.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {
|
bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {
|
||||||
|
|||||||
@@ -3,6 +3,11 @@
|
|||||||
#include "core/Core.hpp"
|
#include "core/Core.hpp"
|
||||||
|
|
||||||
namespace Monitor {
|
namespace Monitor {
|
||||||
|
extern std::vector<std::string> chats;
|
||||||
|
extern std::vector<std::string> bcasts;
|
||||||
|
extern std::vector<std::string> emails;
|
||||||
|
extern std::vector<std::string> namereqs;
|
||||||
|
|
||||||
SOCKET init();
|
SOCKET init();
|
||||||
bool acceptConnection(SOCKET, uint16_t);
|
bool acceptConnection(SOCKET, uint16_t);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ bool settings::SANDBOX = true;
|
|||||||
std::string settings::SANDBOXEXTRAPATH = "";
|
std::string settings::SANDBOXEXTRAPATH = "";
|
||||||
|
|
||||||
int settings::LOGINPORT = 23000;
|
int settings::LOGINPORT = 23000;
|
||||||
bool settings::APPROVEALLNAMES = true;
|
bool settings::APPROVEWHEELNAMES = true;
|
||||||
|
bool settings::APPROVECUSTOMNAMES = true;
|
||||||
bool settings::AUTOCREATEACCOUNTS = true;
|
bool settings::AUTOCREATEACCOUNTS = true;
|
||||||
std::string settings::AUTHMETHODS = "password";
|
std::string settings::AUTHMETHODS = "password";
|
||||||
int settings::DBSAVEINTERVAL = 240;
|
int settings::DBSAVEINTERVAL = 240;
|
||||||
@@ -89,7 +90,8 @@ 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);
|
||||||
APPROVEALLNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVEALLNAMES);
|
APPROVEWHEELNAMES = reader.GetBoolean("login", "acceptallwheelnames", APPROVEWHEELNAMES);
|
||||||
|
APPROVECUSTOMNAMES = reader.GetBoolean("login", "acceptallcustomnames", APPROVECUSTOMNAMES);
|
||||||
AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS);
|
AUTOCREATEACCOUNTS = reader.GetBoolean("login", "autocreateaccounts", AUTOCREATEACCOUNTS);
|
||||||
AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS);
|
AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS);
|
||||||
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);
|
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ namespace settings {
|
|||||||
extern bool SANDBOX;
|
extern bool SANDBOX;
|
||||||
extern std::string SANDBOXEXTRAPATH;
|
extern std::string SANDBOXEXTRAPATH;
|
||||||
extern int LOGINPORT;
|
extern int LOGINPORT;
|
||||||
extern bool APPROVEALLNAMES;
|
extern bool APPROVEWHEELNAMES;
|
||||||
|
extern bool APPROVECUSTOMNAMES;
|
||||||
extern bool AUTOCREATEACCOUNTS;
|
extern bool AUTOCREATEACCOUNTS;
|
||||||
extern std::string AUTHMETHODS;
|
extern std::string AUTHMETHODS;
|
||||||
extern int DBSAVEINTERVAL;
|
extern int DBSAVEINTERVAL;
|
||||||
|
|||||||
52
win-setup.ps1
Normal file
52
win-setup.ps1
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# Run this first if needed:
|
||||||
|
# Set-ExecutionPolicy -ExecutionPolicy Bypass -Scope Process
|
||||||
|
param (
|
||||||
|
# height of largest column without top bar
|
||||||
|
[Parameter(Mandatory=$true)]
|
||||||
|
[string]$protocolVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = 'Stop'
|
||||||
|
# check for vscmd
|
||||||
|
if ([string]::IsNullOrEmpty($env:VSCMD_VER)) {
|
||||||
|
Write-Host 'Must be run inside of VS Developer Powershell'
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# check for git
|
||||||
|
try {
|
||||||
|
$git_version = git --version
|
||||||
|
} catch {
|
||||||
|
Write-Host 'git not installed'
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# setup vcpkg
|
||||||
|
if (Test-Path -Path 'vcpkg\') {
|
||||||
|
Write-Host 'vcpkg already setup'
|
||||||
|
} else {
|
||||||
|
Write-Host 'Setting up vcpkg...'
|
||||||
|
git clone "https://github.com/microsoft/vcpkg.git"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path -Path 'vcpkg\vcpkg.exe')) {
|
||||||
|
Write-Host 'Bootstrapping vcpkg...'
|
||||||
|
Start-Process -Wait -NoNewWindow -FilePath 'vcpkg\bootstrap-vcpkg.bat' -ArgumentList '-disableMetrics'
|
||||||
|
}
|
||||||
|
|
||||||
|
$env:VCPKG_ROOT='' # ignore msvc's vcpkg root, it doesn't work
|
||||||
|
$vcpkg = (Resolve-Path -Path '.\vcpkg')
|
||||||
|
Write-Host "vcpkg installed to $vcpkg"
|
||||||
|
Start-Process -Wait -NoNewWindow -FilePath 'vcpkg\vcpkg.exe' -ArgumentList 'install sqlite3:x64-windows'
|
||||||
|
|
||||||
|
# setup cmake project
|
||||||
|
if (Test-Path -Path 'build\') {
|
||||||
|
Write-Host 'cmake project already setup';
|
||||||
|
} else {
|
||||||
|
Write-Host 'Setting up cmake project...'
|
||||||
|
Start-Process -Wait -NoNewWindow -FilePath 'cmake' -ArgumentList "-B build -DPROTOCOL_VERSION=$protocolVersion -DCMAKE_TOOLCHAIN_FILE=$vcpkg\scripts\buildsystems\vcpkg.cmake"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host 'Done!'
|
||||||
|
$sln = (Resolve-Path -Path '.\build\OpenFusion.sln')
|
||||||
|
Write-Host "Solution file is at $sln"
|
||||||
Reference in New Issue
Block a user