mirror of
https://github.com/OpenFusionProject/OpenFusion.git
synced 2025-09-26 01:50:23 +00:00
Compare commits
13 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
0078be8e9a | ||
b617456aa1
|
|||
935ee1bf6f
|
|||
43a2504357
|
|||
ca196bf620
|
|||
6b9ae4c325
|
|||
d06c324aa3
|
|||
052196d1cd
|
|||
e84f6505b8
|
|||
b483bf7190
|
|||
6ff51685a8
|
|||
b4ed31d4fb
|
|||
36e0667ed2 |
14
.github/workflows/check-builds.yaml
vendored
14
.github/workflows/check-builds.yaml
vendored
@@ -8,6 +8,7 @@ on:
|
||||
- .github/workflows/check-builds.yaml
|
||||
- CMakeLists.txt
|
||||
- Makefile
|
||||
- tdata
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
@@ -19,7 +20,7 @@ on:
|
||||
|
||||
jobs:
|
||||
ubuntu-build:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Set environment
|
||||
run: echo "SHORT_SHA=${GITHUB_SHA::7}" >> $GITHUB_ENV
|
||||
@@ -48,6 +49,7 @@ jobs:
|
||||
Rename-Item -Path "bin/fusion" -newName "$version-fusion"
|
||||
Write-Output "Built version $version"
|
||||
}
|
||||
Copy-Item -Path "tdata" -Destination "bin/tdata" -Recurse
|
||||
Copy-Item -Path "sql" -Destination "bin/sql" -Recurse
|
||||
Copy-Item -Path "config.ini" -Destination "bin"
|
||||
shell: pwsh
|
||||
@@ -58,7 +60,7 @@ jobs:
|
||||
path: bin
|
||||
|
||||
windows-build:
|
||||
runs-on: windows-2019
|
||||
runs-on: windows-2022
|
||||
steps:
|
||||
- name: Set environment
|
||||
run: $s = $env:GITHUB_SHA.subString(0, 7); echo "SHORT_SHA=$s" >> $env:GITHUB_ENV
|
||||
@@ -73,7 +75,7 @@ jobs:
|
||||
$configurations = "Release"
|
||||
# "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"
|
||||
Enter-VsDevShell -VsInstallPath $vsPath -SkipAutomaticLocation
|
||||
@@ -100,6 +102,7 @@ jobs:
|
||||
}
|
||||
Rename-Item -Path "bin/$configuration" -newName "$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 "config.ini" -Destination "bin/$version-$configuration"
|
||||
}
|
||||
@@ -108,11 +111,11 @@ jobs:
|
||||
- name: Upload build artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: 'windows-vs2019-bin-x64-${{ env.SHORT_SHA }}'
|
||||
name: 'windows-vs2022-bin-x64-${{ env.SHORT_SHA }}'
|
||||
path: bin
|
||||
|
||||
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
|
||||
needs: [windows-build, ubuntu-build]
|
||||
env:
|
||||
@@ -121,7 +124,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- run: |
|
||||
GITDESC=$(git describe --tags)
|
||||
|
@@ -38,4 +38,4 @@ EXPOSE 23000/tcp
|
||||
EXPOSE 23001/tcp
|
||||
EXPOSE 8003/tcp
|
||||
|
||||
LABEL Name=openfusion Version=1.6.0
|
||||
LABEL Name=openfusion Version=2.0.0
|
||||
|
@@ -1,6 +1,6 @@
|
||||
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
|
||||
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
|
||||
#### 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.
|
||||
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.
|
||||
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 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.
|
||||
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
|
||||
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.
|
||||
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.
|
||||
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/).
|
||||
|
||||
### 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.
|
||||
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.
|
||||
|
@@ -12,6 +12,8 @@ sandbox=true
|
||||
[login]
|
||||
# must be kept in sync with loginInfo.php
|
||||
port=23000
|
||||
# will all name wheel names be approved instantly?
|
||||
acceptallwheelnames=true
|
||||
# will all custom names be approved instantly?
|
||||
acceptallcustomnames=true
|
||||
# should attempts to log into non-existent accounts
|
||||
|
32
src/Chat.cpp
32
src/Chat.cpp
@@ -1,6 +1,7 @@
|
||||
#include "Chat.hpp"
|
||||
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "servers/Monitor.hpp"
|
||||
|
||||
#include "Player.hpp"
|
||||
#include "PlayerManager.hpp"
|
||||
@@ -8,8 +9,6 @@
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
std::vector<std::string> Chat::dump;
|
||||
|
||||
using namespace Chat;
|
||||
|
||||
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::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
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::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
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));
|
||||
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) {
|
||||
case 0: // area (all players in viewable chunks)
|
||||
sock->sendPacket(msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
PlayerManager::sendToViewable(sock, msg, P_FE2CL_GM_REP_PC_ANNOUNCE);
|
||||
break;
|
||||
case 1: // shard
|
||||
case 2: // world
|
||||
break; // not applicable to OpenFusion
|
||||
case 1: // channel
|
||||
case 2: // shard
|
||||
case 3: // global (all players)
|
||||
for (it = PlayerManager::players.begin(); it != PlayerManager::players.end(); it++) {
|
||||
CNSocket* allSock = it->first;
|
||||
@@ -120,9 +119,12 @@ static void announcementHandler(CNSocket* sock, CNPacketData* data) {
|
||||
break;
|
||||
}
|
||||
|
||||
std::string logLine = "[Bcast " + std::to_string(announcement->iAreaType) + "] " + PlayerManager::getPlayerName(plr, false) + ": " + AUTOU16TOU8(msg.szAnnounceMsg);
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back("**" + logLine + "**");
|
||||
std::string logLine = std::to_string(announcement->iAreaType) + " "
|
||||
+ std::to_string(announcement->iAnnounceType) + " "
|
||||
+ 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
|
||||
@@ -155,7 +157,7 @@ static void buddyChatHandler(CNSocket* sock, CNPacketData* data) {
|
||||
|
||||
std::string logLine = "[BuddyChat] " + PlayerManager::getPlayerName(plr) + " (to " + PlayerManager::getPlayerName(otherPlr) + "): " + fullChat;
|
||||
std::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
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::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
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::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
resp.iEmoteCode = pacdat->iEmoteCode;
|
||||
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::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
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::cout << logLine << std::endl;
|
||||
dump.push_back(logLine);
|
||||
Monitor::chats.push_back(logLine);
|
||||
|
||||
// send to client
|
||||
INITSTRUCT(sP_FE2CL_REP_SEND_ALL_GROUP_MENUCHAT_MESSAGE_SUCC, resp);
|
||||
|
@@ -8,7 +8,6 @@
|
||||
#include <vector>
|
||||
|
||||
namespace Chat {
|
||||
extern std::vector<std::string> dump;
|
||||
void init();
|
||||
|
||||
void sendServerMessage(CNSocket* sock, std::string msg); // uses MOTD
|
||||
|
@@ -3,6 +3,7 @@
|
||||
#include "core/Core.hpp"
|
||||
#include "db/Database.hpp"
|
||||
#include "servers/CNShardServer.hpp"
|
||||
#include "servers/Monitor.hpp"
|
||||
|
||||
#include "PlayerManager.hpp"
|
||||
#include "Items.hpp"
|
||||
@@ -10,8 +11,6 @@
|
||||
|
||||
using namespace Email;
|
||||
|
||||
std::vector<std::string> Email::dump;
|
||||
|
||||
// New email notification
|
||||
static void emailUpdateCheck(CNSocket* sock, CNPacketData* data) {
|
||||
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::cout << logEmail << std::endl;
|
||||
dump.push_back(logEmail);
|
||||
Monitor::emails.push_back(logEmail);
|
||||
|
||||
// notification to recipient if online
|
||||
CNSocket* recipient = PlayerManager::getSockFromID(pkt->iTo_PCUID);
|
||||
|
@@ -4,7 +4,5 @@
|
||||
#include <string>
|
||||
|
||||
namespace Email {
|
||||
extern std::vector<std::string> dump;
|
||||
|
||||
void init();
|
||||
}
|
||||
|
@@ -620,6 +620,10 @@ std::string PlayerManager::getPlayerName(Player *plr, bool id) {
|
||||
if (plr == nullptr)
|
||||
return "NOT IN GAME";
|
||||
|
||||
if (plr->PCStyle.iNameCheck != 1) {
|
||||
return "Player " + std::to_string(plr->iID);
|
||||
}
|
||||
|
||||
std::string ret = "";
|
||||
if (id && plr->accountLevel <= 30)
|
||||
ret += "(GM) ";
|
||||
|
@@ -1,5 +1,7 @@
|
||||
#include "TableData.hpp"
|
||||
|
||||
#include "servers/CNLoginServer.hpp"
|
||||
|
||||
#include "NPCManager.hpp"
|
||||
#include "Missions.hpp"
|
||||
#include "Items.hpp"
|
||||
@@ -79,6 +81,25 @@ static void loadXDT(json& xdtData) {
|
||||
NPCManager::NPCData = xdtData["m_pNpcTable"]["m_pNpcData"];
|
||||
|
||||
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
|
||||
json warpData = xdtData["m_pInstanceTable"]["m_pWarpData"];
|
||||
|
||||
|
@@ -69,7 +69,7 @@ namespace Database {
|
||||
bool isNameFree(std::string firstName, std::string lastName);
|
||||
bool isSlotFree(int accountId, int slotNum);
|
||||
/// 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
|
||||
bool finishCharacter(sP_CL2LS_REQ_CHAR_CREATE* character, int accountId);
|
||||
/// returns true if query succeeded
|
||||
@@ -85,7 +85,7 @@ namespace Database {
|
||||
};
|
||||
void evaluateCustomName(int characterID, CustomName decision);
|
||||
/// 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
|
||||
void getPlayer(Player* plr, int id);
|
||||
|
@@ -2,6 +2,8 @@
|
||||
|
||||
#include "db/internal.hpp"
|
||||
|
||||
#include "servers/CNLoginServer.hpp"
|
||||
|
||||
#include "bcrypt/BCrypt.hpp"
|
||||
|
||||
void Database::findAccount(Account* account, std::string login) {
|
||||
@@ -291,7 +293,7 @@ bool Database::isSlotFree(int accountId, int slotNum) {
|
||||
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);
|
||||
|
||||
sqlite3_exec(db, "BEGIN TRANSACTION;", NULL, NULL, NULL);
|
||||
@@ -304,22 +306,17 @@ int Database::createCharacter(sP_CL2LS_REQ_SAVE_CHAR_NAME* save, int AccountID)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);
|
||||
)";
|
||||
sqlite3_stmt* stmt;
|
||||
std::string firstName = AUTOU16TOU8(save->szFirstName);
|
||||
std::string lastName = AUTOU16TOU8(save->szLastName);
|
||||
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
sqlite3_bind_int(stmt, 1, AccountID);
|
||||
sqlite3_bind_int(stmt, 2, save->iSlotNum);
|
||||
sqlite3_bind_text(stmt, 3, firstName.c_str(), -1, NULL);
|
||||
sqlite3_bind_text(stmt, 4, lastName.c_str(), -1, NULL);
|
||||
sqlite3_bind_int(stmt, 1, accountId);
|
||||
sqlite3_bind_int(stmt, 2, slot);
|
||||
sqlite3_bind_text(stmt, 3, firstName, -1, NULL);
|
||||
sqlite3_bind_text(stmt, 4, lastName, -1, NULL);
|
||||
sqlite3_bind_int(stmt, 5, settings::SPAWN_X);
|
||||
sqlite3_bind_int(stmt, 6, settings::SPAWN_Y);
|
||||
sqlite3_bind_int(stmt, 7, settings::SPAWN_Z);
|
||||
sqlite3_bind_int(stmt, 8, settings::SPAWN_ANGLE);
|
||||
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);
|
||||
|
||||
// blobs
|
||||
@@ -648,7 +645,7 @@ void Database::evaluateCustomName(int characterID, CustomName decision) {
|
||||
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);
|
||||
|
||||
const char* sql = R"(
|
||||
@@ -662,15 +659,10 @@ bool Database::changeName(sP_CL2LS_REQ_CHANGE_CHAR_NAME* save, int accountId) {
|
||||
sqlite3_stmt* stmt;
|
||||
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
|
||||
|
||||
std::string firstName = AUTOU16TOU8(save->szFirstName);
|
||||
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_text(stmt, 1, firstName, -1, NULL);
|
||||
sqlite3_bind_text(stmt, 2, lastName, -1, NULL);
|
||||
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);
|
||||
|
||||
int rc = sqlite3_step(stmt);
|
||||
|
@@ -1,4 +1,5 @@
|
||||
#include "servers/CNLoginServer.hpp"
|
||||
#include "servers/Monitor.hpp"
|
||||
|
||||
#include "core/CNShared.hpp"
|
||||
#include "db/Database.hpp"
|
||||
@@ -12,6 +13,12 @@
|
||||
|
||||
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) {
|
||||
serverType = "login";
|
||||
port = p;
|
||||
@@ -285,10 +292,29 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
|
||||
INITSTRUCT(sP_LS2CL_REP_SAVE_CHAR_NAME_SUCC, resp);
|
||||
|
||||
int errorCode = 0;
|
||||
if (!CNLoginServer::isCharacterNameGood(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
|
||||
errorCode = 4;
|
||||
} else if (!Database::isNameFree(AUTOU16TOU8(save->szFirstName), AUTOU16TOU8(save->szLastName))) {
|
||||
errorCode = 1;
|
||||
|
||||
std::string firstName = AUTOU16TOU8(save->szFirstName);
|
||||
std::string lastName = AUTOU16TOU8(save->szLastName);
|
||||
int nameCheck = 0;
|
||||
|
||||
// if FNCode isn't 0, it's a wheel name
|
||||
if (save->iFNCode != 0) {
|
||||
if (!CNLoginServer::isNameWheelNameGood(save->iFNCode, save->iMNCode, save->iLNCode, firstName, lastName)) {
|
||||
errorCode = 4;
|
||||
} else {
|
||||
nameCheck = settings:: APPROVEWHEELNAMES ? 1 : 0;
|
||||
}
|
||||
} else {
|
||||
// custom name
|
||||
nameCheck = settings::APPROVECUSTOMNAMES ? 1 : 0;
|
||||
}
|
||||
|
||||
if (errorCode == 0) {
|
||||
if (!CNLoginServer::isCharacterNameGood(firstName, lastName)) {
|
||||
errorCode = 4;
|
||||
} else if (!Database::isNameFree(firstName, lastName)) {
|
||||
errorCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (errorCode != 0) {
|
||||
@@ -306,12 +332,19 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
|
||||
if (!Database::isSlotFree(loginSessions[sock].userID, save->iSlotNum))
|
||||
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 (resp.iPC_UID == 0) {
|
||||
std::cout << "[WARN] Login Server: Database failed to create new character!" << std::endl;
|
||||
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.iGender = save->iGender;
|
||||
|
||||
@@ -327,7 +360,9 @@ void CNLoginServer::nameSave(CNSocket* sock, CNPacketData* data) {
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: new character created" << 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;
|
||||
)
|
||||
}
|
||||
|
||||
@@ -494,11 +529,30 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
|
||||
auto save = (sP_CL2LS_REQ_CHANGE_CHAR_NAME*)data->buf;
|
||||
|
||||
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) {
|
||||
@@ -513,9 +567,15 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
|
||||
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);
|
||||
|
||||
// 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);
|
||||
resp.iPC_UID = save->iPCUID;
|
||||
memcpy(resp.szFirstName, save->szFirstName, sizeof(resp.szFirstName));
|
||||
@@ -527,8 +587,10 @@ void CNLoginServer::changeName(CNSocket* sock, CNPacketData* data) {
|
||||
sock->sendPacket(resp, P_LS2CL_REP_CHANGE_CHAR_NAME_SUCC);
|
||||
|
||||
DEBUGLOG(
|
||||
std::cout << "Login Server: Name check success for character [" << save->iPCUID << "]" << std::endl;
|
||||
std::cout << "\tNew name: " << AUTOU16TOU8(save->szFirstName) << " " << AUTOU16TOU8(save->szLastName) << std::endl;
|
||||
std::cout << "Login Server: Name change request for character [" << save->iPCUID << "]" << std::endl;
|
||||
std::cout << "\tNew name: " << firstName << " " << lastName;
|
||||
if (nameCheck != 1) std::cout << " (pending approval)";
|
||||
std::cout << std::endl;
|
||||
)
|
||||
}
|
||||
|
||||
@@ -621,6 +683,42 @@ bool CNLoginServer::isPasswordCorrect(std::string actualPassword, std::string tr
|
||||
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) {
|
||||
//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}(?! +))*$)");
|
||||
|
@@ -7,6 +7,12 @@
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace LoginServer {
|
||||
extern std::vector<std::string> WheelFirstNames;
|
||||
extern std::vector<std::string> WheelMiddleNames;
|
||||
extern std::vector<std::string> WheelLastNames;
|
||||
}
|
||||
|
||||
struct CNLoginData {
|
||||
int userID;
|
||||
time_t lastHeartbeat;
|
||||
@@ -51,6 +57,7 @@ private:
|
||||
static bool isPasswordGood(std::string& password);
|
||||
static bool isPasswordCorrect(std::string actualPassword, std::string tryPassword);
|
||||
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 isAuthMethodAllowed(AuthMethod authMethod);
|
||||
static bool checkUsername(CNSocket* sock, std::string& username);
|
||||
|
@@ -14,6 +14,13 @@ static std::mutex sockLock; // guards socket list
|
||||
static std::list<SOCKET> sockets;
|
||||
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) {
|
||||
int n = 0;
|
||||
int sock = *it;
|
||||
@@ -99,15 +106,23 @@ outer:
|
||||
}
|
||||
|
||||
// chat
|
||||
for (auto& str : Chat::dump) {
|
||||
for (auto& str : chats) {
|
||||
n = std::snprintf(buff, sizeof(buff), "chat %s\n", str.c_str());
|
||||
|
||||
if (!transmit(it, buff, n))
|
||||
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
|
||||
for (auto& str : Email::dump) {
|
||||
for (auto& str : emails) {
|
||||
n = process_email(buff, str);
|
||||
|
||||
if (!transmit(it, buff, n))
|
||||
@@ -117,14 +132,24 @@ 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))
|
||||
continue;
|
||||
|
||||
it++;
|
||||
}
|
||||
|
||||
Chat::dump.clear();
|
||||
Email::dump.clear();
|
||||
chats.clear();
|
||||
bcasts.clear();
|
||||
emails.clear();
|
||||
namereqs.clear();
|
||||
}
|
||||
|
||||
bool Monitor::acceptConnection(SOCKET fd, uint16_t revents) {
|
||||
|
@@ -3,6 +3,11 @@
|
||||
#include "core/Core.hpp"
|
||||
|
||||
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();
|
||||
bool acceptConnection(SOCKET, uint16_t);
|
||||
};
|
||||
|
@@ -12,7 +12,8 @@ bool settings::SANDBOX = true;
|
||||
std::string settings::SANDBOXEXTRAPATH = "";
|
||||
|
||||
int settings::LOGINPORT = 23000;
|
||||
bool settings::APPROVEALLNAMES = true;
|
||||
bool settings::APPROVEWHEELNAMES = true;
|
||||
bool settings::APPROVECUSTOMNAMES = true;
|
||||
bool settings::AUTOCREATEACCOUNTS = true;
|
||||
std::string settings::AUTHMETHODS = "password";
|
||||
int settings::DBSAVEINTERVAL = 240;
|
||||
@@ -89,7 +90,8 @@ void settings::init() {
|
||||
SANDBOX = reader.GetBoolean("", "sandbox", SANDBOX);
|
||||
SANDBOXEXTRAPATH = reader.Get("", "sandboxextrapath", SANDBOXEXTRAPATH);
|
||||
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);
|
||||
AUTHMETHODS = reader.Get("login", "authmethods", AUTHMETHODS);
|
||||
DBSAVEINTERVAL = reader.GetInteger("login", "dbsaveinterval", DBSAVEINTERVAL);
|
||||
|
@@ -9,7 +9,8 @@ namespace settings {
|
||||
extern bool SANDBOX;
|
||||
extern std::string SANDBOXEXTRAPATH;
|
||||
extern int LOGINPORT;
|
||||
extern bool APPROVEALLNAMES;
|
||||
extern bool APPROVEWHEELNAMES;
|
||||
extern bool APPROVECUSTOMNAMES;
|
||||
extern bool AUTOCREATEACCOUNTS;
|
||||
extern std::string AUTHMETHODS;
|
||||
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