Compare commits

..

9 Commits

Author SHA1 Message Date
David Markowitz
7d62ec543a add back header 2023-07-27 20:06:12 -07:00
David Markowitz
3bab2af1a4 Merge branch 'main' into cdclient-rework 2023-07-27 20:03:12 -07:00
Wincent Holm
633b87a5e7 Merge branch 'main' into cdclient-rework 2022-10-29 21:52:08 +02:00
wincent
3031f942dc Removed log statement 2022-10-29 21:46:09 +02:00
wincent
e3e9718db5 Reverted local hardcoded NET_VERSION value. 2022-08-11 16:40:17 +02:00
wincent
56f371216b Abstracted the CDClient tables
There is now an option to utilize shared memory for some CDClient tables by adding `CD_PROVIDER_MEMORY=1` to the CMakeVariables.txt file.

Allows masterconfig.ini to specify another run command for the world server, to allow for easier debugging through `valgrind`.
2022-08-11 16:36:03 +02:00
Wincent Holm
45ae46f6f1 Merge branch 'main' into cdclient-rework 2022-07-18 16:44:53 +02:00
wincent
db7f7c60b2 Optimizations to shared memory
Load the context at start in readonly mode instead of fetching for every entry.
2022-07-10 00:40:13 +02:00
wincent
34fa43ed93 Initial concept for CDClient rework.
Utilizes `boost::interprocess` to share a map between worlds.
The master server loads the table into memory, and the worlds hook into it when they want to get an entry.

This solves the issue of skill delay, but introduces the third-party library Boost.
There should be a conversation about the introduction of Boost — it is a large library and adds extra steps to the installation.
2022-07-09 22:59:36 +02:00
511 changed files with 7716 additions and 11186 deletions

View File

@@ -14,4 +14,4 @@ EXTERNAL_IP=localhost
MARIADB_USER=darkflame
MARIADB_PASSWORD=SECRET_VALUE_CHANGE_ME
MARIADB_ROOT_PASSWORD=SECRET_VALUE_CHANGE_ME
MARIADB_DATABASE=darkflame
MARIADB_DATABASE=darkflame

3
.gitmodules vendored
View File

@@ -17,6 +17,3 @@
[submodule "thirdparty/AccountManager"]
path = thirdparty/AccountManager
url = https://github.com/DarkflameUniverse/AccountManager
[submodule "thirdparty/magic_enum"]
path = thirdparty/magic_enum
url = https://github.com/Neargye/magic_enum.git

View File

@@ -148,19 +148,16 @@ foreach (resource_file ${RESOURCE_FILES})
endforeach()
message(STATUS "Resource file integrity check complete")
# if navmeshes directory does not exist, create it
if (NOT EXISTS ${PROJECT_BINARY_DIR}/navmeshes)
file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/navmeshes)
endif()
# Copy navmesh data on first build and extract it
configure_file(
${CMAKE_SOURCE_DIR}/resources/navmeshes.zip ${PROJECT_BINARY_DIR}/navmeshes.zip
COPYONLY
)
if (NOT EXISTS ${PROJECT_BINARY_DIR}/navmeshes/)
configure_file(
${CMAKE_SOURCE_DIR}/resources/navmeshes.zip ${PROJECT_BINARY_DIR}/navmeshes.zip
COPYONLY
)
file(ARCHIVE_EXTRACT INPUT ${PROJECT_BINARY_DIR}/navmeshes.zip DESTINATION ${PROJECT_BINARY_DIR}/navmeshes)
file(REMOVE ${PROJECT_BINARY_DIR}/navmeshes.zip)
file(ARCHIVE_EXTRACT INPUT ${PROJECT_BINARY_DIR}/navmeshes.zip)
file(REMOVE ${PROJECT_BINARY_DIR}/navmeshes.zip)
endif()
# Copy vanity files on first build
set(VANITY_FILES "CREDITS.md" "INFO.md" "TESTAMENT.md" "NPC.xml")
@@ -214,12 +211,7 @@ set(INCLUDED_DIRECTORIES
"dNavigation/dTerrain"
"dZoneManager"
"dDatabase"
"dDatabase/CDClientDatabase"
"dDatabase/CDClientDatabase/CDClientTables"
"dDatabase/GameDatabase"
"dDatabase/GameDatabase/ITables"
"dDatabase/GameDatabase/MySQL"
"dDatabase/GameDatabase/MySQL/Tables"
"dDatabase/Tables"
"dNet"
"dScripts"
"dScripts/02_server"
@@ -297,7 +289,6 @@ set(INCLUDED_DIRECTORIES
"thirdparty/recastnavigation"
"thirdparty/SQLite"
"thirdparty/cpplinq"
"thirdparty/magic_enum/include"
"tests"
"tests/dCommonTests"
@@ -331,13 +322,21 @@ link_directories(${PROJECT_BINARY_DIR})
# Load all of our third party directories
add_subdirectory(thirdparty)
# Include Boost
find_package(Boost COMPONENTS interprocess)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
target_link_libraries(dGame ${Boost_LIBRARIES})
endif()
# Glob together all headers that need to be precompiled
file(
GLOB HEADERS_DDATABASE
LIST_DIRECTORIES false
${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/*.h
${PROJECT_SOURCE_DIR}/dDatabase/CDClientDatabase/CDClientTables/*.h
${PROJECT_SOURCE_DIR}/dDatabase/GameDatabase/ITables/*.h
${PROJECT_SOURCE_DIR}/dDatabase/*.h
${PROJECT_SOURCE_DIR}/dDatabase/Tables/*.h
${PROJECT_SOURCE_DIR}/thirdparty/SQLite/*.h
)

View File

@@ -1,8 +1,12 @@
PROJECT_VERSION_MAJOR=1
PROJECT_VERSION_MINOR=1
PROJECT_VERSION_PATCH=1
PROJECT_VERSION_PATCH=0
# LICENSE
LICENSE=AGPL-3.0
# The network version.
# 171023 - Darkflame Universe client
# 171022 - Unmodded client
NET_VERSION=171022
# Debugging
# Set __dynamic to 1 to enable the -rdynamic flag for the linker, yielding some symbols in crashlogs.
__dynamic=1
@@ -18,5 +22,3 @@ __maria_db_connector_compile_jobs__=1
__enable_testing__=1
# The path to OpenSSL. Change this if your OpenSSL install path is different than the default.
OPENSSL_ROOT_DIR=/usr/local/opt/openssl@3/
# Uncomment the below line to cache the entire CDClient into memory
# CDCLIENT_CACHE_ALL=1

View File

@@ -23,9 +23,6 @@ We do not recommend hosting public servers. Darkflame Universe is intended for s
### Supply of resource files
Darkflame Universe is a server emulator and does not distribute any LEGO® Universe files. A separate game client is required to setup this server emulator and play the game, which we cannot supply. Users are strongly suggested to refer to the safe checksums listed [here](#verifying-your-client-files) to see if a client will work.
## Step by step walkthrough for a single-player server
If you would like a setup for a single player server only on a Windows machine, use the [Native Windows Setup Guide by HailStorm](https://gist.github.com/HailStorm32/169df65a47a104199b5cc57d10fa57de) and skip this README.
## Steps to setup server
* [Clone this repository](#clone-the-repository)
* [Install dependencies](#install-dependencies)
@@ -182,7 +179,7 @@ If you would like to build the server faster, append `-j<number>` where number i
### Notes
Depending on your operating system, you may need to adjust some pre-processor defines in [CMakeVariables.txt](./CMakeVariables.txt) before building:
* If you are on MacOS, ensure OPENSSL_ROOT_DIR is pointing to the openssl root directory.
* If you are using a Darkflame Universe client, ensure `client_net_version` in `build/sharedconfig.ini` is changed to 171023.
* If you are using a Darkflame Universe client, ensure NET_VERSION is changed to 171023.
## Configuring your server
This server has a few steps that need to be taken to configure the server for your use case.
@@ -227,44 +224,6 @@ sudo setcap 'cap_net_bind_service=+ep' AuthServer
```
and then go to `build/masterconfig.ini` and change `use_sudo_auth` to 0.
### Linux Service
If you are running this on a linux based system, it will use your terminal to run the program interactively, preventing you using it for other tasks and requiring it to be open to run the server.
_Note: You could use screen or tmux instead for virtual terminals_
To run the server non-interactively, we can use a systemctl service by copying the following file:
```shell
cp ./systemd.example /etc/systemd/system/darkflame.service
```
Make sure to edit the file in `/etc/systemd/system/darkflame.service` and change the:
- `User` and `Group` to the user that runs the darkflame server.
- `ExecPath` to the full file path of the server executable.
To register, enable and start the service use the following commands:
- Reload the systemd manager configuration to make it aware of the new service file:
```shell
systemctl daemon-reload
```
- Start the service:
```shell
systemctl start darkflame.service
```
- Enable OR disable the service to start on boot using:
```shell
systemctl enable darkflame.service
systemctl disable darkflame.service
```
- Verify that the service is running without errors:
```shell
systemctl status darkflame.service
```
- You can also restart, stop, or check the logs of the service using journalctl
```shell
systemctl restart darkflame.service
systemctl stop darkflame.service
journalctl -xeu darkflame.service
```
### First admin user
Run `MasterServer -a` to get prompted to create an admin account. This method is only intended for the system administrator as a means to get started, do NOT use this method to create accounts for other users!
@@ -326,7 +285,6 @@ Below are known good SHA256 checksums of the client:
* `0d862f71eedcadc4494c4358261669721b40b2131101cbd6ef476c5a6ec6775b` (unpacked client, includes extra locales, rar compressed)
If the returned hash matches one of the lines above then you can continue with setting up the server. If you are using a fully downloaded and complete client from live, then it will work, but the hash above may not match. Otherwise you must obtain a full install of LEGO® Universe 1.10.64.
You must also make absolutely sure your LEGO Universe client is not in a Windows OneDrive. DLU is not and will not support a client being stored in a OneDrive, so ensure you have moved the client outside of that location.
### Darkflame Universe Client
Darkflame Universe clients identify themselves using a higher version number than the regular live clients out there.

View File

@@ -7,7 +7,7 @@
//DLU Includes:
#include "dCommonVars.h"
#include "dServer.h"
#include "Logger.h"
#include "dLogger.h"
#include "Database.h"
#include "dConfig.h"
#include "Diagnostics.h"
@@ -25,14 +25,14 @@
#include "Game.h"
namespace Game {
Logger* logger = nullptr;
dLogger* logger = nullptr;
dServer* server = nullptr;
dConfig* config = nullptr;
bool shouldShutdown = false;
std::mt19937 randomEngine;
}
Logger* SetupLogger();
dLogger* SetupLogger();
void HandlePacket(Packet* packet);
int main(int argc, char** argv) {
@@ -51,14 +51,20 @@ int main(int argc, char** argv) {
Game::logger->SetLogToConsole(Game::config->GetValue("log_to_console") != "0");
Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1");
LOG("Starting Auth server...");
LOG("Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
LOG("Compiled on: %s", __TIMESTAMP__);
Game::logger->Log("AuthServer", "Starting Auth server...");
Game::logger->Log("AuthServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
Game::logger->Log("AuthServer", "Compiled on: %s", __TIMESTAMP__);
//Connect to the MySQL Database
std::string mysql_host = Game::config->GetValue("mysql_host");
std::string mysql_database = Game::config->GetValue("mysql_database");
std::string mysql_username = Game::config->GetValue("mysql_username");
std::string mysql_password = Game::config->GetValue("mysql_password");
try {
Database::Connect();
Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password);
} catch (sql::SQLException& ex) {
LOG("Got an error while connecting to the database: %s", ex.what());
Game::logger->Log("AuthServer", "Got an error while connecting to the database: %s", ex.what());
Database::Destroy("AuthServer");
delete Game::server;
delete Game::logger;
@@ -68,13 +74,16 @@ int main(int argc, char** argv) {
//Find out the master's IP:
std::string masterIP;
uint32_t masterPort = 1500;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
auto res = stmt->executeQuery();
while (res->next()) {
masterIP = res->getString(1).c_str();
masterPort = res->getInt(2);
}
delete res;
delete stmt;
Game::randomEngine = std::mt19937(time(0));
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
@@ -94,8 +103,6 @@ int main(int argc, char** argv) {
uint32_t framesSinceMasterDisconnect = 0;
uint32_t framesSinceLastSQLPing = 0;
AuthPackets::LoadClaimCodes();
while (!Game::shouldShutdown) {
//Check if we're still connected to master:
if (!Game::server->GetIsConnectedToMaster()) {
@@ -127,12 +134,16 @@ int main(int argc, char** argv) {
//Find out the master's IP for absolutely no reason:
std::string masterIP;
uint32_t masterPort;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
auto res = stmt->executeQuery();
while (res->next()) {
masterIP = res->getString(1).c_str();
masterPort = res->getInt(2);
}
delete res;
delete stmt;
framesSinceLastSQLPing = 0;
} else framesSinceLastSQLPing++;
@@ -150,7 +161,7 @@ int main(int argc, char** argv) {
return EXIT_SUCCESS;
}
Logger* SetupLogger() {
dLogger* SetupLogger() {
std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/AuthServer_" + std::to_string(time(nullptr)) + ".log")).string();
bool logToConsole = false;
bool logDebugStatements = false;
@@ -159,7 +170,7 @@ Logger* SetupLogger() {
logDebugStatements = true;
#endif
return new Logger(logPath, logToConsole, logDebugStatements);
return new dLogger(logPath, logToConsole, logDebugStatements);
}
void HandlePacket(Packet* packet) {

View File

@@ -8,7 +8,7 @@
#include <regex>
#include "dCommonVars.h"
#include "Logger.h"
#include "dLogger.h"
#include "dConfig.h"
#include "Database.h"
#include "Game.h"
@@ -32,11 +32,15 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) {
}
//Read player names that are ok as well:
auto approvedNames = Database::Get()->GetApprovedCharacterNames();
for (auto& name : approvedNames) {
std::transform(name.begin(), name.end(), name.begin(), ::tolower); //Transform to lowercase
m_ApprovedWords.push_back(CalculateHash(name));
auto stmt = Database::CreatePreppedStmt("select name from charinfo;");
auto res = stmt->executeQuery();
while (res->next()) {
std::string line = res->getString(1).c_str();
std::transform(line.begin(), line.end(), line.begin(), ::tolower); //Transform to lowercase
m_ApprovedWords.push_back(CalculateHash(line));
}
delete res;
delete stmt;
}
dChatFilter::~dChatFilter() {
@@ -140,7 +144,7 @@ std::vector<std::pair<uint8_t, uint8_t>> dChatFilter::IsSentenceOkay(const std::
listOfBadSegments.emplace_back(position, originalSegment.length());
}
position += originalSegment.length() + 1;
position += segment.length() + 1;
}
return listOfBadSegments;

View File

@@ -1,5 +1,4 @@
set(DCHATSERVER_SOURCES
"ChatIgnoreList.cpp"
"ChatPacketHandler.cpp"
"PlayerContainer.cpp"
)

View File

@@ -1,173 +0,0 @@
#include "ChatIgnoreList.h"
#include "PlayerContainer.h"
#include "eChatInternalMessageType.h"
#include "BitStreamUtils.h"
#include "PacketUtils.h"
#include "Game.h"
#include "Logger.h"
#include "eObjectBits.h"
#include "Database.h"
// A note to future readers, The client handles all the actual ignoring logic:
// not allowing teams, rejecting DMs, friends requets etc.
// The only thing not auto-handled is instance activities force joining the team on the server.
void WriteOutgoingReplyHeader(RakNet::BitStream& bitStream, const LWOOBJID& receivingPlayer, const ChatIgnoreList::Response type) {
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receivingPlayer);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, type);
}
void ChatIgnoreList::GetIgnoreList(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerId;
inStream.Read(playerId);
auto* receiver = Game::playerContainer.GetPlayerData(playerId);
if (!receiver) {
LOG("Tried to get ignore list, but player %llu not found in container", playerId);
return;
}
if (!receiver->ignoredPlayers.empty()) {
LOG_DEBUG("Player %llu already has an ignore list", playerId);
return;
}
auto ignoreList = Database::Get()->GetIgnoreList(static_cast<uint32_t>(playerId));
if (ignoreList.empty()) {
LOG_DEBUG("Player %llu has no ignores", playerId);
return;
}
for (auto& ignoredPlayer : ignoreList) {
receiver->ignoredPlayers.push_back(IgnoreData{ ignoredPlayer.id, ignoredPlayer.name });
GeneralUtils::SetBit(receiver->ignoredPlayers.back().playerId, eObjectBits::CHARACTER);
GeneralUtils::SetBit(receiver->ignoredPlayers.back().playerId, eObjectBits::PERSISTENT);
}
CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver->playerID, ChatIgnoreList::Response::GET_IGNORE);
bitStream.Write<uint8_t>(false); // Probably is Is Free Trial, but we don't care about that
bitStream.Write<uint16_t>(0); // literally spacing due to struct alignment
bitStream.Write<uint16_t>(receiver->ignoredPlayers.size());
for (const auto& ignoredPlayer : receiver->ignoredPlayers) {
bitStream.Write(ignoredPlayer.playerId);
bitStream.Write(LUWString(ignoredPlayer.playerName, 36));
}
Game::server->Send(&bitStream, packet->systemAddress, false);
}
void ChatIgnoreList::AddIgnore(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerId;
inStream.Read(playerId);
auto* receiver = Game::playerContainer.GetPlayerData(playerId);
if (!receiver) {
LOG("Tried to get ignore list, but player %llu not found in container", playerId);
return;
}
constexpr int32_t MAX_IGNORES = 32;
if (receiver->ignoredPlayers.size() > MAX_IGNORES) {
LOG_DEBUG("Player %llu has too many ignores", playerId);
return;
}
inStream.IgnoreBytes(4); // ignore some garbage zeros idk
LUWString toIgnoreName(33);
inStream.Read(toIgnoreName);
std::string toIgnoreStr = toIgnoreName.GetAsString();
CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver->playerID, ChatIgnoreList::Response::ADD_IGNORE);
// Check if the player exists
LWOOBJID ignoredPlayerId = LWOOBJID_EMPTY;
if (toIgnoreStr == receiver->playerName || toIgnoreStr.find("[GM]") == 0) {
LOG_DEBUG("Player %llu tried to ignore themselves", playerId);
bitStream.Write(ChatIgnoreList::AddResponse::GENERAL_ERROR);
} else if (std::count(receiver->ignoredPlayers.begin(), receiver->ignoredPlayers.end(), toIgnoreStr) > 0) {
LOG_DEBUG("Player %llu is already ignoring %s", playerId, toIgnoreStr.c_str());
bitStream.Write(ChatIgnoreList::AddResponse::ALREADY_IGNORED);
} else {
// Get the playerId falling back to query if not online
auto* playerData = Game::playerContainer.GetPlayerData(toIgnoreStr);
if (!playerData) {
// Fall back to query
auto player = Database::Get()->GetCharacterInfo(toIgnoreStr);
if (!player || player->name != toIgnoreStr) {
LOG_DEBUG("Player %s not found", toIgnoreStr.c_str());
} else {
ignoredPlayerId = player->id;
}
} else {
ignoredPlayerId = playerData->playerID;
}
if (ignoredPlayerId != LWOOBJID_EMPTY) {
Database::Get()->AddIgnore(static_cast<uint32_t>(playerId), static_cast<uint32_t>(ignoredPlayerId));
GeneralUtils::SetBit(ignoredPlayerId, eObjectBits::CHARACTER);
GeneralUtils::SetBit(ignoredPlayerId, eObjectBits::PERSISTENT);
receiver->ignoredPlayers.push_back(IgnoreData{ ignoredPlayerId, toIgnoreStr });
LOG_DEBUG("Player %llu is ignoring %s", playerId, toIgnoreStr.c_str());
bitStream.Write(ChatIgnoreList::AddResponse::SUCCESS);
} else {
bitStream.Write(ChatIgnoreList::AddResponse::PLAYER_NOT_FOUND);
}
}
LUWString playerNameSend(toIgnoreStr, 33);
bitStream.Write(playerNameSend);
bitStream.Write(ignoredPlayerId);
Game::server->Send(&bitStream, packet->systemAddress, false);
}
void ChatIgnoreList::RemoveIgnore(Packet* packet) {
CINSTREAM_SKIP_HEADER;
LWOOBJID playerId;
inStream.Read(playerId);
auto* receiver = Game::playerContainer.GetPlayerData(playerId);
if (!receiver) {
LOG("Tried to get ignore list, but player %llu not found in container", playerId);
return;
}
inStream.IgnoreBytes(4); // ignore some garbage zeros idk
LUWString removedIgnoreName(33);
inStream.Read(removedIgnoreName);
std::string removedIgnoreStr = removedIgnoreName.GetAsString();
auto toRemove = std::remove(receiver->ignoredPlayers.begin(), receiver->ignoredPlayers.end(), removedIgnoreStr);
if (toRemove == receiver->ignoredPlayers.end()) {
LOG_DEBUG("Player %llu is not ignoring %s", playerId, removedIgnoreStr.c_str());
return;
}
Database::Get()->RemoveIgnore(static_cast<uint32_t>(playerId), static_cast<uint32_t>(toRemove->playerId));
receiver->ignoredPlayers.erase(toRemove, receiver->ignoredPlayers.end());
CBITSTREAM;
WriteOutgoingReplyHeader(bitStream, receiver->playerID, ChatIgnoreList::Response::REMOVE_IGNORE);
bitStream.Write<int8_t>(0);
LUWString playerNameSend(removedIgnoreStr, 33);
bitStream.Write(playerNameSend);
Game::server->Send(&bitStream, packet->systemAddress, false);
}

View File

@@ -1,27 +0,0 @@
#ifndef __CHATIGNORELIST__H__
#define __CHATIGNORELIST__H__
struct Packet;
#include <cstdint>
namespace ChatIgnoreList {
void GetIgnoreList(Packet* packet);
void AddIgnore(Packet* packet);
void RemoveIgnore(Packet* packet);
enum class Response : uint8_t {
ADD_IGNORE = 32,
REMOVE_IGNORE = 33,
GET_IGNORE = 34,
};
enum class AddResponse : uint8_t {
SUCCESS,
ALREADY_IGNORED,
PLAYER_NOT_FOUND,
GENERAL_ERROR,
};
};
#endif //!__CHATIGNORELIST__H__

View File

@@ -3,11 +3,10 @@
#include "Database.h"
#include <vector>
#include "PacketUtils.h"
#include "BitStreamUtils.h"
#include "Game.h"
#include "dServer.h"
#include "GeneralUtils.h"
#include "Logger.h"
#include "dLogger.h"
#include "eAddFriendResponseCode.h"
#include "eAddFriendResponseType.h"
#include "RakString.h"
@@ -19,29 +18,46 @@
#include "eClientMessageType.h"
#include "eGameMessageType.h"
extern PlayerContainer playerContainer;
void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//Get from the packet which player we want to do something with:
CINSTREAM_SKIP_HEADER;
LWOOBJID playerID = 0;
inStream.Read(playerID);
auto player = Game::playerContainer.GetPlayerData(playerID);
auto player = playerContainer.GetPlayerData(playerID);
if (!player) return;
auto friendsList = Database::Get()->GetFriendsList(playerID);
for (const auto& friendData : friendsList) {
//Get our friends list from the Db. Using a derived table since the friend of a player can be in either column.
std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt(
"SELECT fr.requested_player, best_friend, ci.name FROM "
"(SELECT CASE "
"WHEN player_id = ? THEN friend_id "
"WHEN friend_id = ? THEN player_id "
"END AS requested_player, best_friend FROM friends) AS fr "
"JOIN charinfo AS ci ON ci.id = fr.requested_player "
"WHERE fr.requested_player IS NOT NULL AND fr.requested_player != ?;"));
stmt->setUInt(1, static_cast<uint32_t>(playerID));
stmt->setUInt(2, static_cast<uint32_t>(playerID));
stmt->setUInt(3, static_cast<uint32_t>(playerID));
std::vector<FriendData> friends;
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery());
while (res->next()) {
FriendData fd;
fd.isFTP = false; // not a thing in DLU
fd.friendID = friendData.friendID;
fd.friendID = res->getUInt(1);
GeneralUtils::SetBit(fd.friendID, eObjectBits::PERSISTENT);
GeneralUtils::SetBit(fd.friendID, eObjectBits::CHARACTER);
fd.isBestFriend = friendData.isBestFriend; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
fd.isBestFriend = res->getInt(2) == 3; //0 = friends, 1 = left_requested, 2 = right_requested, 3 = both_accepted - are now bffs
if (fd.isBestFriend) player->countOfBestFriends += 1;
fd.friendName = friendData.friendName;
fd.friendName = res->getString(3);
//Now check if they're online:
auto fr = Game::playerContainer.GetPlayerData(fd.friendID);
auto fr = playerContainer.GetPlayerData(fd.friendID);
if (fr) {
fd.isOnline = true;
@@ -54,29 +70,34 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
fd.zoneID = LWOZONEID();
}
player->friends.push_back(fd);
friends.push_back(fd);
}
//Now, we need to send the friendlist to the server they came from:
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::GET_FRIENDS_LIST_RESPONSE);
PacketUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::GET_FRIENDS_LIST_RESPONSE);
bitStream.Write<uint8_t>(0);
bitStream.Write<uint16_t>(1); //Length of packet -- just writing one as it doesn't matter, client skips it.
bitStream.Write((uint16_t)player->friends.size());
bitStream.Write((uint16_t)friends.size());
for (auto& data : player->friends) {
for (auto& data : friends) {
data.Serialize(bitStream);
}
player->friends = friends;
SystemAddress sysAddr = player->sysAddr;
SEND_PACKET;
}
void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
auto maxNumberOfBestFriendsAsString = Game::config->GetValue("max_number_of_best_friends");
// If this config option doesn't exist, default to 5 which is what live used.
auto maxNumberOfBestFriends = maxNumberOfBestFriendsAsString != "" ? std::stoi(maxNumberOfBestFriendsAsString) : 5U;
CINSTREAM_SKIP_HEADER;
LWOOBJID requestorPlayerID;
inStream.Read(requestorPlayerID);
@@ -95,17 +116,12 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
char isBestFriendRequest{};
inStream.Read(isBestFriendRequest);
auto requestor = Game::playerContainer.GetPlayerData(requestorPlayerID);
if (!requestor) {
LOG("No requestor player %llu sent to %s found.", requestorPlayerID, playerName.c_str());
return;
}
auto requestor = playerContainer.GetPlayerData(requestorPlayerID);
if (requestor->playerName == playerName) {
SendFriendResponse(requestor, requestor, eAddFriendResponseType::MYTHRAN);
return;
};
std::unique_ptr<PlayerData> requestee(Game::playerContainer.GetPlayerData(playerName));
std::unique_ptr<PlayerData> requestee(playerContainer.GetPlayerData(playerName));
// Check if player is online first
if (isBestFriendRequest && !requestee) {
@@ -133,26 +149,35 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
// If at this point we dont have a target, then they arent online and we cant send the request.
// Send the response code that corresponds to what the error is.
if (!requestee) {
std::unique_ptr<sql::PreparedStatement> nameQuery(Database::CreatePreppedStmt("SELECT name from charinfo where name = ?;"));
nameQuery->setString(1, playerName);
std::unique_ptr<sql::ResultSet> result(nameQuery->executeQuery());
requestee.reset(new PlayerData());
requestee->playerName = playerName;
auto responseType = Database::Get()->GetCharacterInfo(playerName)
? eAddFriendResponseType::NOTONLINE
: eAddFriendResponseType::INVALIDCHARACTER;
SendFriendResponse(requestor, requestee.get(), responseType);
SendFriendResponse(requestor, requestee.get(), result->next() ? eAddFriendResponseType::NOTONLINE : eAddFriendResponseType::INVALIDCHARACTER);
return;
}
if (isBestFriendRequest) {
std::unique_ptr<sql::PreparedStatement> friendUpdate(Database::CreatePreppedStmt("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
friendUpdate->setUInt(1, static_cast<uint32_t>(requestorPlayerID));
friendUpdate->setUInt(2, static_cast<uint32_t>(requestee->playerID));
friendUpdate->setUInt(3, static_cast<uint32_t>(requestee->playerID));
friendUpdate->setUInt(4, static_cast<uint32_t>(requestorPlayerID));
std::unique_ptr<sql::ResultSet> result(friendUpdate->executeQuery());
LWOOBJID queryPlayerID = LWOOBJID_EMPTY;
LWOOBJID queryFriendID = LWOOBJID_EMPTY;
uint8_t oldBestFriendStatus{};
uint8_t bestFriendStatus{};
auto bestFriendInfo = Database::Get()->GetBestFriendStatus(requestorPlayerID, requestee->playerID);
if (bestFriendInfo) {
if (result->next()) {
// Get the IDs
LWOOBJID queryPlayerID = bestFriendInfo->playerCharacterId;
LWOOBJID queryFriendID = bestFriendInfo->friendCharacterId;
oldBestFriendStatus = bestFriendInfo->bestFriendStatus;
queryPlayerID = result->getInt(1);
queryFriendID = result->getInt(2);
oldBestFriendStatus = result->getInt(3);
bestFriendStatus = oldBestFriendStatus;
// Set the bits
@@ -173,17 +198,22 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
// Only do updates if there was a change in the bff status.
if (oldBestFriendStatus != bestFriendStatus) {
auto maxBestFriends = Game::playerContainer.GetMaxNumberOfBestFriends();
if (requestee->countOfBestFriends >= maxBestFriends || requestor->countOfBestFriends >= maxBestFriends) {
if (requestee->countOfBestFriends >= maxBestFriends) {
if (requestee->countOfBestFriends >= maxNumberOfBestFriends || requestor->countOfBestFriends >= maxNumberOfBestFriends) {
if (requestee->countOfBestFriends >= maxNumberOfBestFriends) {
SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::THEIRFRIENDLISTFULL, false);
}
if (requestor->countOfBestFriends >= maxBestFriends) {
if (requestor->countOfBestFriends >= maxNumberOfBestFriends) {
SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::YOURFRIENDSLISTFULL, false);
}
} else {
// Then update the database with this new info.
Database::Get()->SetBestFriendStatus(requestorPlayerID, requestee->playerID, bestFriendStatus);
std::unique_ptr<sql::PreparedStatement> updateQuery(Database::CreatePreppedStmt("UPDATE friends SET best_friend = ? WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
updateQuery->setUInt(1, bestFriendStatus);
updateQuery->setUInt(2, static_cast<uint32_t>(requestorPlayerID));
updateQuery->setUInt(3, static_cast<uint32_t>(requestee->playerID));
updateQuery->setUInt(4, static_cast<uint32_t>(requestee->playerID));
updateQuery->setUInt(5, static_cast<uint32_t>(requestorPlayerID));
updateQuery->executeUpdate();
// Sent the best friend update here if the value is 3
if (bestFriendStatus == 3U) {
requestee->countOfBestFriends += 1;
@@ -206,15 +236,8 @@ void ChatPacketHandler::HandleFriendRequest(Packet* packet) {
if (requestor->sysAddr != UNASSIGNED_SYSTEM_ADDRESS) SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::WAITINGAPPROVAL, true, true);
}
} else {
auto maxFriends = Game::playerContainer.GetMaxNumberOfFriends();
if (requestee->friends.size() >= maxFriends) {
SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::THEIRFRIENDLISTFULL, false);
} else if (requestor->friends.size() >= maxFriends) {
SendFriendResponse(requestor, requestee.get(), eAddFriendResponseType::YOURFRIENDSLISTFULL, false);
} else {
// Do not send this if we are requesting to be a best friend.
SendFriendRequest(requestee.get(), requestor);
}
// Do not send this if we are requesting to be a best friend.
SendFriendRequest(requestee.get(), requestor);
}
// If the player is actually a player and not a ghost one defined above, release it from being deleted.
@@ -230,8 +253,8 @@ void ChatPacketHandler::HandleFriendResponse(Packet* packet) {
std::string friendName = PacketUtils::ReadString(0x15, packet, true);
//Now to try and find both of these:
auto requestor = Game::playerContainer.GetPlayerData(playerID);
auto requestee = Game::playerContainer.GetPlayerData(friendName);
auto requestor = playerContainer.GetPlayerData(playerID);
auto requestee = playerContainer.GetPlayerData(friendName);
if (!requestor || !requestee) return;
eAddFriendResponseType serverResponseCode{};
@@ -285,7 +308,11 @@ void ChatPacketHandler::HandleFriendResponse(Packet* packet) {
requesteeData.isOnline = true;
requestor->friends.push_back(requesteeData);
Database::Get()->AddFriend(requestor->playerID, requestee->playerID);
std::unique_ptr<sql::PreparedStatement> statement(Database::CreatePreppedStmt("INSERT IGNORE INTO `friends` (`player_id`, `friend_id`, `best_friend`) VALUES (?,?,?);"));
statement->setUInt(1, static_cast<uint32_t>(requestor->playerID));
statement->setUInt(2, static_cast<uint32_t>(requestee->playerID));
statement->setInt(3, 0);
statement->execute();
}
if (serverResponseCode != eAddFriendResponseType::DECLINED) SendFriendResponse(requestor, requestee, serverResponseCode, isAlreadyBestFriends);
@@ -300,20 +327,28 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
//we'll have to query the db here to find the user, since you can delete them while they're offline.
//First, we need to find their ID:
std::unique_ptr<sql::PreparedStatement> stmt(Database::CreatePreppedStmt("SELECT id FROM charinfo WHERE name=? LIMIT 1;"));
stmt->setString(1, friendName.c_str());
LWOOBJID friendID = 0;
auto friendIdResult = Database::Get()->GetCharacterInfo(friendName);
if (friendIdResult) {
friendID = friendIdResult->id;
std::unique_ptr<sql::ResultSet> res(stmt->executeQuery());
while (res->next()) {
friendID = res->getUInt(1);
}
// Convert friendID to LWOOBJID
GeneralUtils::SetBit(friendID, eObjectBits::PERSISTENT);
GeneralUtils::SetBit(friendID, eObjectBits::CHARACTER);
Database::Get()->RemoveFriend(playerID, friendID);
std::unique_ptr<sql::PreparedStatement> deletestmt(Database::CreatePreppedStmt("DELETE FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;"));
deletestmt->setUInt(1, static_cast<uint32_t>(playerID));
deletestmt->setUInt(2, static_cast<uint32_t>(friendID));
deletestmt->setUInt(3, static_cast<uint32_t>(friendID));
deletestmt->setUInt(4, static_cast<uint32_t>(playerID));
deletestmt->execute();
//Now, we need to send an update to notify the sender (and possibly, receiver) that their friendship has been ended:
auto goonA = Game::playerContainer.GetPlayerData(playerID);
auto goonA = playerContainer.GetPlayerData(playerID);
if (goonA) {
// Remove the friend from our list of friends
for (auto friendData = goonA->friends.begin(); friendData != goonA->friends.end(); friendData++) {
@@ -326,7 +361,7 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
SendRemoveFriend(goonA, friendName, true);
}
auto goonB = Game::playerContainer.GetPlayerData(friendID);
auto goonB = playerContainer.GetPlayerData(friendID);
if (!goonB) return;
// Do it again for other person
for (auto friendData = goonB->friends.begin(); friendData != goonB->friends.end(); friendData++) {
@@ -337,7 +372,7 @@ void ChatPacketHandler::HandleRemoveFriend(Packet* packet) {
}
}
std::string goonAName = GeneralUtils::UTF16ToWTF8(Game::playerContainer.GetName(playerID));
std::string goonAName = GeneralUtils::UTF16ToWTF8(playerContainer.GetName(playerID));
SendRemoveFriend(goonB, goonAName, true);
}
@@ -346,11 +381,11 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
auto* sender = Game::playerContainer.GetPlayerData(playerID);
auto* sender = playerContainer.GetPlayerData(playerID);
if (sender == nullptr) return;
if (Game::playerContainer.GetIsMuted(sender)) return;
if (playerContainer.GetIsMuted(sender)) return;
const auto senderName = std::string(sender->playerName.c_str());
@@ -361,37 +396,37 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
std::string message = PacketUtils::ReadString(0x66, packet, true, 512);
LOG("Got a message from (%s) [%d]: %s", senderName.c_str(), channel, message.c_str());
Game::logger->Log("ChatPacketHandler", "Got a message from (%s) [%d]: %s", senderName.c_str(), channel, message.c_str());
if (channel != 8) return;
auto* team = Game::playerContainer.GetTeam(playerID);
auto* team = playerContainer.GetTeam(playerID);
if (team == nullptr) return;
for (const auto memberId : team->memberIDs) {
auto* otherMember = Game::playerContainer.GetPlayerData(memberId);
auto* otherMember = playerContainer.GetPlayerData(memberId);
if (otherMember == nullptr) return;
const auto otherName = std::string(otherMember->playerName.c_str());
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(otherMember->playerID);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::PRIVATE_CHAT_MESSAGE);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::PRIVATE_CHAT_MESSAGE);
bitStream.Write(otherMember->playerID);
bitStream.Write<uint8_t>(8);
bitStream.Write<unsigned int>(69);
bitStream.Write(LUWString(senderName));
PacketUtils::WritePacketWString(senderName, 33, &bitStream);
bitStream.Write(sender->playerID);
bitStream.Write<uint16_t>(0);
bitStream.Write<uint8_t>(0); //not mythran nametag
bitStream.Write(LUWString(otherName));
PacketUtils::WritePacketWString(otherName, 33, &bitStream);
bitStream.Write<uint8_t>(0); //not mythran for receiver
bitStream.Write<uint8_t>(0); //teams?
bitStream.Write(LUWString(message, 512));
PacketUtils::WritePacketWString(message, 512, &bitStream);
SystemAddress sysAddr = otherMember->sysAddr;
SEND_PACKET;
@@ -399,16 +434,16 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
}
void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
LWOOBJID senderID = PacketUtils::ReadS64(0x08, packet);
LWOOBJID senderID = PacketUtils::ReadPacketS64(0x08, packet);
std::string receiverName = PacketUtils::ReadString(0x66, packet, true);
std::string message = PacketUtils::ReadString(0xAA, packet, true, 512);
//Get the bois:
auto goonA = Game::playerContainer.GetPlayerData(senderID);
auto goonB = Game::playerContainer.GetPlayerData(receiverName);
auto goonA = playerContainer.GetPlayerData(senderID);
auto goonB = playerContainer.GetPlayerData(receiverName);
if (!goonA || !goonB) return;
if (Game::playerContainer.GetIsMuted(goonA)) return;
if (playerContainer.GetIsMuted(goonA)) return;
std::string goonAName = goonA->playerName.c_str();
std::string goonBName = goonB->playerName.c_str();
@@ -416,21 +451,21 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
//To the sender:
{
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(goonA->playerID);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::PRIVATE_CHAT_MESSAGE);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::PRIVATE_CHAT_MESSAGE);
bitStream.Write(goonA->playerID);
bitStream.Write<uint8_t>(7);
bitStream.Write<unsigned int>(69);
bitStream.Write(LUWString(goonAName));
PacketUtils::WritePacketWString(goonAName, 33, &bitStream);
bitStream.Write(goonA->playerID);
bitStream.Write<uint16_t>(0);
bitStream.Write<uint8_t>(0); //not mythran nametag
bitStream.Write(LUWString(goonBName));
PacketUtils::WritePacketWString(goonBName, 33, &bitStream);
bitStream.Write<uint8_t>(0); //not mythran for receiver
bitStream.Write<uint8_t>(0); //success
bitStream.Write(LUWString(message, 512));
PacketUtils::WritePacketWString(message, 512, &bitStream);
SystemAddress sysAddr = goonA->sysAddr;
SEND_PACKET;
@@ -439,21 +474,21 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) {
//To the receiver:
{
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(goonB->playerID);
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::PRIVATE_CHAT_MESSAGE);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT, eChatMessageType::PRIVATE_CHAT_MESSAGE);
bitStream.Write(goonA->playerID);
bitStream.Write<uint8_t>(7);
bitStream.Write<unsigned int>(69);
bitStream.Write(LUWString(goonAName));
PacketUtils::WritePacketWString(goonAName, 33, &bitStream);
bitStream.Write(goonA->playerID);
bitStream.Write<uint16_t>(0);
bitStream.Write<uint8_t>(0); //not mythran nametag
bitStream.Write(LUWString(goonBName));
PacketUtils::WritePacketWString(goonBName, 33, &bitStream);
bitStream.Write<uint8_t>(0); //not mythran for receiver
bitStream.Write<uint8_t>(3); //new whisper
bitStream.Write(LUWString(message, 512));
PacketUtils::WritePacketWString(message, 512, &bitStream);
SystemAddress sysAddr = goonB->sysAddr;
SEND_PACKET;
@@ -466,38 +501,38 @@ void ChatPacketHandler::HandleTeamInvite(Packet* packet) {
inStream.Read(playerID);
std::string invitedPlayer = PacketUtils::ReadString(0x14, packet, true);
auto* player = Game::playerContainer.GetPlayerData(playerID);
auto* player = playerContainer.GetPlayerData(playerID);
if (player == nullptr) {
return;
}
auto* team = Game::playerContainer.GetTeam(playerID);
auto* team = playerContainer.GetTeam(playerID);
if (team == nullptr) {
team = Game::playerContainer.CreateTeam(playerID);
team = playerContainer.CreateTeam(playerID);
}
auto* other = Game::playerContainer.GetPlayerData(invitedPlayer);
auto* other = playerContainer.GetPlayerData(invitedPlayer);
if (other == nullptr) {
return;
}
if (Game::playerContainer.GetTeam(other->playerID) != nullptr) {
if (playerContainer.GetTeam(other->playerID) != nullptr) {
return;
}
if (team->memberIDs.size() > 3) {
// no more teams greater than 4
LOG("Someone tried to invite a 5th player to a team");
Game::logger->Log("ChatPacketHandler", "Someone tried to invite a 5th player to a team");
return;
}
SendTeamInvite(other, player);
LOG("Got team invite: %llu -> %s", playerID, invitedPlayer.c_str());
Game::logger->Log("ChatPacketHandler", "Got team invite: %llu -> %s", playerID, invitedPlayer.c_str());
}
void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
@@ -511,26 +546,26 @@ void ChatPacketHandler::HandleTeamInviteResponse(Packet* packet) {
LWOOBJID leaderID = LWOOBJID_EMPTY;
inStream.Read(leaderID);
LOG("Accepted invite: %llu -> %llu (%d)", playerID, leaderID, declined);
Game::logger->Log("ChatPacketHandler", "Accepted invite: %llu -> %llu (%d)", playerID, leaderID, declined);
if (declined) {
return;
}
auto* team = Game::playerContainer.GetTeam(leaderID);
auto* team = playerContainer.GetTeam(leaderID);
if (team == nullptr) {
LOG("Failed to find team for leader (%llu)", leaderID);
Game::logger->Log("ChatPacketHandler", "Failed to find team for leader (%llu)", leaderID);
team = Game::playerContainer.GetTeam(playerID);
team = playerContainer.GetTeam(playerID);
}
if (team == nullptr) {
LOG("Failed to find team for player (%llu)", playerID);
Game::logger->Log("ChatPacketHandler", "Failed to find team for player (%llu)", playerID);
return;
}
Game::playerContainer.AddMember(team, playerID);
playerContainer.AddMember(team, playerID);
}
void ChatPacketHandler::HandleTeamLeave(Packet* packet) {
@@ -540,12 +575,12 @@ void ChatPacketHandler::HandleTeamLeave(Packet* packet) {
uint32_t size = 0;
inStream.Read(size);
auto* team = Game::playerContainer.GetTeam(playerID);
auto* team = playerContainer.GetTeam(playerID);
LOG("(%llu) leaving team", playerID);
Game::logger->Log("ChatPacketHandler", "(%llu) leaving team", playerID);
if (team != nullptr) {
Game::playerContainer.RemoveMember(team, playerID, false, false, true);
playerContainer.RemoveMember(team, playerID, false, false, true);
}
}
@@ -556,26 +591,26 @@ void ChatPacketHandler::HandleTeamKick(Packet* packet) {
std::string kickedPlayer = PacketUtils::ReadString(0x14, packet, true);
LOG("(%llu) kicking (%s) from team", playerID, kickedPlayer.c_str());
Game::logger->Log("ChatPacketHandler", "(%llu) kicking (%s) from team", playerID, kickedPlayer.c_str());
auto* kicked = Game::playerContainer.GetPlayerData(kickedPlayer);
auto* kicked = playerContainer.GetPlayerData(kickedPlayer);
LWOOBJID kickedId = LWOOBJID_EMPTY;
if (kicked != nullptr) {
kickedId = kicked->playerID;
} else {
kickedId = Game::playerContainer.GetId(GeneralUtils::UTF8ToUTF16(kickedPlayer));
kickedId = playerContainer.GetId(GeneralUtils::UTF8ToUTF16(kickedPlayer));
}
if (kickedId == LWOOBJID_EMPTY) return;
auto* team = Game::playerContainer.GetTeam(playerID);
auto* team = playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID || team->leaderID == kickedId) return;
Game::playerContainer.RemoveMember(team, kickedId, false, true, false);
playerContainer.RemoveMember(team, kickedId, false, true, false);
}
}
@@ -586,18 +621,18 @@ void ChatPacketHandler::HandleTeamPromote(Packet* packet) {
std::string promotedPlayer = PacketUtils::ReadString(0x14, packet, true);
LOG("(%llu) promoting (%s) to team leader", playerID, promotedPlayer.c_str());
Game::logger->Log("ChatPacketHandler", "(%llu) promoting (%s) to team leader", playerID, promotedPlayer.c_str());
auto* promoted = Game::playerContainer.GetPlayerData(promotedPlayer);
auto* promoted = playerContainer.GetPlayerData(promotedPlayer);
if (promoted == nullptr) return;
auto* team = Game::playerContainer.GetTeam(playerID);
auto* team = playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
Game::playerContainer.PromoteMember(team, promoted->playerID);
playerContainer.PromoteMember(team, promoted->playerID);
}
}
@@ -611,16 +646,16 @@ void ChatPacketHandler::HandleTeamLootOption(Packet* packet) {
char option;
inStream.Read(option);
auto* team = Game::playerContainer.GetTeam(playerID);
auto* team = playerContainer.GetTeam(playerID);
if (team != nullptr) {
if (team->leaderID != playerID) return;
team->lootFlag = option;
Game::playerContainer.TeamStatusUpdate(team);
playerContainer.TeamStatusUpdate(team);
Game::playerContainer.UpdateTeamsOnWorld(team, false);
playerContainer.UpdateTeamsOnWorld(team, false);
}
}
@@ -629,18 +664,18 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
LWOOBJID playerID = LWOOBJID_EMPTY;
inStream.Read(playerID);
auto* team = Game::playerContainer.GetTeam(playerID);
auto* data = Game::playerContainer.GetPlayerData(playerID);
auto* team = playerContainer.GetTeam(playerID);
auto* data = playerContainer.GetPlayerData(playerID);
if (team != nullptr && data != nullptr) {
if (team->local && data->zoneID.GetMapID() != team->zoneId.GetMapID() && data->zoneID.GetCloneID() != team->zoneId.GetCloneID()) {
Game::playerContainer.RemoveMember(team, playerID, false, false, true, true);
playerContainer.RemoveMember(team, playerID, false, false, true, true);
return;
}
if (team->memberIDs.size() <= 1 && !team->local) {
Game::playerContainer.DisbandTeam(team);
playerContainer.DisbandTeam(team);
return;
}
@@ -651,16 +686,16 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
ChatPacketHandler::SendTeamSetLeader(data, LWOOBJID_EMPTY);
}
Game::playerContainer.TeamStatusUpdate(team);
playerContainer.TeamStatusUpdate(team);
const auto leaderName = GeneralUtils::UTF8ToUTF16(data->playerName);
for (const auto memberId : team->memberIDs) {
auto* otherMember = Game::playerContainer.GetPlayerData(memberId);
auto* otherMember = playerContainer.GetPlayerData(memberId);
if (memberId == playerID) continue;
const auto memberName = Game::playerContainer.GetName(memberId);
const auto memberName = playerContainer.GetName(memberId);
if (otherMember != nullptr) {
ChatPacketHandler::SendTeamSetOffWorldFlag(otherMember, data->playerID, data->zoneID);
@@ -668,19 +703,19 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
ChatPacketHandler::SendTeamAddPlayer(data, false, team->local, false, memberId, memberName, otherMember != nullptr ? otherMember->zoneID : LWOZONEID(0, 0, 0));
}
Game::playerContainer.UpdateTeamsOnWorld(team, false);
playerContainer.UpdateTeamsOnWorld(team, false);
}
}
void ChatPacketHandler::SendTeamInvite(PlayerData* receiver, PlayerData* sender) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::TEAM_INVITE);
PacketUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::TEAM_INVITE);
bitStream.Write(LUWString(sender->playerName.c_str()));
PacketUtils::WritePacketWString(sender->playerName.c_str(), 33, &bitStream);
bitStream.Write(sender->playerID);
SystemAddress sysAddr = receiver->sysAddr;
@@ -689,7 +724,7 @@ void ChatPacketHandler::SendTeamInvite(PlayerData* receiver, PlayerData* sender)
void ChatPacketHandler::SendTeamInviteConfirm(PlayerData* receiver, bool bLeaderIsFreeTrial, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, uint8_t ucResponseCode, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
//portion that will get routed:
@@ -716,7 +751,7 @@ void ChatPacketHandler::SendTeamInviteConfirm(PlayerData* receiver, bool bLeader
void ChatPacketHandler::SendTeamStatus(PlayerData* receiver, LWOOBJID i64LeaderID, LWOZONEID i64LeaderZoneID, uint8_t ucLootFlag, uint8_t ucNumOfOtherPlayers, std::u16string wsLeaderName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
//portion that will get routed:
@@ -741,7 +776,7 @@ void ChatPacketHandler::SendTeamStatus(PlayerData* receiver, LWOOBJID i64LeaderI
void ChatPacketHandler::SendTeamSetLeader(PlayerData* receiver, LWOOBJID i64PlayerID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
//portion that will get routed:
@@ -758,7 +793,7 @@ void ChatPacketHandler::SendTeamSetLeader(PlayerData* receiver, LWOOBJID i64Play
void ChatPacketHandler::SendTeamAddPlayer(PlayerData* receiver, bool bIsFreeTrial, bool bLocal, bool bNoLootOnDeath, LWOOBJID i64PlayerID, std::u16string wsPlayerName, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
//portion that will get routed:
@@ -787,7 +822,7 @@ void ChatPacketHandler::SendTeamAddPlayer(PlayerData* receiver, bool bIsFreeTria
void ChatPacketHandler::SendTeamRemovePlayer(PlayerData* receiver, bool bDisband, bool bIsKicked, bool bIsLeaving, bool bLocal, LWOOBJID i64LeaderID, LWOOBJID i64PlayerID, std::u16string wsPlayerName) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
//portion that will get routed:
@@ -813,7 +848,7 @@ void ChatPacketHandler::SendTeamRemovePlayer(PlayerData* receiver, bool bDisband
void ChatPacketHandler::SendTeamSetOffWorldFlag(PlayerData* receiver, LWOOBJID i64PlayerID, LWOZONEID zoneID) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
//portion that will get routed:
@@ -847,16 +882,16 @@ void ChatPacketHandler::SendFriendUpdate(PlayerData* friendData, PlayerData* pla
[bool] - is FTP*/
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(friendData->playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::UPDATE_FRIEND_NOTIFY);
PacketUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::UPDATE_FRIEND_NOTIFY);
bitStream.Write<uint8_t>(notifyType);
std::string playerName = playerData->playerName.c_str();
bitStream.Write(LUWString(playerName));
PacketUtils::WritePacketWString(playerName, 33, &bitStream);
bitStream.Write(playerData->zoneID.GetMapID());
bitStream.Write(playerData->zoneID.GetInstanceID());
@@ -886,12 +921,12 @@ void ChatPacketHandler::SendFriendRequest(PlayerData* receiver, PlayerData* send
}
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::ADD_FRIEND_REQUEST);
bitStream.Write(LUWString(sender->playerName.c_str()));
PacketUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::ADD_FRIEND_REQUEST);
PacketUtils::WritePacketWString(sender->playerName.c_str(), 33, &bitStream);
bitStream.Write<uint8_t>(0); // This is a BFF flag however this is unused in live and does not have an implementation client side.
SystemAddress sysAddr = receiver->sysAddr;
@@ -902,16 +937,16 @@ void ChatPacketHandler::SendFriendResponse(PlayerData* receiver, PlayerData* sen
if (!receiver || !sender) return;
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
// Portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::ADD_FRIEND_RESPONSE);
PacketUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::ADD_FRIEND_RESPONSE);
bitStream.Write(responseCode);
// For all requests besides accepted, write a flag that says whether or not we are already best friends with the receiver.
bitStream.Write<uint8_t>(responseCode != eAddFriendResponseType::ACCEPTED ? isBestFriendsAlready : sender->sysAddr != UNASSIGNED_SYSTEM_ADDRESS);
// Then write the player name
bitStream.Write(LUWString(sender->playerName.c_str()));
PacketUtils::WritePacketWString(sender->playerName.c_str(), 33, &bitStream);
// Then if this is an acceptance code, write the following extra info.
if (responseCode == eAddFriendResponseType::ACCEPTED) {
bitStream.Write(sender->playerID);
@@ -927,13 +962,13 @@ void ChatPacketHandler::SendRemoveFriend(PlayerData* receiver, std::string& pers
if (!receiver) return;
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::ROUTE_TO_PLAYER);
bitStream.Write(receiver->playerID);
//portion that will get routed:
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::REMOVE_FRIEND_RESPONSE);
PacketUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::REMOVE_FRIEND_RESPONSE);
bitStream.Write<uint8_t>(isSuccessful); //isOnline
bitStream.Write(LUWString(personToRemove));
PacketUtils::WritePacketWString(personToRemove, 33, &bitStream);
SystemAddress sysAddr = receiver->sysAddr;
SEND_PACKET;

View File

@@ -6,7 +6,7 @@
//DLU Includes:
#include "dCommonVars.h"
#include "dServer.h"
#include "Logger.h"
#include "dLogger.h"
#include "Database.h"
#include "dConfig.h"
#include "dChatFilter.h"
@@ -19,7 +19,6 @@
#include "eChatMessageType.h"
#include "eChatInternalMessageType.h"
#include "eWorldMessageType.h"
#include "ChatIgnoreList.h"
#include "Game.h"
@@ -28,19 +27,20 @@
#include <MessageIdentifiers.h>
namespace Game {
Logger* logger = nullptr;
dLogger* logger = nullptr;
dServer* server = nullptr;
dConfig* config = nullptr;
dChatFilter* chatFilter = nullptr;
AssetManager* assetManager = nullptr;
bool shouldShutdown = false;
std::mt19937 randomEngine;
PlayerContainer playerContainer;
}
Logger* SetupLogger();
dLogger* SetupLogger();
void HandlePacket(Packet* packet);
PlayerContainer playerContainer;
int main(int argc, char** argv) {
constexpr uint32_t chatFramerate = mediumFramerate;
constexpr uint32_t chatFrameDelta = mediumFrameDelta;
@@ -57,9 +57,9 @@ int main(int argc, char** argv) {
Game::logger->SetLogToConsole(Game::config->GetValue("log_to_console") != "0");
Game::logger->SetLogDebugStatements(Game::config->GetValue("log_debug_statements") == "1");
LOG("Starting Chat server...");
LOG("Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
LOG("Compiled on: %s", __TIMESTAMP__);
Game::logger->Log("ChatServer", "Starting Chat server...");
Game::logger->Log("ChatServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
Game::logger->Log("ChatServer", "Compiled on: %s", __TIMESTAMP__);
try {
std::string clientPathStr = Game::config->GetValue("client_location");
@@ -71,16 +71,21 @@ int main(int argc, char** argv) {
Game::assetManager = new AssetManager(clientPath);
} catch (std::runtime_error& ex) {
LOG("Got an error while setting up assets: %s", ex.what());
Game::logger->Log("ChatServer", "Got an error while setting up assets: %s", ex.what());
return EXIT_FAILURE;
}
//Connect to the MySQL Database
std::string mysql_host = Game::config->GetValue("mysql_host");
std::string mysql_database = Game::config->GetValue("mysql_database");
std::string mysql_username = Game::config->GetValue("mysql_username");
std::string mysql_password = Game::config->GetValue("mysql_password");
try {
Database::Connect();
Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password);
} catch (sql::SQLException& ex) {
LOG("Got an error while connecting to the database: %s", ex.what());
Game::logger->Log("ChatServer", "Got an error while connecting to the database: %s", ex.what());
Database::Destroy("ChatServer");
delete Game::server;
delete Game::logger;
@@ -90,11 +95,16 @@ int main(int argc, char** argv) {
//Find out the master's IP:
std::string masterIP;
uint32_t masterPort = 1000;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
auto res = stmt->executeQuery();
while (res->next()) {
masterIP = res->getString(1).c_str();
masterPort = res->getInt(2);
}
delete res;
delete stmt;
//It's safe to pass 'localhost' here, as the IP is only used as the external IP.
uint32_t maxClients = 50;
uint32_t ourPort = 1501;
@@ -104,10 +114,6 @@ int main(int argc, char** argv) {
Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Chat, Game::config, &Game::shouldShutdown);
Game::chatFilter = new dChatFilter(Game::assetManager->GetResPath().string() + "/chatplus_en_us", bool(std::stoi(Game::config->GetValue("dont_generate_dcf"))));
Game::randomEngine = std::mt19937(time(0));
Game::playerContainer.Initialize();
//Run it until server gets a kill message from Master:
auto t = std::chrono::high_resolution_clock::now();
@@ -149,13 +155,16 @@ int main(int argc, char** argv) {
//Find out the master's IP for absolutely no reason:
std::string masterIP;
uint32_t masterPort;
auto masterInfo = Database::Get()->GetMasterInfo();
if (masterInfo) {
masterIP = masterInfo->ip;
masterPort = masterInfo->port;
sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT ip, port FROM servers WHERE name='master';");
auto res = stmt->executeQuery();
while (res->next()) {
masterIP = res->getString(1).c_str();
masterPort = res->getInt(2);
}
delete res;
delete stmt;
framesSinceLastSQLPing = 0;
} else framesSinceLastSQLPing++;
@@ -173,7 +182,7 @@ int main(int argc, char** argv) {
return EXIT_SUCCESS;
}
Logger* SetupLogger() {
dLogger* SetupLogger() {
std::string logPath = (BinaryPathFinder::GetBinaryDir() / ("logs/ChatServer_" + std::to_string(time(nullptr)) + ".log")).string();
bool logToConsole = false;
bool logDebugStatements = false;
@@ -182,16 +191,16 @@ Logger* SetupLogger() {
logDebugStatements = true;
#endif
return new Logger(logPath, logToConsole, logDebugStatements);
return new dLogger(logPath, logToConsole, logDebugStatements);
}
void HandlePacket(Packet* packet) {
if (packet->data[0] == ID_DISCONNECTION_NOTIFICATION || packet->data[0] == ID_CONNECTION_LOST) {
LOG("A server has disconnected, erasing their connected players from the list.");
Game::logger->Log("ChatServer", "A server has disconnected, erasing their connected players from the list.");
}
if (packet->data[0] == ID_NEW_INCOMING_CONNECTION) {
LOG("A server is connecting, awaiting user list.");
Game::logger->Log("ChatServer", "A server is connecting, awaiting user list.");
}
if (packet->length < 4) return; // Nothing left to process. Need 4 bytes to continue.
@@ -199,19 +208,19 @@ void HandlePacket(Packet* packet) {
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::CHAT_INTERNAL) {
switch (static_cast<eChatInternalMessageType>(packet->data[3])) {
case eChatInternalMessageType::PLAYER_ADDED_NOTIFICATION:
Game::playerContainer.InsertPlayer(packet);
playerContainer.InsertPlayer(packet);
break;
case eChatInternalMessageType::PLAYER_REMOVED_NOTIFICATION:
Game::playerContainer.RemovePlayer(packet);
playerContainer.RemovePlayer(packet);
break;
case eChatInternalMessageType::MUTE_UPDATE:
Game::playerContainer.MuteUpdate(packet);
playerContainer.MuteUpdate(packet);
break;
case eChatInternalMessageType::CREATE_TEAM:
Game::playerContainer.CreateTeamServer(packet);
playerContainer.CreateTeamServer(packet);
break;
case eChatInternalMessageType::ANNOUNCEMENT: {
@@ -222,7 +231,7 @@ void HandlePacket(Packet* packet) {
}
default:
LOG("Unknown CHAT_INTERNAL id: %i", int(packet->data[3]));
Game::logger->Log("ChatServer", "Unknown CHAT_INTERNAL id: %i", int(packet->data[3]));
}
}
@@ -233,15 +242,7 @@ void HandlePacket(Packet* packet) {
break;
case eChatMessageType::GET_IGNORE_LIST:
ChatIgnoreList::GetIgnoreList(packet);
break;
case eChatMessageType::ADD_IGNORE:
ChatIgnoreList::AddIgnore(packet);
break;
case eChatMessageType::REMOVE_IGNORE:
ChatIgnoreList::RemoveIgnore(packet);
Game::logger->Log("ChatServer", "Asked for ignore list, but is unimplemented right now.");
break;
case eChatMessageType::TEAM_GET_STATUS:
@@ -299,19 +300,19 @@ void HandlePacket(Packet* packet) {
break;
default:
LOG("Unknown CHAT id: %i", int(packet->data[3]));
Game::logger->Log("ChatServer", "Unknown CHAT id: %i", int(packet->data[3]));
}
}
if (static_cast<eConnectionType>(packet->data[1]) == eConnectionType::WORLD) {
switch (static_cast<eWorldMessageType>(packet->data[3])) {
case eWorldMessageType::ROUTE_PACKET: {
LOG("Routing packet from world");
Game::logger->Log("ChatServer", "Routing packet from world");
break;
}
default:
LOG("Unknown World id: %i", int(packet->data[3]));
Game::logger->Log("ChatServer", "Unknown World id: %i", int(packet->data[3]));
}
}
}

View File

@@ -3,27 +3,19 @@
#include <iostream>
#include <algorithm>
#include "Game.h"
#include "Logger.h"
#include "dLogger.h"
#include "ChatPacketHandler.h"
#include "GeneralUtils.h"
#include "BitStreamUtils.h"
#include "PacketUtils.h"
#include "Database.h"
#include "eConnectionType.h"
#include "eChatInternalMessageType.h"
#include "ChatPackets.h"
#include "dConfig.h"
void PlayerContainer::Initialize() {
GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("max_number_of_best_friends"), m_MaxNumberOfBestFriends);
GeneralUtils::TryParse<uint32_t>(Game::config->GetValue("max_number_of_friends"), m_MaxNumberOfFriends);
PlayerContainer::PlayerContainer() {
}
PlayerContainer::~PlayerContainer() {
m_Players.clear();
}
TeamData::TeamData() {
lootFlag = Game::config->GetValue("default_team_loot") == "0" ? 0 : 1;
mPlayers.clear();
}
void PlayerContainer::InsertPlayer(Packet* packet) {
@@ -43,12 +35,19 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
inStream.Read(data->muteExpire);
data->sysAddr = packet->systemAddress;
m_Names[data->playerID] = GeneralUtils::UTF8ToUTF16(data->playerName);
mNames[data->playerID] = GeneralUtils::UTF8ToUTF16(data->playerName);
m_Players.insert(std::make_pair(data->playerID, data));
LOG("Added user: %s (%llu), zone: %i", data->playerName.c_str(), data->playerID, data->zoneID.GetMapID());
mPlayers.insert(std::make_pair(data->playerID, data));
Game::logger->Log("PlayerContainer", "Added user: %s (%llu), zone: %i", data->playerName.c_str(), data->playerID, data->zoneID.GetMapID());
Database::Get()->UpdateActivityLog(data->playerID, eActivityType::PlayerLoggedIn, data->zoneID.GetMapID());
auto* insertLog = Database::CreatePreppedStmt("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);");
insertLog->setInt(1, data->playerID);
insertLog->setInt(2, 0);
insertLog->setUInt64(3, time(nullptr));
insertLog->setInt(4, data->zoneID.GetMapID());
insertLog->executeUpdate();
}
void PlayerContainer::RemovePlayer(Packet* packet) {
@@ -82,10 +81,17 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
}
}
LOG("Removed user: %llu", playerID);
m_Players.erase(playerID);
Game::logger->Log("PlayerContainer", "Removed user: %llu", playerID);
mPlayers.erase(playerID);
Database::Get()->UpdateActivityLog(playerID, eActivityType::PlayerLoggedOut, player->zoneID.GetMapID());
auto* insertLog = Database::CreatePreppedStmt("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);");
insertLog->setInt(1, playerID);
insertLog->setInt(2, 1);
insertLog->setUInt64(3, time(nullptr));
insertLog->setInt(4, player->zoneID.GetMapID());
insertLog->executeUpdate();
}
void PlayerContainer::MuteUpdate(Packet* packet) {
@@ -98,7 +104,7 @@ void PlayerContainer::MuteUpdate(Packet* packet) {
auto* player = this->GetPlayerData(playerID);
if (player == nullptr) {
LOG("Failed to find user: %llu", playerID);
Game::logger->Log("PlayerContainer", "Failed to find user: %llu", playerID);
return;
}
@@ -140,7 +146,7 @@ void PlayerContainer::CreateTeamServer(Packet* packet) {
void PlayerContainer::BroadcastMuteUpdate(LWOOBJID player, time_t time) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::MUTE_UPDATE);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::MUTE_UPDATE);
bitStream.Write(player);
bitStream.Write(time);
@@ -179,7 +185,7 @@ TeamData* PlayerContainer::CreateLocalTeam(std::vector<LWOOBJID> members) {
TeamData* PlayerContainer::CreateTeam(LWOOBJID leader, bool local) {
auto* team = new TeamData();
team->teamID = ++m_TeamIDCounter;
team->teamID = ++mTeamIDCounter;
team->leaderID = leader;
team->local = local;
@@ -201,14 +207,6 @@ TeamData* PlayerContainer::GetTeam(LWOOBJID playerID) {
}
void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
if (team->memberIDs.size() >= 4){
LOG("Tried to add player to team that already had 4 players");
auto* player = GetPlayerData(playerID);
if (!player) return;
ChatPackets::SendSystemMessage(player->sysAddr, u"The teams is full! You have not been added to a team!");
return;
}
const auto index = std::find(team->memberIDs.begin(), team->memberIDs.end(), playerID);
if (index != team->memberIDs.end()) return;
@@ -347,7 +345,7 @@ void PlayerContainer::TeamStatusUpdate(TeamData* team) {
void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
CBITSTREAM;
BitStreamUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::TEAM_UPDATE);
PacketUtils::WriteHeader(bitStream, eConnectionType::CHAT_INTERNAL, eChatInternalMessageType::TEAM_UPDATE);
bitStream.Write(team->teamID);
bitStream.Write(deleteTeam);
@@ -364,15 +362,15 @@ void PlayerContainer::UpdateTeamsOnWorld(TeamData* team, bool deleteTeam) {
}
std::u16string PlayerContainer::GetName(LWOOBJID playerID) {
const auto& pair = m_Names.find(playerID);
const auto& pair = mNames.find(playerID);
if (pair == m_Names.end()) return u"";
if (pair == mNames.end()) return u"";
return pair->second;
}
LWOOBJID PlayerContainer::GetId(const std::u16string& playerName) {
for (const auto& pair : m_Names) {
for (const auto& pair : mNames) {
if (pair.second == playerName) {
return pair.first;
}

View File

@@ -7,32 +7,17 @@
#include "dServer.h"
#include <unordered_map>
struct IgnoreData {
inline bool operator==(const std::string& other) const noexcept {
return playerName == other;
}
inline bool operator==(const LWOOBJID& other) const noexcept {
return playerId == other;
}
LWOOBJID playerId;
std::string playerName;
};
struct PlayerData {
LWOOBJID playerID;
std::string playerName;
SystemAddress sysAddr;
LWOZONEID zoneID;
std::vector<FriendData> friends;
std::vector<IgnoreData> ignoredPlayers;
time_t muteExpire;
uint8_t countOfBestFriends = 0;
};
struct TeamData {
TeamData();
LWOOBJID teamID = LWOOBJID_EMPTY; // Internal use
LWOOBJID leaderID = LWOOBJID_EMPTY;
std::vector<LWOOBJID> memberIDs{};
@@ -43,9 +28,9 @@ struct TeamData {
class PlayerContainer {
public:
PlayerContainer();
~PlayerContainer();
void Initialize();
void InsertPlayer(Packet* packet);
void RemovePlayer(Packet* packet);
void MuteUpdate(Packet* packet);
@@ -53,13 +38,13 @@ public:
void BroadcastMuteUpdate(LWOOBJID player, time_t time);
PlayerData* GetPlayerData(const LWOOBJID& playerID) {
auto it = m_Players.find(playerID);
if (it != m_Players.end()) return it->second;
auto it = mPlayers.find(playerID);
if (it != mPlayers.end()) return it->second;
return nullptr;
}
PlayerData* GetPlayerData(const std::string& playerName) {
for (auto player : m_Players) {
for (auto player : mPlayers) {
if (player.second) {
std::string pn = player.second->playerName.c_str();
if (pn == playerName) return player.second;
@@ -81,17 +66,13 @@ public:
std::u16string GetName(LWOOBJID playerID);
LWOOBJID GetId(const std::u16string& playerName);
bool GetIsMuted(PlayerData* data);
uint32_t GetMaxNumberOfBestFriends() { return m_MaxNumberOfBestFriends; }
uint32_t GetMaxNumberOfFriends() { return m_MaxNumberOfFriends; }
std::map<LWOOBJID, PlayerData*>& GetAllPlayerData() { return m_Players; }
std::map<LWOOBJID, PlayerData*>& GetAllPlayerData() { return mPlayers; }
private:
LWOOBJID m_TeamIDCounter = 0;
std::map<LWOOBJID, PlayerData*> m_Players;
LWOOBJID mTeamIDCounter = 0;
std::map<LWOOBJID, PlayerData*> mPlayers;
std::vector<TeamData*> mTeams;
std::unordered_map<LWOOBJID, std::u16string> m_Names;
uint32_t m_MaxNumberOfBestFriends = 5;
uint32_t m_MaxNumberOfFriends = 50;
std::unordered_map<LWOOBJID, std::u16string> mNames;
};

View File

@@ -2,7 +2,7 @@
#define __AMF3__H__
#include "dCommonVars.h"
#include "Logger.h"
#include "dLogger.h"
#include "Game.h"
#include <unordered_map>

View File

@@ -1,7 +1,7 @@
#include "AmfSerialize.h"
#include "Game.h"
#include "Logger.h"
#include "dLogger.h"
// Writes an AMFValue pointer to a RakNet::BitStream
template<>
@@ -29,7 +29,7 @@ void RakNet::BitStream::Write<AMFBaseValue&>(AMFBaseValue& value) {
break;
}
default: {
LOG("Encountered unwritable AMFType %i!", type);
Game::logger->Log("AmfSerialize", "Encountered unwritable AMFType %i!", type);
}
case eAmf::Undefined:
case eAmf::Null:

View File

@@ -9,12 +9,13 @@
#include "Database.h"
#include "Game.h"
#include "ZCompression.h"
#include "Logger.h"
#include "dLogger.h"
//! Forward declarations
std::unique_ptr<sql::ResultSet> GetModelsFromDatabase();
void WriteSd0Magic(char* input, uint32_t chunkSize);
bool CheckSd0Magic(std::istream& streamToCheck);
bool CheckSd0Magic(sql::Blob* streamToCheck);
/**
* @brief Truncates all models with broken data from the database.
@@ -23,24 +24,28 @@ bool CheckSd0Magic(std::istream& streamToCheck);
*/
uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
uint32_t modelsTruncated{};
auto modelsToTruncate = Database::Get()->GetAllUgcModels();
bool previousCommitValue = Database::Get()->GetAutoCommit();
Database::Get()->SetAutoCommit(false);
for (auto& model : modelsToTruncate) {
auto modelsToTruncate = GetModelsFromDatabase();
bool previousCommitValue = Database::GetAutoCommit();
Database::SetAutoCommit(false);
while (modelsToTruncate->next()) {
std::unique_ptr<sql::PreparedStatement> ugcModelToDelete(Database::CreatePreppedStmt("DELETE FROM ugc WHERE ugc.id = ?;"));
std::unique_ptr<sql::PreparedStatement> pcModelToDelete(Database::CreatePreppedStmt("DELETE FROM properties_contents WHERE ugc_id = ?;"));
std::string completeUncompressedModel{};
uint32_t chunkCount{};
uint64_t modelId = modelsToTruncate->getInt(1);
std::unique_ptr<sql::Blob> modelAsSd0(modelsToTruncate->getBlob(2));
// Check that header is sd0 by checking for the sd0 magic.
if (CheckSd0Magic(model.lxfmlData)) {
if (CheckSd0Magic(modelAsSd0.get())) {
while (true) {
uint32_t chunkSize{};
model.lxfmlData.read(reinterpret_cast<char*>(&chunkSize), sizeof(uint32_t)); // Extract chunk size from istream
modelAsSd0->read(reinterpret_cast<char*>(&chunkSize), sizeof(uint32_t)); // Extract chunk size from istream
// Check if good here since if at the end of an sd0 file, this will have eof flagged.
if (!model.lxfmlData.good()) break;
if (!modelAsSd0->good()) break;
std::unique_ptr<uint8_t[]> compressedChunk(new uint8_t[chunkSize]);
for (uint32_t i = 0; i < chunkSize; i++) {
compressedChunk[i] = model.lxfmlData.get();
compressedChunk[i] = modelAsSd0->get();
}
// Ignore the valgrind warning about uninitialized values. These are discarded later when we know the actual uncompressed size.
@@ -54,14 +59,14 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
completeUncompressedModel.append((char*)uncompressedChunk.get());
completeUncompressedModel.resize(previousSize + actualUncompressedSize);
} else {
LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err);
Game::logger->Log("BrickByBrickFix", "Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, modelId, err);
break;
}
chunkCount++;
}
std::unique_ptr<tinyxml2::XMLDocument> document = std::make_unique<tinyxml2::XMLDocument>();
if (!document) {
LOG("Failed to initialize tinyxml document. Aborting.");
Game::logger->Log("BrickByBrickFix", "Failed to initialize tinyxml document. Aborting.");
return 0;
}
@@ -70,20 +75,28 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
"</LXFML>",
completeUncompressedModel.length() >= 15 ? completeUncompressedModel.length() - 15 : 0) == std::string::npos
) {
LOG("Brick-by-brick model %llu will be deleted!", model.id);
Database::Get()->DeleteUgcModelData(model.id);
Game::logger->Log("BrickByBrickFix",
"Brick-by-brick model %llu will be deleted!", modelId);
ugcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
pcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
ugcModelToDelete->execute();
pcModelToDelete->execute();
modelsTruncated++;
}
}
} else {
LOG("Brick-by-brick model %llu will be deleted!", model.id);
Database::Get()->DeleteUgcModelData(model.id);
Game::logger->Log("BrickByBrickFix",
"Brick-by-brick model %llu will be deleted!", modelId);
ugcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
pcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1));
ugcModelToDelete->execute();
pcModelToDelete->execute();
modelsTruncated++;
}
}
Database::Get()->Commit();
Database::Get()->SetAutoCommit(previousCommitValue);
Database::Commit();
Database::SetAutoCommit(previousCommitValue);
return modelsTruncated;
}
@@ -95,17 +108,21 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
*/
uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
uint32_t updatedModels = 0;
auto modelsToUpdate = Database::Get()->GetAllUgcModels();
auto previousAutoCommitState = Database::Get()->GetAutoCommit();
Database::Get()->SetAutoCommit(false);
for (auto& model : modelsToUpdate) {
auto modelsToUpdate = GetModelsFromDatabase();
auto previousAutoCommitState = Database::GetAutoCommit();
Database::SetAutoCommit(false);
std::unique_ptr<sql::PreparedStatement> insertionStatement(Database::CreatePreppedStmt("UPDATE ugc SET lxfml = ? WHERE id = ?;"));
while (modelsToUpdate->next()) {
int64_t modelId = modelsToUpdate->getInt64(1);
std::unique_ptr<sql::Blob> oldLxfml(modelsToUpdate->getBlob(2));
// Check if the stored blob starts with zlib magic (0x78 0xDA - best compression of zlib)
// If it does, convert it to sd0.
if (model.lxfmlData.get() == 0x78 && model.lxfmlData.get() == 0xDA) {
if (oldLxfml->get() == 0x78 && oldLxfml->get() == 0xDA) {
// Get and save size of zlib compressed chunk.
model.lxfmlData.seekg(0, std::ios::end);
uint32_t oldLxfmlSize = static_cast<uint32_t>(model.lxfmlData.tellg());
model.lxfmlData.seekg(0);
oldLxfml->seekg(0, std::ios::end);
uint32_t oldLxfmlSize = static_cast<uint32_t>(oldLxfml->tellg());
oldLxfml->seekg(0);
// Allocate 9 extra bytes. 5 for sd0 magic, 4 for the only zlib compressed size.
uint32_t oldLxfmlSizeWithHeader = oldLxfmlSize + 9;
@@ -113,27 +130,36 @@ uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
WriteSd0Magic(sd0ConvertedModel.get(), oldLxfmlSize);
for (uint32_t i = 9; i < oldLxfmlSizeWithHeader; i++) {
sd0ConvertedModel.get()[i] = model.lxfmlData.get();
sd0ConvertedModel.get()[i] = oldLxfml->get();
}
std::string outputString(sd0ConvertedModel.get(), oldLxfmlSizeWithHeader);
std::istringstream outputStringStream(outputString);
insertionStatement->setBlob(1, static_cast<std::istream*>(&outputStringStream));
insertionStatement->setInt64(2, modelId);
try {
Database::Get()->UpdateUgcModelData(model.id, outputStringStream);
LOG("Updated model %i to sd0", model.id);
insertionStatement->executeUpdate();
Game::logger->Log("BrickByBrickFix", "Updated model %i to sd0", modelId);
updatedModels++;
} catch (sql::SQLException exception) {
LOG("Failed to update model %i. This model should be inspected manually to see why."
"The database error is %s", model.id, exception.what());
Game::logger->Log(
"BrickByBrickFix",
"Failed to update model %i. This model should be inspected manually to see why."
"The database error is %s", modelId, exception.what());
}
}
}
Database::Get()->Commit();
Database::Get()->SetAutoCommit(previousAutoCommitState);
Database::Commit();
Database::SetAutoCommit(previousAutoCommitState);
return updatedModels;
}
std::unique_ptr<sql::ResultSet> GetModelsFromDatabase() {
std::unique_ptr<sql::PreparedStatement> modelsRawDataQuery(Database::CreatePreppedStmt("SELECT id, lxfml FROM ugc;"));
return std::unique_ptr<sql::ResultSet>(modelsRawDataQuery->executeQuery());
}
/**
* @brief Writes sd0 magic at the front of a char*
*
@@ -149,6 +175,6 @@ void WriteSd0Magic(char* input, uint32_t chunkSize) {
*reinterpret_cast<uint32_t*>(input + 5) = chunkSize; // Write the integer to the character array
}
bool CheckSd0Magic(std::istream& streamToCheck) {
return streamToCheck.get() == 's' && streamToCheck.get() == 'd' && streamToCheck.get() == '0' && streamToCheck.get() == 0x01 && streamToCheck.get() == 0xFF;
bool CheckSd0Magic(sql::Blob* streamToCheck) {
return streamToCheck->get() == 's' && streamToCheck->get() == 'd' && streamToCheck->get() == '0' && streamToCheck->get() == 0x01 && streamToCheck->get() == 0xFF;
}

View File

@@ -4,7 +4,7 @@ set(DCOMMON_SOURCES
"BinaryIO.cpp"
"dConfig.cpp"
"Diagnostics.cpp"
"Logger.cpp"
"dLogger.cpp"
"GeneralUtils.cpp"
"LDFFormat.cpp"
"MD5.cpp"
@@ -12,7 +12,7 @@ set(DCOMMON_SOURCES
"NiPoint3.cpp"
"NiQuaternion.cpp"
"SHA512.cpp"
"Demangler.cpp"
"Type.cpp"
"ZCompression.cpp"
"BrickByBrickFix.cpp"
"BinaryPathFinder.cpp"

View File

@@ -1,29 +0,0 @@
#include "Demangler.h"
#ifdef __GNUG__
#include <cstdlib>
#include <cxxabi.h>
#include <memory>
#include <typeinfo>
std::string Demangler::Demangle(const char* name) {
// some arbitrary value to eliminate the compiler warning
// -4 is not a valid return value for __cxa_demangle so we'll use that.
int status = -4;
// __cxa_demangle requires that we free the returned char*
std::unique_ptr<char, void (*)(void*)> res{
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
};
return (status == 0) ? res.get() : "";
}
#else // __GNUG__
// does nothing if not g++
std::string Demangler::Demangle(const char* name) {
return name;
}
#endif // __GNUG__

View File

@@ -1,9 +0,0 @@
#pragma once
#include <string>
namespace Demangler {
// Given a char* containing a mangled name, return a std::string containing the demangled name.
// If the function fails for any reason, it returns an empty string.
std::string Demangle(const char* name);
}

View File

@@ -1,6 +1,6 @@
#include "Diagnostics.h"
#include "Game.h"
#include "Logger.h"
#include "dLogger.h"
// If we're on Win32, we'll include our minidump writer
#ifdef _WIN32
@@ -9,7 +9,7 @@
#include <Dbghelp.h>
#include "Game.h"
#include "Logger.h"
#include "dLogger.h"
void make_minidump(EXCEPTION_POINTERS* e) {
auto hDbgHelp = LoadLibraryA("dbghelp");
@@ -28,7 +28,7 @@ void make_minidump(EXCEPTION_POINTERS* e) {
"_%4d%02d%02d_%02d%02d%02d.dmp",
t.wYear, t.wMonth, t.wDay, t.wHour, t.wMinute, t.wSecond);
}
LOG("Creating crash dump %s", name);
Game::logger->Log("Diagnostics", "Creating crash dump %s", name);
auto hFile = CreateFileA(name, GENERIC_WRITE, FILE_SHARE_READ, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
if (hFile == INVALID_HANDLE_VALUE)
return;
@@ -83,7 +83,7 @@ struct bt_ctx {
static inline void Bt(struct backtrace_state* state) {
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
LOG("backtrace is enabled, crash dump located at %s", fileName.c_str());
Game::logger->Log("Diagnostics", "backtrace is enabled, crash dump located at %s", fileName.c_str());
FILE* file = fopen(fileName.c_str(), "w+");
if (file != nullptr) {
backtrace_print(state, 2, file);
@@ -107,7 +107,7 @@ static void ErrorCallback(void* data, const char* msg, int errnum) {
}
#endif
#include "Demangler.h"
#include "Type.h"
void GenerateDump() {
std::string cmd = "sudo gcore " + std::to_string(getpid());
@@ -118,56 +118,55 @@ void CatchUnhandled(int sig) {
#ifndef __include_backtrace__
std::string fileName = Diagnostics::GetOutDirectory() + "crash_" + Diagnostics::GetProcessName() + "_" + std::to_string(getpid()) + ".log";
LOG("Encountered signal %i, creating crash dump %s", sig, fileName.c_str());
Game::logger->Log("Diagnostics", "Encountered signal %i, creating crash dump %s", sig, fileName.c_str());
if (Diagnostics::GetProduceMemoryDump()) {
GenerateDump();
}
constexpr uint8_t MaxStackTrace = 32;
void* array[MaxStackTrace];
void* array[10];
size_t size;
// get void*'s for all entries on the stack
size = backtrace(array, MaxStackTrace);
size = backtrace(array, 10);
# if defined(__GNUG__)
#if defined(__GNUG__) and defined(__dynamic)
// Loop through the returned addresses, and get the symbols to be demangled
char** strings = backtrace_symbols(array, size);
FILE* file = fopen(fileName.c_str(), "w+");
if (file != NULL) {
fprintf(file, "Error: signal %d:\n", sig);
}
// Print the stack trace
for (size_t i = 0; i < size; i++) {
// Take a string like './WorldServer(_ZN19SlashCommandHandler17HandleChatCommandERKSbIDsSt11char_traitsIDsESaIDsEEP6EntityRK13SystemAddress+0x6187) [0x55869c44ecf7]'
// and extract '_ZN19SlashCommandHandler17HandleChatCommandERKSbIDsSt11char_traitsIDsESaIDsEEP6EntityRK13SystemAddress' from it to be demangled into a proper name
// Take a string like './WorldServer(_ZN19SlashCommandHandler17HandleChatCommandERKSbIDsSt11char_traitsIDsESaIDsEEP6EntityRK13SystemAddress+0x6187) [0x55869c44ecf7]' and extract the function name
std::string functionName = strings[i];
std::string::size_type start = functionName.find('(');
std::string::size_type end = functionName.find('+');
if (start != std::string::npos && end != std::string::npos) {
std::string demangled = functionName.substr(start + 1, end - start - 1);
demangled = Demangler::Demangle(demangled.c_str());
demangled = demangle(functionName.c_str());
// If the demangled string is not empty, then we can replace the mangled string with the demangled one
if (!demangled.empty()) {
demangled.push_back('(');
demangled += functionName.substr(end);
functionName = demangled;
if (demangled.empty()) {
Game::logger->Log("Diagnostics", "[%02zu] %s", i, demangled.c_str());
} else {
Game::logger->Log("Diagnostics", "[%02zu] %s", i, functionName.c_str());
}
}
LOG("[%02zu] %s", i, functionName.c_str());
if (file != NULL) {
fprintf(file, "[%02zu] %s\n", i, functionName.c_str());
} else {
Game::logger->Log("Diagnostics", "[%02zu] %s", i, functionName.c_str());
}
}
# else // defined(__GNUG__)
#else
backtrace_symbols_fd(array, size, STDOUT_FILENO);
# endif // defined(__GNUG__)
#endif
#else // __include_backtrace__
FILE* file = fopen(fileName.c_str(), "w+");
if (file != NULL) {
// print out all the frames to stderr
fprintf(file, "Error: signal %d:\n", sig);
backtrace_symbols_fd(array, size, fileno(file));
fclose(file);
}
#else
struct backtrace_state* state = backtrace_create_state(
Diagnostics::GetProcessFileName().c_str(),
@@ -178,7 +177,7 @@ void CatchUnhandled(int sig) {
struct bt_ctx ctx = { state, 0 };
Bt(state);
#endif // __include_backtrace__
#endif
exit(EXIT_FAILURE);
}

View File

@@ -9,7 +9,7 @@
#include "CDClientDatabase.h"
#include "GeneralUtils.h"
#include "Game.h"
#include "Logger.h"
#include "dLogger.h"
#include "AssetManager.h"
#include "eSqliteDataType.h"
@@ -44,7 +44,7 @@ bool FdbToSqlite::Convert::ConvertDatabase(AssetMemoryBuffer& buffer) {
CDClientDatabase::ExecuteQuery("COMMIT;");
} catch (CppSQLite3Exception& e) {
LOG("Encountered error %s converting FDB to SQLite", e.errorMessage());
Game::logger->Log("FdbToSqlite", "Encountered error %s converting FDB to SQLite", e.errorMessage());
return false;
}

View File

@@ -3,7 +3,7 @@
#include <random>
class dServer;
class Logger;
class dLogger;
class InstanceManager;
class dChatFilter;
class dConfig;
@@ -12,10 +12,9 @@ class AssetManager;
struct SystemAddress;
class EntityManager;
class dZoneManager;
class PlayerContainer;
namespace Game {
extern Logger* logger;
extern dLogger* logger;
extern dServer* server;
extern InstanceManager* im;
extern dChatFilter* chatFilter;
@@ -27,5 +26,4 @@ namespace Game {
extern bool shouldShutdown;
extern EntityManager* entityManager;
extern dZoneManager* zoneManager;
extern PlayerContainer playerContainer;
}

View File

@@ -13,7 +13,7 @@
#include "NiPoint3.h"
#include "Game.h"
#include "Logger.h"
#include "dLogger.h"
enum eInventoryType : uint32_t;
enum class eObjectBits : size_t;
@@ -126,11 +126,6 @@ namespace GeneralUtils {
template <typename T>
T Parse(const char* value);
template <>
inline bool Parse(const char* value) {
return std::stoi(value);
}
template <>
inline int32_t Parse(const char* value) {
return std::stoi(value);
@@ -151,11 +146,6 @@ namespace GeneralUtils {
return std::stod(value);
}
template <>
inline uint16_t Parse(const char* value) {
return std::stoul(value);
}
template <>
inline uint32_t Parse(const char* value) {
return std::stoul(value);

View File

@@ -4,7 +4,7 @@
#include "GeneralUtils.h"
#include "Game.h"
#include "Logger.h"
#include "dLogger.h"
// C++
#include <string_view>
@@ -48,7 +48,7 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
try {
type = static_cast<eLDFType>(strtol(ldfTypeAndValue.first.data(), &storage, 10));
} catch (std::exception) {
LOG("Attempted to process invalid ldf type (%s) from string (%s)", ldfTypeAndValue.first.data(), format.data());
Game::logger->Log("LDFFormat", "Attempted to process invalid ldf type (%s) from string (%s)", ldfTypeAndValue.first.data(), format.data());
return nullptr;
}
@@ -61,33 +61,35 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
}
case LDF_TYPE_S32: {
int32_t data;
if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) {
LOG("Warning: Attempted to process invalid int32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
try {
int32_t data = static_cast<int32_t>(strtoul(ldfTypeAndValue.second.data(), &storage, 10));
returnValue = new LDFData<int32_t>(key, data);
} catch (std::exception) {
Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid int32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<int32_t>(key, data);
break;
}
case LDF_TYPE_FLOAT: {
float data;
if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) {
LOG("Warning: Attempted to process invalid float value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
try {
float data = strtof(ldfTypeAndValue.second.data(), &storage);
returnValue = new LDFData<float>(key, data);
} catch (std::exception) {
Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid float value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<float>(key, data);
break;
}
case LDF_TYPE_DOUBLE: {
double data;
if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) {
LOG("Warning: Attempted to process invalid double value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
try {
double data = strtod(ldfTypeAndValue.second.data(), &storage);
returnValue = new LDFData<double>(key, data);
} catch (std::exception) {
Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid double value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<double>(key, data);
break;
}
@@ -100,8 +102,10 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
} else if (ldfTypeAndValue.second == "false") {
data = 0;
} else {
if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) {
LOG("Warning: Attempted to process invalid uint32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
try {
data = static_cast<uint32_t>(strtoul(ldfTypeAndValue.second.data(), &storage, 10));
} catch (std::exception) {
Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid uint32 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
}
@@ -118,8 +122,10 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
} else if (ldfTypeAndValue.second == "false") {
data = false;
} else {
if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) {
LOG("Warning: Attempted to process invalid bool value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
try {
data = static_cast<bool>(strtol(ldfTypeAndValue.second.data(), &storage, 10));
} catch (std::exception) {
Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid bool value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
}
@@ -129,22 +135,24 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
}
case LDF_TYPE_U64: {
uint64_t data;
if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) {
LOG("Warning: Attempted to process invalid uint64 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
try {
uint64_t data = static_cast<uint64_t>(strtoull(ldfTypeAndValue.second.data(), &storage, 10));
returnValue = new LDFData<uint64_t>(key, data);
} catch (std::exception) {
Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid uint64 value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<uint64_t>(key, data);
break;
}
case LDF_TYPE_OBJID: {
LWOOBJID data;
if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), data)) {
LOG("Warning: Attempted to process invalid LWOOBJID value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
try {
LWOOBJID data = static_cast<LWOOBJID>(strtoll(ldfTypeAndValue.second.data(), &storage, 10));
returnValue = new LDFData<LWOOBJID>(key, data);
} catch (std::exception) {
Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid LWOOBJID value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
return nullptr;
}
returnValue = new LDFData<LWOOBJID>(key, data);
break;
}
@@ -155,12 +163,12 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
}
case LDF_TYPE_UNKNOWN: {
LOG("Warning: Attempted to process invalid unknown value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid unknown value (%s) from string (%s)", ldfTypeAndValue.second.data(), format.data());
break;
}
default: {
LOG("Warning: Attempted to process invalid LDF type (%d) from string (%s)", type, format.data());
Game::logger->Log("LDFFormat", "Warning: Attempted to process invalid LDF type (%d) from string (%s)", type, format.data());
break;
}
}

View File

@@ -1,96 +0,0 @@
#include "Logger.h"
#include <algorithm>
#include <ctime>
#include <filesystem>
#include <stdarg.h>
Writer::~Writer() {
// Dont try to close stdcout...
if (!m_Outfile || m_IsConsoleWriter) return;
fclose(m_Outfile);
m_Outfile = NULL;
}
void Writer::Log(const char* time, const char* message) {
if (!m_Outfile) return;
fputs(time, m_Outfile);
fputs(message, m_Outfile);
}
void Writer::Flush() {
if (!m_Outfile) return;
fflush(m_Outfile);
}
FileWriter::FileWriter(const char* outpath) {
m_Outfile = fopen(outpath, "wt");
if (!m_Outfile) printf("Couldn't open %s for writing!\n", outpath);
m_Outpath = outpath;
m_IsConsoleWriter = false;
}
ConsoleWriter::ConsoleWriter(bool enabled) {
m_Enabled = enabled;
m_Outfile = stdout;
m_IsConsoleWriter = true;
}
Logger::Logger(const std::string& outpath, bool logToConsole, bool logDebugStatements) {
m_logDebugStatements = logDebugStatements;
std::filesystem::path outpathPath(outpath);
if (!std::filesystem::exists(outpathPath.parent_path())) std::filesystem::create_directories(outpathPath.parent_path());
m_Writers.push_back(std::make_unique<FileWriter>(outpath));
m_Writers.push_back(std::make_unique<ConsoleWriter>(logToConsole));
}
void Logger::vLog(const char* format, va_list args) {
time_t t = time(NULL);
struct tm* time = localtime(&t);
char timeStr[70];
strftime(timeStr, sizeof(timeStr), "[%d-%m-%y %H:%M:%S ", time);
char message[2048];
vsnprintf(message, 2048, format, args);
for (const auto& writer : m_Writers) {
writer->Log(timeStr, message);
}
}
void Logger::Log(const char* className, const char* format, ...) {
va_list args;
std::string log = std::string(className) + "] " + std::string(format) + "\n";
va_start(args, format);
vLog(log.c_str(), args);
va_end(args);
}
void Logger::LogDebug(const char* className, const char* format, ...) {
if (!m_logDebugStatements) return;
va_list args;
std::string log = std::string(className) + "] " + std::string(format) + "\n";
va_start(args, format);
vLog(log.c_str(), args);
va_end(args);
}
void Logger::Flush() {
for (const auto& writer : m_Writers) {
writer->Flush();
}
}
void Logger::SetLogToConsole(bool logToConsole) {
for (const auto& writer : m_Writers) {
if (writer->IsConsoleWriter()) writer->SetEnabled(logToConsole);
}
}
bool Logger::GetLogToConsole() const {
bool toReturn = false;
for (const auto& writer : m_Writers) {
if (writer->IsConsoleWriter()) toReturn |= writer->GetEnabled();
}
return toReturn;
}

View File

@@ -1,89 +0,0 @@
#pragma once
#include <memory>
#include <string>
#include <vector>
#define STRINGIFY_IMPL(x) #x
#define STRINGIFY(x) STRINGIFY_IMPL(x)
#define GET_FILE_NAME(x, y) GetFileNameFromAbsolutePath(__FILE__ x y)
#define FILENAME_AND_LINE GET_FILE_NAME(":", STRINGIFY(__LINE__))
// Calculate the filename at compile time from the path.
// We just do this by scanning the path for the last '/' or '\' character and returning the string after it.
constexpr const char* GetFileNameFromAbsolutePath(const char* path) {
const char* file = path;
while (*path) {
char nextChar = *path++;
if (nextChar == '/' || nextChar == '\\') {
file = path;
}
}
return file;
}
// These have to have a constexpr variable to store the filename_and_line result in a local variable otherwise
// they will not be valid constexpr and will be evaluated at runtime instead of compile time!
// The full string is still stored in the binary, however the offset of the filename in the absolute paths
// is used in the instruction instead of the start of the absolute path.
#define LOG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->Log(str, message, ##__VA_ARGS__); } while(0)
#define LOG_DEBUG(message, ...) do { auto str = FILENAME_AND_LINE; Game::logger->LogDebug(str, message, ##__VA_ARGS__); } while(0)
// Writer class for writing data to files.
class Writer {
public:
Writer(bool enabled = true) : m_Enabled(enabled) {};
virtual ~Writer();
virtual void Log(const char* time, const char* message);
virtual void Flush();
void SetEnabled(bool disabled) { m_Enabled = disabled; }
bool GetEnabled() const { return m_Enabled; }
bool IsConsoleWriter() { return m_IsConsoleWriter; }
public:
bool m_Enabled = true;
bool m_IsConsoleWriter = false;
FILE* m_Outfile;
};
// FileWriter class for writing data to a file on a disk.
class FileWriter : public Writer {
public:
FileWriter(const char* outpath);
FileWriter(const std::string& outpath) : FileWriter(outpath.c_str()) {};
private:
std::string m_Outpath;
};
// ConsoleWriter class for writing data to the console.
class ConsoleWriter : public Writer {
public:
ConsoleWriter(bool enabled);
};
class Logger {
public:
Logger() = delete;
Logger(const std::string& outpath, bool logToConsole, bool logDebugStatements);
void Log(const char* filenameAndLine, const char* format, ...);
void LogDebug(const char* filenameAndLine, const char* format, ...);
void Flush();
bool GetLogToConsole() const;
void SetLogToConsole(bool logToConsole);
void SetLogDebugStatements(bool logDebugStatements) { m_logDebugStatements = logDebugStatements; }
private:
void vLog(const char* format, va_list args);
bool m_logDebugStatements;
std::vector<std::unique_ptr<Writer>> m_Writers;
};

View File

@@ -129,19 +129,10 @@ NiPoint3 NiPoint3::operator+(const NiPoint3& point) const {
}
//! Operator for addition of vectors
NiPoint3& NiPoint3::operator+=(const NiPoint3& point) {
this->x += point.x;
this->y += point.y;
this->z += point.z;
return *this;
NiPoint3 NiPoint3::operator+=(const NiPoint3& point) const {
return NiPoint3(this->x + point.x, this->y + point.y, this->z + point.z);
}
NiPoint3& NiPoint3::operator*=(const float scalar) {
this->x *= scalar;
this->y *= scalar;
this->z *= scalar;
return *this;
}
//! Operator for subtraction of vectors
NiPoint3 NiPoint3::operator-(const NiPoint3& point) const {

View File

@@ -136,9 +136,7 @@ public:
NiPoint3 operator+(const NiPoint3& point) const;
//! Operator for addition of vectors
NiPoint3& operator+=(const NiPoint3& point);
NiPoint3& operator*=(const float scalar);
NiPoint3 operator+=(const NiPoint3& point) const;
//! Operator for subtraction of vectors
NiPoint3 operator-(const NiPoint3& point) const;

27
dCommon/Type.cpp Normal file
View File

@@ -0,0 +1,27 @@
#include "Type.h"
#ifdef __GNUG__
#include <cstdlib>
#include <memory>
#include <cxxabi.h>
std::string demangle(const char* name) {
int status = -4; // some arbitrary value to eliminate the compiler warning
// enable c++11 by passing the flag -std=c++11 to g++
std::unique_ptr<char, void(*)(void*)> res{
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
};
return (status == 0) ? res.get() : name;
}
#else
// does nothing if not g++
std::string demangle(const char* name) {
return name;
}
#endif

12
dCommon/Type.h Normal file
View File

@@ -0,0 +1,12 @@
#pragma once
#include <string>
#include <typeinfo>
std::string demangle(const char* name);
template <class T>
std::string type(const T& t) {
return demangle(typeid(t).name());
}

View File

@@ -2,7 +2,7 @@
#include "AssetManager.h"
#include "Game.h"
#include "Logger.h"
#include "dLogger.h"
#include <zlib.h>

View File

@@ -42,7 +42,7 @@ struct AssetMemoryBuffer : std::streambuf {
}
void close() {
free(m_Base);
delete m_Base;
}
};

View File

@@ -3,7 +3,6 @@
#include <vector>
#include <string>
#include <filesystem>
#include <fstream>
#pragma pack(push, 1)
struct PackRecord {

View File

@@ -1,7 +1,7 @@
#include "PackIndex.h"
#include "BinaryIO.h"
#include "Game.h"
#include "Logger.h"
#include "dLogger.h"
PackIndex::PackIndex(const std::filesystem::path& filePath) {
m_FileStream = std::ifstream(filePath / "versions" / "primary.pki", std::ios::in | std::ios::binary);
@@ -34,7 +34,7 @@ PackIndex::PackIndex(const std::filesystem::path& filePath) {
m_PackFileIndices.push_back(packFileIndex);
}
LOG("Loaded pack catalog with %i pack files and %i files", m_PackPaths.size(), m_PackFileIndices.size());
Game::logger->Log("PackIndex", "Loaded pack catalog with %i pack files and %i files", m_PackPaths.size(), m_PackFileIndices.size());
for (auto& item : m_PackPaths) {
std::replace(item.begin(), item.end(), '\\', '/');

View File

@@ -38,11 +38,13 @@ const std::string& dConfig::GetValue(std::string key) {
}
void dConfig::ProcessLine(const std::string& line) {
auto splitLoc = line.find('=');
auto key = line.substr(0, splitLoc);
auto value = line.substr(splitLoc + 1);
auto splitLine = GeneralUtils::SplitString(line, '=');
if (splitLine.size() != 2) return;
//Make sure that on Linux, we remove special characters:
auto& key = splitLine.at(0);
auto& value = splitLine.at(1);
if (!value.empty() && value.at(value.size() - 1) == '\r') value.erase(value.size() - 1);
if (this->m_ConfigValues.find(key) != this->m_ConfigValues.end()) return;

View File

@@ -9,7 +9,6 @@
#include "BitStream.h"
#include "eConnectionType.h"
#include "eClientMessageType.h"
#include "BitStreamUtils.h"
#pragma warning (disable:4251) //Disables SQL warnings
@@ -33,7 +32,7 @@ constexpr uint32_t lowFrameDelta = FRAMES_TO_MS(lowFramerate);
#define CBITSTREAM RakNet::BitStream bitStream;
#define CINSTREAM RakNet::BitStream inStream(packet->data, packet->length, false);
#define CINSTREAM_SKIP_HEADER CINSTREAM if (inStream.GetNumberOfUnreadBits() >= BYTES_TO_BITS(HEADER_SIZE)) inStream.IgnoreBytes(HEADER_SIZE); else inStream.IgnoreBits(inStream.GetNumberOfUnreadBits());
#define CMSGHEADER BitStreamUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::GAME_MSG);
#define CMSGHEADER PacketUtils::WriteHeader(bitStream, eConnectionType::CLIENT, eClientMessageType::GAME_MSG);
#define SEND_PACKET Game::server->Send(&bitStream, sysAddr, false);
#define SEND_PACKET_BROADCAST Game::server->Send(&bitStream, UNASSIGNED_SYSTEM_ADDRESS, true);

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,20 @@ enum class ePermissionMap : uint64_t {
* The character has restricted chat access, bit 6.
*/
RestrictedChatAccess = 0x1 << 6,
//
// Combined permissions
//
/**
* The character is marked as 'old', restricted from trade and mail.
*/
Old = RestrictedTradeAccess | RestrictedMailAccess,
/**
* The character is soft banned, restricted from trade, mail, and chat.
*/
SoftBanned = RestrictedTradeAccess | RestrictedMailAccess | RestrictedChatAccess,
};
#endif //!__EPERMISSIONMAP__H__

View File

@@ -166,8 +166,7 @@ enum ePlayerFlag : int32_t {
NJ_LIGHTNING_SPINJITZU = 2031,
NJ_ICE_SPINJITZU = 2032,
NJ_FIRE_SPINJITZU = 2033,
NJ_WU_SHOW_DAILY_CHEST = 2099,
DLU_SKIP_CINEMATICS = 1'000'000,
NJ_WU_SHOW_DAILY_CHEST = 2099
};
#endif //!__EPLAYERFLAG__H__

View File

@@ -34,7 +34,7 @@ enum class eReplicaComponentType : uint32_t {
PLATFORM_BOUNDARY,
MODULE,
ARCADE,
HAVOK_VEHICLE_PHYSICS,
VEHICLE_PHYSICS, // Havok demo based
MOVEMENT_AI,
EXHIBIT,
OVERHEAD_ICON,
@@ -50,11 +50,11 @@ enum class eReplicaComponentType : uint32_t {
PROPERTY_ENTRANCE,
FX,
PROPERTY_MANAGEMENT,
VEHICLE_PHYSICS,
VEHICLE_PHYSICS_NEW, // internal physics based on havok
PHYSICS_SYSTEM,
QUICK_BUILD,
SWITCH,
MINI_GAME_CONTROL,
ZONE_CONTROL, // Minigame
CHANGLING,
CHOICE_BUILD,
PACKAGE,
@@ -101,7 +101,7 @@ enum class eReplicaComponentType : uint32_t {
TRADE,
USER_CONTROL,
IGNORE_LIST,
MULTI_ZONE_ENTRANCE,
ROCKET_LAUNCH_LUP,
BUFF_REAL, // the real buff component, should just be name BUFF
INTERACTION_MANAGER,
DONATION_VENDOR,

View File

@@ -36,8 +36,7 @@ enum class eWorldMessageType : uint32_t {
HANDLE_FUNNESS,
FAKE_PRG_CSR_MESSAGE,
REQUEST_FREE_TRIAL_REFRESH,
GM_SET_FREE_TRIAL_STATUS,
UI_HELP_TOP_5 = 91
GM_SET_FREE_TRIAL_STATUS
};
#endif //!__EWORLDMESSAGETYPE__H__

110
dCommon/dLogger.cpp Normal file
View File

@@ -0,0 +1,110 @@
#include "dLogger.h"
dLogger::dLogger(const std::string& outpath, bool logToConsole, bool logDebugStatements) {
m_logToConsole = logToConsole;
m_logDebugStatements = logDebugStatements;
m_outpath = outpath;
#ifdef _WIN32
mFile = std::ofstream(m_outpath);
if (!mFile) { printf("Couldn't open %s for writing!\n", outpath.c_str()); }
#else
fp = fopen(outpath.c_str(), "wt");
if (fp == NULL) { printf("Couldn't open %s for writing!\n", outpath.c_str()); }
#endif
}
dLogger::~dLogger() {
#ifdef _WIN32
mFile.close();
#else
if (fp != nullptr) {
fclose(fp);
fp = nullptr;
}
#endif
}
void dLogger::vLog(const char* format, va_list args) {
#ifdef _WIN32
time_t t = time(NULL);
struct tm time;
localtime_s(&time, &t);
char timeStr[70];
strftime(timeStr, sizeof(timeStr), "%d-%m-%y %H:%M:%S", &time);
char message[2048];
vsnprintf(message, 2048, format, args);
if (m_logToConsole) std::cout << "[" << timeStr << "] " << message;
mFile << "[" << timeStr << "] " << message;
#else
time_t t = time(NULL);
struct tm* time = localtime(&t);
char timeStr[70];
strftime(timeStr, sizeof(timeStr), "%d-%m-%y %H:%M:%S", time);
char message[2048];
vsnprintf(message, 2048, format, args);
if (m_logToConsole) {
fputs("[", stdout);
fputs(timeStr, stdout);
fputs("] ", stdout);
fputs(message, stdout);
}
if (fp != nullptr) {
fputs("[", fp);
fputs(timeStr, fp);
fputs("] ", fp);
fputs(message, fp);
} else {
printf("Logger not initialized!\n");
}
#endif
}
void dLogger::LogBasic(const char* format, ...) {
va_list args;
va_start(args, format);
vLog(format, args);
va_end(args);
}
void dLogger::LogBasic(const std::string& message) {
LogBasic(message.c_str());
}
void dLogger::Log(const char* className, const char* format, ...) {
va_list args;
std::string log = "[" + std::string(className) + "] " + std::string(format) + "\n";
va_start(args, format);
vLog(log.c_str(), args);
va_end(args);
}
void dLogger::Log(const std::string& className, const std::string& message) {
Log(className.c_str(), message.c_str());
}
void dLogger::LogDebug(const char* className, const char* format, ...) {
if (!m_logDebugStatements) return;
va_list args;
std::string log = "[" + std::string(className) + "] " + std::string(format) + "\n";
va_start(args, format);
vLog(log.c_str(), args);
va_end(args);
}
void dLogger::LogDebug(const std::string& className, const std::string& message) {
LogDebug(className.c_str(), message.c_str());
}
void dLogger::Flush() {
#ifdef _WIN32
mFile.flush();
#else
if (fp != nullptr) {
std::fflush(fp);
}
#endif
}

38
dCommon/dLogger.h Normal file
View File

@@ -0,0 +1,38 @@
#pragma once
#include <ctime>
#include <cstdarg>
#include <string>
#include <fstream>
#include <iostream>
class dLogger {
public:
dLogger(const std::string& outpath, bool logToConsole, bool logDebugStatements);
~dLogger();
void SetLogToConsole(bool logToConsole) { m_logToConsole = logToConsole; }
void SetLogDebugStatements(bool logDebugStatements) { m_logDebugStatements = logDebugStatements; }
void vLog(const char* format, va_list args);
void LogBasic(const std::string& message);
void LogBasic(const char* format, ...);
void Log(const char* className, const char* format, ...);
void Log(const std::string& className, const std::string& message);
void LogDebug(const std::string& className, const std::string& message);
void LogDebug(const char* className, const char* format, ...);
void Flush();
const bool GetIsLoggingToConsole() const { return m_logToConsole; }
private:
bool m_logDebugStatements;
bool m_logToConsole;
std::string m_outpath;
std::ofstream mFile;
#ifndef _WIN32
//Glorious linux can run with SPEED:
FILE* fp = nullptr;
#endif
};

View File

@@ -0,0 +1,22 @@
#pragma once
#include "GeneralUtils.h"
#include "Game.h"
#include "dLogger.h"
#include "dServer.h"
#include "CDTable.h"
template <
typename KeyType,
typename MappedType
>
class CDAbstractProvider
{
public:
virtual void LoadClient() = 0;
virtual void LoadHost() = 0;
virtual const MappedType& GetEntry(const KeyType& key, const MappedType& defaultValue) = 0;
};

View File

@@ -0,0 +1,124 @@
#pragma once
#include "GeneralUtils.h"
#include "Game.h"
#include "dLogger.h"
#include "dServer.h"
#include "CDTable.h"
#include "CDAbstractProvider.h"
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <functional>
#include <utility>
#include <chrono>
template <
typename KeyType,
typename MappedType
>
class CDAbstractSharedMemoryMap : public CDAbstractProvider<KeyType, MappedType>
{
typedef std::pair<const KeyType, MappedType> ValueType;
typedef boost::interprocess::allocator<ValueType, boost::interprocess::managed_shared_memory::segment_manager> ShmemAllocator;
typedef boost::interprocess::map<KeyType, MappedType, std::less<KeyType>, ShmemAllocator> Map;
public:
std::map<KeyType, MappedType> m_CacheMap;
std::string m_Name;
size_t m_Size;
bool m_Host;
Map* m_HostEntries;
boost::interprocess::managed_shared_memory m_ClientSegment;
ShmemAllocator* m_ClientAllocInst;
boost::interprocess::offset_ptr<Map> m_ClientEntires;
CDAbstractSharedMemoryMap(std::string name, size_t size)
{
m_Name = name;
m_Size = size;
m_Host = false;
m_HostEntries = nullptr;
m_ClientAllocInst = nullptr;
m_ClientEntires = nullptr;
LoadClient();
}
const MappedType& GetEntry(const KeyType& key, const MappedType& defaultValue) override {
const auto& cacheIt = m_CacheMap.find(key);
if (cacheIt != m_CacheMap.end()) {
return cacheIt->second;
}
const auto& it = m_ClientEntires->find(key);
if (it == m_ClientEntires->end()) {
return defaultValue;
}
return it->second;
}
const void SetEntry(const KeyType& key, const MappedType& value) {
if (m_Host) {
// If we are already hosting, we cannot add to the map, throw an error
throw std::runtime_error("Can not add to a map that is already being hosted");
}
m_CacheMap.emplace(key, value);
}
void LoadClient() override {
try {
m_ClientSegment = boost::interprocess::managed_shared_memory(boost::interprocess::open_read_only, m_Name.c_str());
m_ClientAllocInst = new ShmemAllocator(m_ClientSegment.get_segment_manager());
m_ClientEntires = m_ClientSegment.find<Map>(m_Name.c_str()).first;
if (m_ClientEntires == nullptr) {
throw std::runtime_error("Could not find shared memory segment " + m_Name);
}
} catch (std::exception &e) {
// Not open
}
}
void LoadHost() override {
try {
boost::interprocess::shared_memory_object::remove(m_Name.c_str());
boost::interprocess::managed_shared_memory segment(boost::interprocess::create_only, m_Name.c_str(), m_Size);
ShmemAllocator alloc(segment.get_segment_manager());
m_HostEntries = segment.construct<Map>(m_Name.c_str()) (std::less<KeyType>(), alloc);
// Copy cache
for (const auto& pair : m_CacheMap) {
m_HostEntries->insert(std::make_pair(pair.first, pair.second));
}
m_Host = true;
LoadClient();
} catch (std::exception &e) {
// Make sure the smemory is removed
boost::interprocess::shared_memory_object::remove(m_Name.c_str());
throw e;
}
}
};

View File

@@ -0,0 +1,150 @@
#pragma once
#include "CDAbstractProvider.h"
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <functional>
#include <utility>
#include <chrono>
#include <mutex>
template <
typename KeyType,
typename MappedType
>
class CDAbstractSharedMemoryProvider : public CDAbstractProvider<KeyType, MappedType>
{
typedef std::pair<const KeyType, MappedType> ValueType;
typedef boost::interprocess::allocator<ValueType, boost::interprocess::managed_shared_memory::segment_manager> ShmemAllocator;
typedef boost::interprocess::map<KeyType, MappedType, std::less<KeyType>, ShmemAllocator> Map;
public:
std::string m_Name;
std::string m_MapName;
std::function <ValueType(CppSQLite3Query&)> m_ParseEntry;
std::function <int32_t(int32_t)> m_CalculateSize;
bool m_Cache;
std::unordered_map<KeyType, MappedType> m_CacheMap;
bool m_Host;
Map* m_HostEntries;
boost::interprocess::managed_shared_memory m_ClientSegment;
ShmemAllocator* m_ClientAllocInst;
boost::interprocess::offset_ptr<Map> m_ClientEntires;
CDAbstractSharedMemoryProvider(std::string name, std::function <ValueType(CppSQLite3Query&)> parseEntry, std::function <int32_t(int32_t)> calculateSize, bool cache)
{
m_Name = name;
m_MapName = name + "Map";
m_ParseEntry = parseEntry;
m_CalculateSize = calculateSize;
m_Cache = cache;
m_Host = false;
m_HostEntries = nullptr;
LoadClient();
}
const MappedType& GetEntry(const KeyType& key, const MappedType& defaultValue) override {
if (m_Host) {
auto it = m_HostEntries->find(key);
if (it == m_HostEntries->end())
{
return defaultValue;
}
return it->second;
}
if (m_Cache) {
auto it = m_CacheMap.find(key);
if (it != m_CacheMap.end()) {
return it->second;
}
}
const auto& it = m_ClientEntires->find(key);
if (it == m_ClientEntires->end())
{
if (m_Cache) {
m_CacheMap.emplace(key, defaultValue);
}
return defaultValue;
}
if (m_Cache) {
m_CacheMap.emplace(key, it->second);
}
return it->second;
}
void LoadClient() override {
try {
m_ClientSegment = boost::interprocess::managed_shared_memory(boost::interprocess::open_read_only, m_MapName.c_str());
m_ClientAllocInst = new ShmemAllocator(m_ClientSegment.get_segment_manager());
m_ClientEntires = m_ClientSegment.find<Map>(m_Name.c_str()).first;
if (m_ClientEntires == nullptr) {
throw std::runtime_error("Could not find shared memory segment " + m_Name);
}
} catch (boost::interprocess::interprocess_exception& e) {
// Not open
}
}
void LoadHost() override {
try {
boost::interprocess::shared_memory_object::remove(m_MapName.c_str());
auto sizeQuery = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM " + m_Name);
if (sizeQuery.eof()) {
throw std::runtime_error("Could not get size of table " + m_Name);
return;
}
int32_t size = sizeQuery.getIntField(0);
size = m_CalculateSize(size);
boost::interprocess::managed_shared_memory segment(boost::interprocess::create_only, m_MapName.c_str(), size);
ShmemAllocator alloc_inst (segment.get_segment_manager());
m_HostEntries = segment.construct<Map>(m_Name.c_str()) (std::less<KeyType>(), alloc_inst);
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM " + m_Name);
while (!tableData.eof()) {
ValueType entry = m_ParseEntry(tableData);
m_HostEntries->insert(entry);
tableData.nextRow();
}
tableData.finalize();
m_Host = true;
LoadClient();
} catch (std::exception &e) {
// Make sure the smemory is removed
boost::interprocess::shared_memory_object::remove(m_MapName.c_str());
throw e;
}
}
};

View File

@@ -0,0 +1,74 @@
#pragma once
#include "GeneralUtils.h"
#include "Game.h"
#include "dLogger.h"
#include "dServer.h"
#include "CDTable.h"
#include "CDAbstractProvider.h"
template <
typename KeyType,
typename MappedType
>
class CDAbstractSqliteProvider : public CDAbstractProvider<KeyType, MappedType>
{
typedef std::pair<const KeyType, MappedType> ValueType;
public:
std::string m_Name;
std::function <ValueType(CppSQLite3Query&)> m_ParseEntry;
std::unordered_map<KeyType, MappedType> m_Entries;
CDAbstractSqliteProvider(std::string name, std::function <ValueType(CppSQLite3Query&)> parseEntry, bool cache)
{
m_Name = name;
m_ParseEntry = parseEntry;
LoadClient();
}
void LoadClient() override {
// First, get the size of the table
uint32_t size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM " + m_Name);
while (!tableSize.eof()) {
size = tableSize.getIntField(0, 0);
tableSize.nextRow();
}
tableSize.finalize();
// Reserve the size
m_Entries.reserve(size);
// Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM " + m_Name);
while (!tableData.eof()) {
auto entry = m_ParseEntry(tableData);
m_Entries.insert(entry);
tableData.nextRow();
}
tableData.finalize();
}
void LoadHost() override {
return;
}
const MappedType& GetEntry(const KeyType& key, const MappedType& defaultValue) override {
auto it = m_Entries.find(key);
if (it != m_Entries.end()) {
return it->second;
}
return defaultValue;
}
};

View File

@@ -13,7 +13,15 @@
#include <sstream>
#include <iostream>
//! The CDClient Database namespace
// Enable this to cache all entries in each table for fast access, comes with more memory cost
//#define CDCLIENT_CACHE_ALL
/*!
\file CDClientDatabase.hpp
\brief An interface between the CDClient.sqlite file and the server
*/
//! The CDClient Database namespace
namespace CDClientDatabase {
//! Opens a connection with the CDClient

View File

@@ -1,92 +0,0 @@
#include "CDClientManager.h"
#include "CDActivityRewardsTable.h"
#include "CDAnimationsTable.h"
#include "CDBehaviorParameterTable.h"
#include "CDBehaviorTemplateTable.h"
#include "CDComponentsRegistryTable.h"
#include "CDCurrencyTableTable.h"
#include "CDDestructibleComponentTable.h"
#include "CDEmoteTable.h"
#include "CDInventoryComponentTable.h"
#include "CDItemComponentTable.h"
#include "CDItemSetsTable.h"
#include "CDItemSetSkillsTable.h"
#include "CDLevelProgressionLookupTable.h"
#include "CDLootMatrixTable.h"
#include "CDLootTableTable.h"
#include "CDMissionNPCComponentTable.h"
#include "CDMissionTasksTable.h"
#include "CDMissionsTable.h"
#include "CDObjectSkillsTable.h"
#include "CDObjectsTable.h"
#include "CDPhysicsComponentTable.h"
#include "CDRebuildComponentTable.h"
#include "CDScriptComponentTable.h"
#include "CDSkillBehaviorTable.h"
#include "CDZoneTableTable.h"
#include "CDVendorComponentTable.h"
#include "CDActivitiesTable.h"
#include "CDPackageComponentTable.h"
#include "CDProximityMonitorComponentTable.h"
#include "CDMovementAIComponentTable.h"
#include "CDBrickIDTableTable.h"
#include "CDRarityTableTable.h"
#include "CDMissionEmailTable.h"
#include "CDRewardsTable.h"
#include "CDPropertyEntranceComponentTable.h"
#include "CDPropertyTemplateTable.h"
#include "CDFeatureGatingTable.h"
#include "CDRailActivatorComponent.h"
#include "CDRewardCodesTable.h"
// Uncomment this to cache the full cdclient database into memory. This will make the server load faster, but will use more memory.
// A vanilla CDClient takes about 46MB of memory + the regular world data.
// #define CDCLIENT_CACHE_ALL
#ifdef CDCLIENT_CACHE_ALL
#define CDCLIENT_DONT_CACHE_TABLE(x) x
#else
#define CDCLIENT_DONT_CACHE_TABLE(x)
#endif
CDClientManager::CDClientManager() {
CDActivityRewardsTable::Instance().LoadValuesFromDatabase();
CDActivitiesTable::Instance().LoadValuesFromDatabase();
CDCLIENT_DONT_CACHE_TABLE(CDAnimationsTable::Instance().LoadValuesFromDatabase());
CDBehaviorParameterTable::Instance().LoadValuesFromDatabase();
CDBehaviorTemplateTable::Instance().LoadValuesFromDatabase();
CDBrickIDTableTable::Instance().LoadValuesFromDatabase();
CDCLIENT_DONT_CACHE_TABLE(CDComponentsRegistryTable::Instance().LoadValuesFromDatabase());
CDCurrencyTableTable::Instance().LoadValuesFromDatabase();
CDDestructibleComponentTable::Instance().LoadValuesFromDatabase();
CDEmoteTableTable::Instance().LoadValuesFromDatabase();
CDFeatureGatingTable::Instance().LoadValuesFromDatabase();
CDInventoryComponentTable::Instance().LoadValuesFromDatabase();
CDCLIENT_DONT_CACHE_TABLE(CDItemComponentTable::Instance().LoadValuesFromDatabase());
CDItemSetSkillsTable::Instance().LoadValuesFromDatabase();
CDItemSetsTable::Instance().LoadValuesFromDatabase();
CDLevelProgressionLookupTable::Instance().LoadValuesFromDatabase();
CDCLIENT_DONT_CACHE_TABLE(CDLootMatrixTable::Instance().LoadValuesFromDatabase());
CDCLIENT_DONT_CACHE_TABLE(CDLootTableTable::Instance().LoadValuesFromDatabase());
CDMissionEmailTable::Instance().LoadValuesFromDatabase();
CDMissionNPCComponentTable::Instance().LoadValuesFromDatabase();
CDMissionTasksTable::Instance().LoadValuesFromDatabase();
CDMissionsTable::Instance().LoadValuesFromDatabase();
CDMovementAIComponentTable::Instance().LoadValuesFromDatabase();
CDObjectSkillsTable::Instance().LoadValuesFromDatabase();
CDCLIENT_DONT_CACHE_TABLE(CDObjectsTable::Instance().LoadValuesFromDatabase());
CDPhysicsComponentTable::Instance().LoadValuesFromDatabase();
CDPackageComponentTable::Instance().LoadValuesFromDatabase();
CDProximityMonitorComponentTable::Instance().LoadValuesFromDatabase();
CDPropertyEntranceComponentTable::Instance().LoadValuesFromDatabase();
CDPropertyTemplateTable::Instance().LoadValuesFromDatabase();
CDRailActivatorComponentTable::Instance().LoadValuesFromDatabase();
CDRarityTableTable::Instance().LoadValuesFromDatabase();
CDRebuildComponentTable::Instance().LoadValuesFromDatabase();
CDRewardCodesTable::Instance().LoadValuesFromDatabase();
CDRewardsTable::Instance().LoadValuesFromDatabase();
CDScriptComponentTable::Instance().LoadValuesFromDatabase();
CDSkillBehaviorTable::Instance().LoadValuesFromDatabase();
CDVendorComponentTable::Instance().LoadValuesFromDatabase();
CDZoneTableTable::Instance().LoadValuesFromDatabase();
}

View File

@@ -1,55 +0,0 @@
#include "CDBehaviorParameterTable.h"
#include "GeneralUtils.h"
uint64_t GetKey(const uint32_t behaviorID, const uint32_t parameterID) {
uint64_t key = behaviorID;
key <<= 31U;
key |= parameterID;
return key;
}
void CDBehaviorParameterTable::LoadValuesFromDatabase() {
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM BehaviorParameter");
while (!tableData.eof()) {
uint32_t behaviorID = tableData.getIntField("behaviorID", -1);
auto candidateStringToAdd = std::string(tableData.getStringField("parameterID", ""));
auto parameter = m_ParametersList.find(candidateStringToAdd);
uint32_t parameterId;
if (parameter != m_ParametersList.end()) {
parameterId = parameter->second;
} else {
parameterId = m_ParametersList.insert(std::make_pair(candidateStringToAdd, m_ParametersList.size())).first->second;
}
uint64_t hash = GetKey(behaviorID, parameterId);
float value = tableData.getFloatField("value", -1.0f);
m_Entries.insert(std::make_pair(hash, value));
tableData.nextRow();
}
tableData.finalize();
}
float CDBehaviorParameterTable::GetValue(const uint32_t behaviorID, const std::string& name, const float defaultValue) {
auto parameterID = this->m_ParametersList.find(name);
if (parameterID == this->m_ParametersList.end()) return defaultValue;
auto hash = GetKey(behaviorID, parameterID->second);
// Search for specific parameter
auto it = m_Entries.find(hash);
return it != m_Entries.end() ? it->second : defaultValue;
}
std::map<std::string, float> CDBehaviorParameterTable::GetParametersByBehaviorID(uint32_t behaviorID) {
uint64_t hashBase = behaviorID;
std::map<std::string, float> returnInfo;
for (auto& [parameterString, parameterId] : m_ParametersList) {
uint64_t hash = GetKey(hashBase, parameterId);
auto infoCandidate = m_Entries.find(hash);
if (infoCandidate != m_Entries.end()) {
returnInfo.insert(std::make_pair(parameterString, infoCandidate->second));
}
}
return returnInfo;
}

View File

@@ -1,52 +0,0 @@
#include "CDComponentsRegistryTable.h"
#include "eReplicaComponentType.h"
void CDComponentsRegistryTable::LoadValuesFromDatabase() {
// Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM ComponentsRegistry");
while (!tableData.eof()) {
CDComponentsRegistry entry;
entry.id = tableData.getIntField("id", -1);
entry.component_type = static_cast<eReplicaComponentType>(tableData.getIntField("component_type", 0));
entry.component_id = tableData.getIntField("component_id", -1);
this->mappedEntries.insert_or_assign(((uint64_t)entry.component_type) << 32 | ((uint64_t)entry.id), entry.component_id);
this->mappedEntries.insert_or_assign(entry.id, 0);
tableData.nextRow();
}
tableData.finalize();
}
int32_t CDComponentsRegistryTable::GetByIDAndType(uint32_t id, eReplicaComponentType componentType, int32_t defaultValue) {
auto exists = mappedEntries.find(id);
if (exists != mappedEntries.end()) {
auto iter = mappedEntries.find(((uint64_t)componentType) << 32 | ((uint64_t)id));
return iter == mappedEntries.end() ? defaultValue : iter->second;
}
// Now get the data. Get all components of this entity so we dont do a query for each component
auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM ComponentsRegistry WHERE id = ?;");
query.bind(1, static_cast<int32_t>(id));
auto tableData = query.execQuery();
while (!tableData.eof()) {
CDComponentsRegistry entry;
entry.id = tableData.getIntField("id", -1);
entry.component_type = static_cast<eReplicaComponentType>(tableData.getIntField("component_type", 0));
entry.component_id = tableData.getIntField("component_id", -1);
this->mappedEntries.insert_or_assign(((uint64_t)entry.component_type) << 32 | ((uint64_t)entry.id), entry.component_id);
tableData.nextRow();
}
mappedEntries.insert_or_assign(id, 0);
auto iter = this->mappedEntries.find(((uint64_t)componentType) << 32 | ((uint64_t)id));
return iter == this->mappedEntries.end() ? defaultValue : iter->second;
}

View File

@@ -1,26 +0,0 @@
#include "CDEmoteTable.h"
void CDEmoteTableTable::LoadValuesFromDatabase() {
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM Emotes");
while (!tableData.eof()) {
CDEmoteTable entry;
entry.ID = tableData.getIntField("id", -1);
entry.animationName = tableData.getStringField("animationName", "");
entry.iconFilename = tableData.getStringField("iconFilename", "");
entry.channel = tableData.getIntField("channel", -1);
entry.locked = tableData.getIntField("locked", -1) != 0;
entry.localize = tableData.getIntField("localize", -1) != 0;
entry.locState = tableData.getIntField("locStatus", -1);
entry.gateVersion = tableData.getStringField("gate_version", "");
entries.insert(std::make_pair(entry.ID, entry));
tableData.nextRow();
}
tableData.finalize();
}
CDEmoteTable* CDEmoteTableTable::GetEmote(int id) {
auto itr = entries.find(id);
return itr != entries.end() ? &itr->second : nullptr;
}

View File

@@ -1,58 +0,0 @@
#include "CDLootMatrixTable.h"
CDLootMatrix CDLootMatrixTable::ReadRow(CppSQLite3Query& tableData) const {
CDLootMatrix entry{};
if (tableData.eof()) return entry;
entry.LootTableIndex = tableData.getIntField("LootTableIndex", -1);
entry.RarityTableIndex = tableData.getIntField("RarityTableIndex", -1);
entry.percent = tableData.getFloatField("percent", -1.0f);
entry.minToDrop = tableData.getIntField("minToDrop", -1);
entry.maxToDrop = tableData.getIntField("maxToDrop", -1);
entry.flagID = tableData.getIntField("flagID", -1);
UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
return entry;
}
void CDLootMatrixTable::LoadValuesFromDatabase() {
// First, get the size of the table
unsigned int size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM LootMatrix");
while (!tableSize.eof()) {
size = tableSize.getIntField(0, 0);
tableSize.nextRow();
}
// Reserve the size
this->entries.reserve(size);
// Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM LootMatrix");
while (!tableData.eof()) {
CDLootMatrix entry;
uint32_t lootMatrixIndex = tableData.getIntField("LootMatrixIndex", -1);
this->entries[lootMatrixIndex].push_back(ReadRow(tableData));
tableData.nextRow();
}
}
const LootMatrixEntries& CDLootMatrixTable::GetMatrix(uint32_t matrixId) {
auto itr = this->entries.find(matrixId);
if (itr != this->entries.end()) {
return itr->second;
}
auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM LootMatrix where LootMatrixIndex = ?;");
query.bind(1, static_cast<int32_t>(matrixId));
auto tableData = query.execQuery();
while (!tableData.eof()) {
this->entries[matrixId].push_back(ReadRow(tableData));
tableData.nextRow();
}
return this->entries[matrixId];
}

View File

@@ -1,86 +0,0 @@
#include "CDLootTableTable.h"
#include "CDClientManager.h"
#include "CDComponentsRegistryTable.h"
#include "CDItemComponentTable.h"
#include "eReplicaComponentType.h"
// Sort the tables by their rarity so the highest rarity items are first.
void SortTable(LootTableEntries& table) {
auto* componentsRegistryTable = CDClientManager::Instance().GetTable<CDComponentsRegistryTable>();
auto* itemComponentTable = CDClientManager::Instance().GetTable<CDItemComponentTable>();
// We modify the table in place so the outer loop keeps track of what is sorted
// and the inner loop finds the highest rarity item and swaps it with the current position
// of the outer loop.
for (auto oldItrOuter = table.begin(); oldItrOuter != table.end(); oldItrOuter++) {
auto lootToInsert = oldItrOuter;
// Its fine if this starts at 0, even if this doesnt match lootToInsert as the actual highest will
// either be found and overwrite these values, or the original is somehow zero and is still the highest rarity.
uint32_t highestLootRarity = 0;
for (auto oldItrInner = oldItrOuter; oldItrInner != table.end(); oldItrInner++) {
uint32_t itemComponentId = componentsRegistryTable->GetByIDAndType(oldItrInner->itemid, eReplicaComponentType::ITEM);
uint32_t rarity = itemComponentTable->GetItemComponentByID(itemComponentId).rarity;
if (rarity > highestLootRarity) {
highestLootRarity = rarity;
lootToInsert = oldItrInner;
}
}
std::swap(*oldItrOuter, *lootToInsert);
}
}
CDLootTable CDLootTableTable::ReadRow(CppSQLite3Query& tableData) const {
CDLootTable entry{};
if (tableData.eof()) return entry;
entry.itemid = tableData.getIntField("itemid", -1);
entry.MissionDrop = tableData.getIntField("MissionDrop", -1) == 1 ? true : false;
entry.sortPriority = tableData.getIntField("sortPriority", -1);
return entry;
}
void CDLootTableTable::LoadValuesFromDatabase() {
// First, get the size of the table
unsigned int size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM LootTable");
while (!tableSize.eof()) {
size = tableSize.getIntField(0, 0);
tableSize.nextRow();
}
// Reserve the size
this->entries.reserve(size);
// Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM LootTable");
while (!tableData.eof()) {
CDLootTable entry;
uint32_t lootTableIndex = tableData.getIntField("LootTableIndex", -1);
this->entries[lootTableIndex].push_back(ReadRow(tableData));
tableData.nextRow();
}
for (auto& [id, table] : this->entries) {
SortTable(table);
}
}
const LootTableEntries& CDLootTableTable::GetTable(uint32_t tableId) {
auto itr = this->entries.find(tableId);
if (itr != this->entries.end()) {
return itr->second;
}
auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM LootTable WHERE LootTableIndex = ?;");
query.bind(1, static_cast<int32_t>(tableId));
auto tableData = query.execQuery();
while (!tableData.eof()) {
CDLootTable entry;
this->entries[tableId].push_back(ReadRow(tableData));
tableData.nextRow();
}
SortTable(this->entries[tableId]);
return this->entries[tableId];
}

View File

@@ -1,35 +0,0 @@
#include "CDPhysicsComponentTable.h"
void CDPhysicsComponentTable::LoadValuesFromDatabase() {
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM PhysicsComponent");
while (!tableData.eof()) {
CDPhysicsComponent entry;
entry.id = tableData.getIntField("id", -1);
entry.bStatic = tableData.getIntField("static", -1) != 0;
entry.physicsAsset = tableData.getStringField("physics_asset", "");
UNUSED(entry->jump = tableData.getIntField("jump", -1) != 0);
UNUSED(entry->doublejump = tableData.getIntField("doublejump", -1) != 0);
entry.speed = tableData.getFloatField("speed", -1);
UNUSED(entry->rotSpeed = tableData.getFloatField("rotSpeed", -1));
entry.playerHeight = tableData.getFloatField("playerHeight");
entry.playerRadius = tableData.getFloatField("playerRadius");
entry.pcShapeType = tableData.getIntField("pcShapeType");
entry.collisionGroup = tableData.getIntField("collisionGroup");
UNUSED(entry->airSpeed = tableData.getFloatField("airSpeed"));
UNUSED(entry->boundaryAsset = tableData.getStringField("boundaryAsset"));
UNUSED(entry->jumpAirSpeed = tableData.getFloatField("jumpAirSpeed"));
UNUSED(entry->friction = tableData.getFloatField("friction"));
UNUSED(entry->gravityVolumeAsset = tableData.getStringField("gravityVolumeAsset"));
m_entries.insert(std::make_pair(entry.id, entry));
tableData.nextRow();
}
tableData.finalize();
}
CDPhysicsComponent* CDPhysicsComponentTable::GetByID(unsigned int componentID) {
auto itr = m_entries.find(componentID);
return itr != m_entries.end() ? &itr->second : nullptr;
}

View File

@@ -1,34 +0,0 @@
#include "CDRarityTableTable.h"
void CDRarityTableTable::LoadValuesFromDatabase() {
// First, get the size of the table
unsigned int size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM RarityTable");
while (!tableSize.eof()) {
size = tableSize.getIntField(0, 0);
tableSize.nextRow();
}
tableSize.finalize();
// Reserve the size
this->entries.reserve(size);
// Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM RarityTable order by randmax desc;");
while (!tableData.eof()) {
uint32_t rarityTableIndex = tableData.getIntField("RarityTableIndex", -1);
CDRarityTable entry;
entry.randmax = tableData.getFloatField("randmax", -1);
entry.rarity = tableData.getIntField("rarity", -1);
entries[rarityTableIndex].push_back(entry);
tableData.nextRow();
}
}
const std::vector<CDRarityTable>& CDRarityTableTable::GetRarityTable(uint32_t id) {
return entries[id];
}

View File

@@ -1,23 +0,0 @@
#pragma once
// Custom Classes
#include "CDTable.h"
struct CDRarityTable {
float randmax;
unsigned int rarity;
};
typedef std::vector<CDRarityTable> RarityTable;
class CDRarityTableTable : public CDTable<CDRarityTableTable> {
private:
typedef uint32_t RarityTableIndex;
std::unordered_map<RarityTableIndex, std::vector<CDRarityTable>> entries;
public:
void LoadValuesFromDatabase();
const std::vector<CDRarityTable>& GetRarityTable(uint32_t predicate);
};

View File

@@ -1,47 +0,0 @@
#include "CDRewardCodesTable.h"
void CDRewardCodesTable::LoadValuesFromDatabase() {
// First, get the size of the table
unsigned int size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM RewardCodes");
while (!tableSize.eof()) {
size = tableSize.getIntField(0, 0);
tableSize.nextRow();
}
tableSize.finalize();
// Reserve the size
this->entries.reserve(size);
// Now get the data
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM RewardCodes");
while (!tableData.eof()) {
CDRewardCode entry;
entry.id = tableData.getIntField("id", -1);
entry.code = tableData.getStringField("code", "");
entry.attachmentLOT = tableData.getIntField("attachmentLOT", -1);
UNUSED_COLUMN(entry.locStatus = tableData.getIntField("locStatus", -1));
UNUSED_COLUMN(entry.gate_version = tableData.getStringField("gate_version", ""));
this->entries.push_back(entry);
tableData.nextRow();
}
}
LOT CDRewardCodesTable::GetAttachmentLOT(uint32_t rewardCodeId) const {
for (auto const &entry : this->entries){
if (rewardCodeId == entry.id) return entry.attachmentLOT;
}
return LOT_NULL;
}
uint32_t CDRewardCodesTable::GetCodeID(std::string code) const {
for (auto const &entry : this->entries){
if (code == entry.code) return entry.id;
}
return -1;
}

View File

@@ -1,25 +0,0 @@
#pragma once
// Custom Classes
#include "CDTable.h"
struct CDRewardCode {
uint32_t id;
std::string code;
LOT attachmentLOT;
UNUSED(uint32_t locStatus);
UNUSED(std::string gate_version);
};
class CDRewardCodesTable : public CDTable<CDRewardCodesTable> {
private:
std::vector<CDRewardCode> entries;
public:
void LoadValuesFromDatabase();
const std::vector<CDRewardCode>& GetEntries() const;
LOT GetAttachmentLOT(uint32_t rewardCodeId) const;
uint32_t GetCodeID(std::string code) const;
};

View File

@@ -1,29 +0,0 @@
#include "CDRewardsTable.h"
void CDRewardsTable::LoadValuesFromDatabase() {
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM Rewards");
while (!tableData.eof()) {
CDRewards entry;
entry.id = tableData.getIntField("id", -1);
entry.levelID = tableData.getIntField("LevelID", -1);
entry.missionID = tableData.getIntField("MissionID", -1);
entry.rewardType = tableData.getIntField("RewardType", -1);
entry.value = tableData.getIntField("value", -1);
entry.count = tableData.getIntField("count", -1);
m_entries.insert(std::make_pair(entry.id, entry));
tableData.nextRow();
}
tableData.finalize();
}
std::vector<CDRewards> CDRewardsTable::GetByLevelID(uint32_t levelID) {
std::vector<CDRewards> result{};
for (const auto& e : m_entries) {
if (e.second.levelID == levelID) result.push_back(e.second);
}
return result;
}

View File

@@ -1,12 +0,0 @@
set(DDATABASE_CDCLIENTDATABASE_SOURCES
"CDClientDatabase.cpp"
"CDClientManager.cpp"
)
add_subdirectory(CDClientTables)
foreach(file ${DDATABASE_CDCLIENTDATABASE_CDCLIENTTABLES_SOURCES})
set(DDATABASE_CDCLIENTDATABASE_SOURCES ${DDATABASE_CDCLIENTDATABASE_SOURCES} "CDClientTables/${file}")
endforeach()
set(DDATABASE_CDCLIENTDATABASE_SOURCES ${DDATABASE_CDCLIENTDATABASE_SOURCES} PARENT_SCOPE)

View File

@@ -0,0 +1,88 @@
#include "CDClientManager.h"
#include "CDActivityRewardsTable.h"
#include "CDAnimationsTable.h"
#include "CDBehaviorParameterTable.h"
#include "CDBehaviorTemplateTable.h"
#include "CDComponentsRegistryTable.h"
#include "CDCurrencyTableTable.h"
#include "CDDestructibleComponentTable.h"
#include "CDEmoteTable.h"
#include "CDInventoryComponentTable.h"
#include "CDItemComponentTable.h"
#include "CDItemSetsTable.h"
#include "CDItemSetSkillsTable.h"
#include "CDLevelProgressionLookupTable.h"
#include "CDLootMatrixTable.h"
#include "CDLootTableTable.h"
#include "CDMissionNPCComponentTable.h"
#include "CDMissionTasksTable.h"
#include "CDMissionsTable.h"
#include "CDObjectSkillsTable.h"
#include "CDObjectsTable.h"
#include "CDPhysicsComponentTable.h"
#include "CDRebuildComponentTable.h"
#include "CDScriptComponentTable.h"
#include "CDSkillBehaviorTable.h"
#include "CDZoneTableTable.h"
#include "CDVendorComponentTable.h"
#include "CDActivitiesTable.h"
#include "CDPackageComponentTable.h"
#include "CDProximityMonitorComponentTable.h"
#include "CDMovementAIComponentTable.h"
#include "CDBrickIDTableTable.h"
#include "CDRarityTableTable.h"
#include "CDMissionEmailTable.h"
#include "CDRewardsTable.h"
#include "CDPropertyEntranceComponentTable.h"
#include "CDPropertyTemplateTable.h"
#include "CDFeatureGatingTable.h"
#include "CDRailActivatorComponent.h"
CDClientManager::CDClientManager() {
CDActivityRewardsTable::Instance();
CDAnimationsTable::Instance();
CDBehaviorParameterTable::Instance();
CDBehaviorTemplateTable::Instance();
CDComponentsRegistryTable::Instance();
CDCurrencyTableTable::Instance();
CDDestructibleComponentTable::Instance();
CDEmoteTableTable::Instance();
CDInventoryComponentTable::Instance();
CDItemComponentTable::Instance();
CDItemSetsTable::Instance();
CDItemSetSkillsTable::Instance();
CDLevelProgressionLookupTable::Instance();
CDLootMatrixTable::Instance();
CDLootTableTable::Instance();
CDMissionNPCComponentTable::Instance();
CDMissionTasksTable::Instance();
CDMissionsTable::Instance();
CDObjectSkillsTable::Instance();
CDObjectsTable::Instance();
CDPhysicsComponentTable::Instance();
CDRebuildComponentTable::Instance();
CDScriptComponentTable::Instance();
CDSkillBehaviorTable::Instance();
CDZoneTableTable::Instance();
CDVendorComponentTable::Instance();
CDActivitiesTable::Instance();
CDPackageComponentTable::Instance();
CDProximityMonitorComponentTable::Instance();
CDMovementAIComponentTable::Instance();
CDBrickIDTableTable::Instance();
CDRarityTableTable::Instance();
CDMissionEmailTable::Instance();
CDRewardsTable::Instance();
CDPropertyEntranceComponentTable::Instance();
CDPropertyTemplateTable::Instance();
CDFeatureGatingTable::Instance();
CDRailActivatorComponentTable::Instance();
}
void CDClientManager::LoadHost() {
for (auto itr = this->tables.begin(); itr != this->tables.end(); ++itr) {
itr->second->LoadHost();
}
CDTable::InitalizeHost();
}

41
dDatabase/CDProvider.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
#include "CDAbstractProvider.h"
/**
* Shared memory provider with CDAbstractSharedMemoryProvider<key, value>
*
* Depends on the boost::interprocess library — header only
*
* Requires that CD_PROVIDER_MEMORY is defined and CD_PROVIDER_SQLITE is not defined
*/
#if defined(CD_PROVIDER_MEMORY) && !defined(CD_PROVIDER_SQLITE)
#include "CDAbstractSharedMemoryProvider.h"
#define CD_PROVIDER(provider, key, value) CDAbstractSharedMemoryProvider<key, value>* provider; typedef key CD_KEY; typedef value CD_VALUE
#define NEW_CD_PROVIDER(provider, name, parser, size_calculator, cache) provider = new CDAbstractSharedMemoryProvider<CD_KEY, CD_VALUE>(name, parser, size_calculator, cache)
template<typename KeyType, typename MappedType>
using CDProvider = CDAbstractSharedMemoryProvider<KeyType, MappedType>;
#endif
/**
* SQLite provider with CDAbstractSqliteProvider<key, value>
*
* No extra dependencies
*
* Requires that CD_PROVIDER_SQLITE or CD_PROVIDER_MEMORY is not defined — the default option
*/
#if defined(CD_PROVIDER_SQLITE) || !defined(CD_PROVIDER_MEMORY)
#include "CDAbstractSqliteProvider.h"
#define CD_PROVIDER(provider, key, value) CDAbstractSqliteProvider<key, value>* provider; typedef key CD_KEY; typedef value CD_VALUE
#define NEW_CD_PROVIDER(provider, name, parser, size_calculator, cache) provider = new CDAbstractSqliteProvider<CD_KEY, CD_VALUE>(name, parser, cache)
template<typename KeyType, typename MappedType>
using CDProvider = CDAbstractSqliteProvider<KeyType, MappedType>;
#endif

View File

@@ -1,15 +1,12 @@
set(DDATABASE_SOURCES)
set(DDATABASE_SOURCES "CDClientDatabase.cpp"
"CDClientManager.cpp"
"Database.cpp"
"MigrationRunner.cpp")
add_subdirectory(CDClientDatabase)
add_subdirectory(Tables)
foreach(file ${DDATABASE_CDCLIENTDATABASE_SOURCES})
set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "CDClientDatabase/${file}")
endforeach()
add_subdirectory(GameDatabase)
foreach(file ${DDATABASE_GAMEDATABASE_SOURCES})
set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "GameDatabase/${file}")
foreach(file ${DDATABASE_TABLES_SOURCES})
set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "Tables/${file}")
endforeach()
add_library(dDatabase STATIC ${DDATABASE_SOURCES})

118
dDatabase/Database.cpp Normal file
View File

@@ -0,0 +1,118 @@
#include "Database.h"
#include "Game.h"
#include "dConfig.h"
#include "dLogger.h"
using namespace std;
#pragma warning (disable:4251) //Disables SQL warnings
sql::Driver* Database::driver;
sql::Connection* Database::con;
sql::Properties Database::props;
std::string Database::database;
void Database::Connect(const string& host, const string& database, const string& username, const string& password) {
//To bypass debug issues:
const char* szDatabase = database.c_str();
const char* szUsername = username.c_str();
const char* szPassword = password.c_str();
driver = sql::mariadb::get_driver_instance();
sql::Properties properties;
// The mariadb connector is *supposed* to handle unix:// and pipe:// prefixes to hostName, but there are bugs where
// 1) it tries to parse a database from the connection string (like in tcp://localhost:3001/darkflame) based on the
// presence of a /
// 2) even avoiding that, the connector still assumes you're connecting with a tcp socket
// So, what we do in the presence of a unix socket or pipe is to set the hostname to the protocol and localhost,
// which avoids parsing errors while still ensuring the correct connection type is used, and then setting the appropriate
// property manually (which the URL parsing fails to do)
const std::string UNIX_PROTO = "unix://";
const std::string PIPE_PROTO = "pipe://";
if (host.find(UNIX_PROTO) == 0) {
properties["hostName"] = "unix://localhost";
properties["localSocket"] = host.substr(UNIX_PROTO.length()).c_str();
} else if (host.find(PIPE_PROTO) == 0) {
properties["hostName"] = "pipe://localhost";
properties["pipe"] = host.substr(PIPE_PROTO.length()).c_str();
} else {
properties["hostName"] = host.c_str();
}
properties["user"] = szUsername;
properties["password"] = szPassword;
properties["autoReconnect"] = "true";
Database::props = properties;
Database::database = database;
Database::Connect();
}
void Database::Connect() {
// `connect(const Properties& props)` segfaults in windows debug, but
// `connect(const SQLString& host, const SQLString& user, const SQLString& pwd)` doesn't handle pipes/unix sockets correctly
if (Database::props.find("localSocket") != Database::props.end() || Database::props.find("pipe") != Database::props.end()) {
con = driver->connect(Database::props);
} else {
con = driver->connect(Database::props["hostName"].c_str(), Database::props["user"].c_str(), Database::props["password"].c_str());
}
con->setSchema(Database::database.c_str());
}
void Database::Destroy(std::string source, bool log) {
if (!con) return;
if (log) {
if (source != "") Game::logger->Log("Database", "Destroying MySQL connection from %s!", source.c_str());
else Game::logger->Log("Database", "Destroying MySQL connection!");
}
con->close();
delete con;
} //Destroy
sql::Statement* Database::CreateStmt() {
sql::Statement* toReturn = con->createStatement();
return toReturn;
} //CreateStmt
sql::PreparedStatement* Database::CreatePreppedStmt(const std::string& query) {
const char* test = query.c_str();
size_t size = query.length();
sql::SQLString str(test, size);
if (!con) {
Connect();
Game::logger->Log("Database", "Trying to reconnect to MySQL");
}
if (!con->isValid() || con->isClosed()) {
delete con;
con = nullptr;
Connect();
Game::logger->Log("Database", "Trying to reconnect to MySQL from invalid or closed connection");
}
auto* stmt = con->prepareStatement(str);
return stmt;
} //CreatePreppedStmt
void Database::Commit() {
Database::con->commit();
}
bool Database::GetAutoCommit() {
// TODO This should not just access a pointer. A future PR should update this
// to check for null and throw an error if the connection is not valid.
return con->getAutoCommit();
}
void Database::SetAutoCommit(bool value) {
// TODO This should not just access a pointer. A future PR should update this
// to check for null and throw an error if the connection is not valid.
Database::con->setAutoCommit(value);
}

31
dDatabase/Database.h Normal file
View File

@@ -0,0 +1,31 @@
#pragma once
#include <string>
#include <conncpp.hpp>
class MySqlException : public std::runtime_error {
public:
MySqlException() : std::runtime_error("MySQL error!") {}
MySqlException(const std::string& msg) : std::runtime_error(msg.c_str()) {}
};
class Database {
private:
static sql::Driver* driver;
static sql::Connection* con;
static sql::Properties props;
static std::string database;
public:
static void Connect(const std::string& host, const std::string& database, const std::string& username, const std::string& password);
static void Connect();
static void Destroy(std::string source = "", bool log = true);
static sql::Statement* CreateStmt();
static sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
static void Commit();
static bool GetAutoCommit();
static void SetAutoCommit(bool value);
static std::string GetDatabase() { return database; }
static sql::Properties GetProperties() { return props; }
};

View File

@@ -1,12 +0,0 @@
set(DDATABASE_GAMEDATABASE_SOURCES
"Database.cpp"
"MigrationRunner.cpp"
)
add_subdirectory(MySQL)
foreach(file ${DDATABSE_DATABSES_MYSQL_SOURCES})
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} "MySQL/${file}")
endforeach()
set(DDATABASE_GAMEDATABASE_SOURCES ${DDATABASE_GAMEDATABASE_SOURCES} PARENT_SCOPE)

View File

@@ -1,40 +0,0 @@
#include "Database.h"
#include "Game.h"
#include "dConfig.h"
#include "Logger.h"
#include "MySQLDatabase.h"
#include "DluAssert.h"
#pragma warning (disable:4251) //Disables SQL warnings
namespace {
GameDatabase* database = nullptr;
}
void Database::Connect() {
if (database) {
LOG("Tried to connect to database when it's already connected!");
return;
}
database = new MySQLDatabase();
database->Connect();
}
GameDatabase* Database::Get() {
if (!database) {
LOG("Tried to get database when it's not connected!");
Connect();
}
return database;
}
void Database::Destroy(std::string source) {
if (database) {
database->Destroy(source);
delete database;
database = nullptr;
} else {
LOG("Trying to destroy database when it's not connected!");
}
}

View File

@@ -1,12 +0,0 @@
#pragma once
#include <string>
#include <conncpp.hpp>
#include "GameDatabase.h"
namespace Database {
void Connect();
GameDatabase* Get();
void Destroy(std::string source = "");
};

View File

@@ -1,57 +0,0 @@
#ifndef __GAMEDATABASE__H__
#define __GAMEDATABASE__H__
#include <optional>
#include "ILeaderboard.h"
#include "IPlayerCheatDetections.h"
#include "ICommandLog.h"
#include "IMail.h"
#include "IObjectIdTracker.h"
#include "IPlayKeys.h"
#include "IServers.h"
#include "IBugReports.h"
#include "IPropertyContents.h"
#include "IProperty.h"
#include "IPetNames.h"
#include "ICharXml.h"
#include "IMigrationHistory.h"
#include "IUgc.h"
#include "IFriends.h"
#include "ICharInfo.h"
#include "IAccounts.h"
#include "IActivityLog.h"
#include "IIgnoreList.h"
#include "IAccountsRewardCodes.h"
namespace sql {
class Statement;
class PreparedStatement;
};
#ifdef _DEBUG
# define DLU_SQL_TRY_CATCH_RETHROW(x) do { try { x; } catch (sql::SQLException& ex) { LOG("SQL Error: %s", ex.what()); throw; } } while(0)
#else
# define DLU_SQL_TRY_CATCH_RETHROW(x) x
#endif // _DEBUG
class GameDatabase :
public IPlayKeys, public ILeaderboard, public IObjectIdTracker, public IServers,
public IMail, public ICommandLog, public IPlayerCheatDetections, public IBugReports,
public IPropertyContents, public IProperty, public IPetNames, public ICharXml,
public IMigrationHistory, public IUgc, public IFriends, public ICharInfo,
public IAccounts, public IActivityLog, public IAccountsRewardCodes, public IIgnoreList {
public:
virtual ~GameDatabase() = default;
// TODO: These should be made private.
virtual void Connect() = 0;
virtual void Destroy(std::string source = "") = 0;
virtual void ExecuteCustomQuery(const std::string_view query) = 0;
virtual sql::PreparedStatement* CreatePreppedStmt(const std::string& query) = 0;
virtual void Commit() = 0;
virtual bool GetAutoCommit() = 0;
virtual void SetAutoCommit(bool value) = 0;
virtual void DeleteCharacter(const uint32_t characterId) = 0;
};
#endif //!__GAMEDATABASE__H__

View File

@@ -1,37 +0,0 @@
#ifndef __IACCOUNTS__H__
#define __IACCOUNTS__H__
#include <cstdint>
#include <optional>
#include <string_view>
enum class eGameMasterLevel : uint8_t;
class IAccounts {
public:
struct Info {
std::string bcryptPassword;
uint32_t id{};
uint32_t playKeyId{};
bool banned{};
bool locked{};
eGameMasterLevel maxGmLevel{};
};
// Get the account info for the given username.
virtual std::optional<IAccounts::Info> GetAccountInfo(const std::string_view username) = 0;
// Update the account's unmute time.
virtual void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) = 0;
// Update the account's ban status.
virtual void UpdateAccountBan(const uint32_t accountId, const bool banned) = 0;
// Update the account's password.
virtual void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) = 0;
// Add a new account to the database.
virtual void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) = 0;
};
#endif //!__IACCOUNTS__H__

View File

@@ -1,13 +0,0 @@
#ifndef __IACCOUNTSREWARDCODES__H__
#define __IACCOUNTSREWARDCODES__H__
#include <cstdint>
#include <vector>
class IAccountsRewardCodes {
public:
virtual void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) = 0;
virtual std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) = 0;
};
#endif //!__IACCOUNTSREWARDCODES__H__

View File

@@ -1,19 +0,0 @@
#ifndef __IACTIVITYLOG__H__
#define __IACTIVITYLOG__H__
#include <cstdint>
#include "dCommonVars.h"
enum class eActivityType : uint32_t {
PlayerLoggedIn,
PlayerLoggedOut,
};
class IActivityLog {
public:
// Update the activity log for the given account.
virtual void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) = 0;
};
#endif //!__IACTIVITYLOG__H__

View File

@@ -1,20 +0,0 @@
#ifndef __IBUGREPORTS__H__
#define __IBUGREPORTS__H__
#include <cstdint>
#include <string_view>
class IBugReports {
public:
struct Info {
std::string body;
std::string clientVersion;
std::string otherPlayer;
std::string selection;
uint32_t characterId{};
};
// Add a new bug report to the database.
virtual void InsertNewBugReport(const Info& info) = 0;
};
#endif //!__IBUGREPORTS__H__

View File

@@ -1,49 +0,0 @@
#ifndef __ICHARINFO__H__
#define __ICHARINFO__H__
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include "ePermissionMap.h"
class ICharInfo {
public:
struct Info {
std::string name;
std::string pendingName;
uint32_t id{};
uint32_t accountId{};
bool needsRename{};
LWOCLONEID cloneId{};
ePermissionMap permissionMap{};
};
// Get the approved names of all characters.
virtual std::vector<std::string> GetApprovedCharacterNames() = 0;
// Get the character info for the given character id.
virtual std::optional<ICharInfo::Info> GetCharacterInfo(const uint32_t charId) = 0;
// Get the character info for the given character name.
virtual std::optional<ICharInfo::Info> GetCharacterInfo(const std::string_view name) = 0;
// Get the character ids for the given account.
virtual std::vector<uint32_t> GetAccountCharacterIds(const uint32_t accountId) = 0;
// Insert a new character into the database.
virtual void InsertNewCharacter(const ICharInfo::Info info) = 0;
// Set the name of the given character.
virtual void SetCharacterName(const uint32_t characterId, const std::string_view name) = 0;
// Set the pending name of the given character.
virtual void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) = 0;
// Updates the given character ids last login to be right now.
virtual void UpdateLastLoggedInCharacter(const uint32_t characterId) = 0;
};
#endif //!__ICHARINFO__H__

View File

@@ -1,20 +0,0 @@
#ifndef __ICHARXML__H__
#define __ICHARXML__H__
#include <cstdint>
#include <string>
#include <string_view>
class ICharXml {
public:
// Get the character xml for the given character id.
virtual std::string GetCharacterXml(const uint32_t charId) = 0;
// Update the character xml for the given character id.
virtual void UpdateCharacterXml(const uint32_t charId, const std::string_view lxfml) = 0;
// Insert the character xml for the given character id.
virtual void InsertCharacterXml(const uint32_t characterId, const std::string_view lxfml) = 0;
};
#endif //!__ICHARXML__H__

View File

@@ -1,14 +0,0 @@
#ifndef __ICOMMANDLOG__H__
#define __ICOMMANDLOG__H__
#include <cstdint>
#include <string_view>
class ICommandLog {
public:
// Insert a new slash command log entry.
virtual void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) = 0;
};
#endif //!__ICOMMANDLOG__H__

View File

@@ -1,32 +0,0 @@
#ifndef __IFRIENDS__H__
#define __IFRIENDS__H__
#include <cstdint>
#include <optional>
#include <vector>
class IFriends {
public:
struct BestFriendStatus {
uint32_t playerCharacterId{};
uint32_t friendCharacterId{};
uint32_t bestFriendStatus{};
};
// Get the friends list for the given character id.
virtual std::vector<FriendData> GetFriendsList(const uint32_t charId) = 0;
// Get the best friend status for the given player and friend character ids.
virtual std::optional<IFriends::BestFriendStatus> GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0;
// Set the best friend status for the given player and friend character ids.
virtual void SetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId, const uint32_t bestFriendStatus) = 0;
// Add a friend to the given character id.
virtual void AddFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0;
// Remove a friend from the given character id.
virtual void RemoveFriend(const uint32_t playerCharacterId, const uint32_t friendCharacterId) = 0;
};
#endif //!__IFRIENDS__H__

View File

@@ -1,20 +0,0 @@
#ifndef __IIGNORELIST__H__
#define __IIGNORELIST__H__
#include <cstdint>
#include <string>
#include <vector>
class IIgnoreList {
public:
struct Info {
std::string name;
uint32_t id;
};
virtual std::vector<Info> GetIgnoreList(const uint32_t playerId) = 0;
virtual void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) = 0;
virtual void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) = 0;
};
#endif //!__IIGNORELIST__H__

View File

@@ -1,14 +0,0 @@
#ifndef __ILEADERBOARD__H__
#define __ILEADERBOARD__H__
#include <cstdint>
#include <optional>
class ILeaderboard {
public:
// Get the donation total for the given activity id.
virtual std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) = 0;
};
#endif //!__ILEADERBOARD__H__

View File

@@ -1,54 +0,0 @@
#ifndef __IMAIL__H__
#define __IMAIL__H__
#include <cstdint>
#include <optional>
#include <string_view>
#include "dCommonVars.h"
#include "NiQuaternion.h"
#include "NiPoint3.h"
class IMail {
public:
struct MailInfo {
std::string senderUsername;
std::string recipient;
std::string subject;
std::string body;
uint64_t id{};
uint32_t senderId{};
uint32_t receiverId{};
uint64_t timeSent{};
bool wasRead{};
struct {
LWOOBJID itemID{};
int32_t itemCount{};
LOT itemLOT{};
LWOOBJID itemSubkey{};
};
};
// Insert a new mail into the database.
virtual void InsertNewMail(const MailInfo& mail) = 0;
// Get the mail for the given character id.
virtual std::vector<MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) = 0;
// Get the mail for the given mail id.
virtual std::optional<MailInfo> GetMail(const uint64_t mailId) = 0;
// Get the number of unread mail for the given character id.
virtual uint32_t GetUnreadMailCount(const uint32_t characterId) = 0;
// Mark the given mail as read.
virtual void MarkMailRead(const uint64_t mailId) = 0;
// Claim the item from the given mail.
virtual void ClaimMailItem(const uint64_t mailId) = 0;
// Delete the given mail.
virtual void DeleteMail(const uint64_t mailId) = 0;
};
#endif //!__IMAIL__H__

View File

@@ -1,17 +0,0 @@
#ifndef __IMIGRATIONHISTORY__H__
#define __IMIGRATIONHISTORY__H__
#include <string_view>
class IMigrationHistory {
public:
// Create the migration history table.
virtual void CreateMigrationHistoryTable() = 0;
// Check if the given migration has been run.
virtual bool IsMigrationRun(const std::string_view str) = 0;
// Insert the given migration into the migration history table.
virtual void InsertMigration(const std::string_view str) = 0;
};
#endif //!__IMIGRATIONHISTORY__H__

View File

@@ -1,19 +0,0 @@
#ifndef __IOBJECTIDTRACKER__H__
#define __IOBJECTIDTRACKER__H__
#include <cstdint>
#include <optional>
class IObjectIdTracker {
public:
// Get the current persistent id.
virtual std::optional<uint32_t> GetCurrentPersistentId() = 0;
// Insert the default persistent id.
virtual void InsertDefaultPersistentId() = 0;
// Update the persistent id.
virtual void UpdatePersistentId(const uint32_t newId) = 0;
};
#endif //!__IOBJECTIDTRACKER__H__

View File

@@ -1,21 +0,0 @@
#ifndef __IPETNAMES__H__
#define __IPETNAMES__H__
#include <cstdint>
#include <optional>
class IPetNames {
public:
struct Info {
std::string petName;
int32_t approvalStatus{};
};
// Set the pet name moderation status for the given pet id.
virtual void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) = 0;
// Get pet info for the given pet id.
virtual std::optional<IPetNames::Info> GetPetNameInfo(const LWOOBJID& petId) = 0;
};
#endif //!__IPETNAMES__H__

View File

@@ -1,15 +0,0 @@
#ifndef __IPLAYKEYS__H__
#define __IPLAYKEYS__H__
#include <cstdint>
#include <optional>
class IPlayKeys {
public:
// Get the playkey id for the given playkey.
// Optional of bool may seem pointless, however the optional indicates if the playkey exists
// and the bool indicates if the playkey is active.
virtual std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) = 0;
};
#endif //!__IPLAYKEYS__H__

View File

@@ -1,20 +0,0 @@
#ifndef __IPLAYERCHEATDETECTIONS__H__
#define __IPLAYERCHEATDETECTIONS__H__
#include <cstdint>
#include <optional>
class IPlayerCheatDetections {
public:
struct Info {
std::optional<uint32_t> userId = std::nullopt;
std::string username;
std::string systemAddress;
std::string extraMessage;
};
// Insert a new cheat detection.
virtual void InsertCheatDetection(const IPlayerCheatDetections::Info& info) = 0;
};
#endif //!__IPLAYERCHEATDETECTIONS__H__

View File

@@ -1,38 +0,0 @@
#ifndef __IPROPERTY__H__
#define __IPROPERTY__H__
#include <cstdint>
#include <optional>
class IProperty {
public:
struct Info {
std::string name;
std::string description;
std::string rejectionReason;
LWOOBJID id{};
uint32_t ownerId{};
LWOCLONEID cloneId{};
int32_t privacyOption{};
uint32_t modApproved{};
uint32_t lastUpdatedTime{};
uint32_t claimedTime{};
uint32_t reputation{};
};
// Get the property info for the given property id.
virtual std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) = 0;
// Update the property moderation info for the given property id.
virtual void UpdatePropertyModerationInfo(const IProperty::Info& info) = 0;
// Update the property details for the given property id.
virtual void UpdatePropertyDetails(const IProperty::Info& info) = 0;
// Update the property performance cost for the given property id.
virtual void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) = 0;
// Insert a new property into the database.
virtual void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) = 0;
};
#endif //!__IPROPERTY__H__

View File

@@ -1,40 +0,0 @@
#ifndef __IPROPERTIESCONTENTS__H__
#define __IPROPERTIESCONTENTS__H__
#include <cstdint>
#include <string_view>
class IPropertyContents {
public:
struct Model {
inline bool operator==(const LWOOBJID& other) const noexcept {
return id == other;
}
NiPoint3 position;
NiQuaternion rotation;
LWOOBJID id{};
LOT lot{};
uint32_t ugcId{};
};
// Inserts a new UGC model into the database.
virtual void InsertNewUgcModel(
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) = 0;
// Get the property models for the given property id.
virtual std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) = 0;
// Insert a new property model into the database.
virtual void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) = 0;
// Update the model position and rotation for the given property id.
virtual void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) = 0;
// Remove the model for the given property id.
virtual void RemoveModel(const LWOOBJID& modelId) = 0;
};
#endif //!__IPROPERTIESCONTENTS__H__

View File

@@ -1,21 +0,0 @@
#ifndef __ISERVERS__H__
#define __ISERVERS__H__
#include <cstdint>
#include <optional>
class IServers {
public:
struct MasterInfo {
std::string ip;
uint32_t port{};
};
// Set the master server ip and port.
virtual void SetMasterIp(const std::string_view ip, const uint32_t port) = 0;
// Get the master server info.
virtual std::optional<MasterInfo> GetMasterInfo() = 0;
};
#endif //!__ISERVERS__H__

View File

@@ -1,32 +0,0 @@
#ifndef __IUGC__H__
#define __IUGC__H__
#include <cstdint>
#include <sstream>
#include <optional>
#include <string>
#include <string_view>
class IUgc {
public:
struct Model {
std::stringstream lxfmlData;
LWOOBJID id{};
};
// Gets all UGC models for the given property id.
virtual std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) = 0;
// Gets all Ugcs models.
virtual std::vector<IUgc::Model> GetAllUgcModels() = 0;
// Removes ugc models that are not referenced by any property.
virtual void RemoveUnreferencedUgcModels() = 0;
// Deletes the ugc model for the given model id.
virtual void DeleteUgcModelData(const LWOOBJID& modelId) = 0;
// Inserts a new UGC model into the database.
virtual void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) = 0;
};
#endif //!__IUGC__H__

View File

@@ -1,11 +0,0 @@
SET(DDATABSE_DATABSES_MYSQL_SOURCES
"MySQLDatabase.cpp"
)
add_subdirectory(Tables)
foreach(file ${DDATABASES_DATABASES_MYSQL_TABLES_SOURCES})
set(DDATABSE_DATABSES_MYSQL_SOURCES ${DDATABSE_DATABSES_MYSQL_SOURCES} "Tables/${file}")
endforeach()
set(DDATABSE_DATABSES_MYSQL_SOURCES ${DDATABSE_DATABSES_MYSQL_SOURCES} PARENT_SCOPE)

View File

@@ -1,113 +0,0 @@
#include "MySQLDatabase.h"
#include "Database.h"
#include "Game.h"
#include "dConfig.h"
#include "Logger.h"
namespace {
std::string databaseName;
sql::Properties properties;
sql::Driver* driver = nullptr;
sql::Connection* con = nullptr;
};
void MySQLDatabase::Connect() {
driver = sql::mariadb::get_driver_instance();
// The mariadb connector is *supposed* to handle unix:// and pipe:// prefixes to hostName, but there are bugs where
// 1) it tries to parse a database from the connection string (like in tcp://localhost:3001/darkflame) based on the
// presence of a /
// 2) even avoiding that, the connector still assumes you're connecting with a tcp socket
// So, what we do in the presence of a unix socket or pipe is to set the hostname to the protocol and localhost,
// which avoids parsing errors while still ensuring the correct connection type is used, and then setting the appropriate
// property manually (which the URL parsing fails to do)
const std::string UNIX_PROTO = "unix://";
const std::string PIPE_PROTO = "pipe://";
std::string mysql_host = Game::config->GetValue("mysql_host");
if (mysql_host.find(UNIX_PROTO) == 0) {
properties["hostName"] = "unix://localhost";
properties["localSocket"] = mysql_host.substr(UNIX_PROTO.length()).c_str();
} else if (mysql_host.find(PIPE_PROTO) == 0) {
properties["hostName"] = "pipe://localhost";
properties["pipe"] = mysql_host.substr(PIPE_PROTO.length()).c_str();
} else {
properties["hostName"] = mysql_host.c_str();
}
properties["user"] = Game::config->GetValue("mysql_username").c_str();
properties["password"] = Game::config->GetValue("mysql_password").c_str();
properties["autoReconnect"] = "true";
databaseName = Game::config->GetValue("mysql_database").c_str();
// `connect(const Properties& props)` segfaults in windows debug, but
// `connect(const SQLString& host, const SQLString& user, const SQLString& pwd)` doesn't handle pipes/unix sockets correctly
if (properties.find("localSocket") != properties.end() || properties.find("pipe") != properties.end()) {
con = driver->connect(properties);
} else {
con = driver->connect(properties["hostName"].c_str(), properties["user"].c_str(), properties["password"].c_str());
}
con->setSchema(databaseName.c_str());
}
void MySQLDatabase::Destroy(std::string source) {
if (!con) return;
if (source.empty()) LOG("Destroying MySQL connection!");
else LOG("Destroying MySQL connection from %s!", source.c_str());
con->close();
delete con;
con = nullptr;
}
void MySQLDatabase::ExecuteCustomQuery(const std::string_view query) {
std::unique_ptr<sql::Statement>(con->createStatement())->execute(query.data());
}
sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& query) {
if (!con) {
Connect();
LOG("Trying to reconnect to MySQL");
}
if (!con->isValid() || con->isClosed()) {
delete con;
con = nullptr;
Connect();
LOG("Trying to reconnect to MySQL from invalid or closed connection");
}
return con->prepareStatement(sql::SQLString(query.c_str(), query.length()));
}
void MySQLDatabase::Commit() {
con->commit();
}
bool MySQLDatabase::GetAutoCommit() {
// TODO This should not just access a pointer. A future PR should update this
// to check for null and throw an error if the connection is not valid.
return con->getAutoCommit();
}
void MySQLDatabase::SetAutoCommit(bool value) {
// TODO This should not just access a pointer. A future PR should update this
// to check for null and throw an error if the connection is not valid.
con->setAutoCommit(value);
}
void MySQLDatabase::DeleteCharacter(const uint32_t characterId) {
ExecuteDelete("DELETE FROM charxml WHERE id=? LIMIT 1;", characterId);
ExecuteDelete("DELETE FROM command_log WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM friends WHERE player_id=? OR friend_id=?;", characterId, characterId);
ExecuteDelete("DELETE FROM leaderboard WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM properties_contents WHERE property_id IN (SELECT id FROM properties WHERE owner_id=?);", characterId);
ExecuteDelete("DELETE FROM properties WHERE owner_id=?;", characterId);
ExecuteDelete("DELETE FROM ugc WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM activity_log WHERE character_id=?;", characterId);
ExecuteDelete("DELETE FROM mail WHERE receiver_id=?;", characterId);
ExecuteDelete("DELETE FROM charinfo WHERE id=? LIMIT 1;", characterId);
}

View File

@@ -1,250 +0,0 @@
#ifndef __MYSQLDATABASE__H__
#define __MYSQLDATABASE__H__
#include <conncpp.hpp>
#include <memory>
#include "GameDatabase.h"
typedef std::unique_ptr<sql::PreparedStatement>& UniquePreppedStmtRef;
// Purposefully no definition for this to provide linker errors in the case someone tries to
// bind a parameter to a type that isn't defined.
template<typename ParamType>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const ParamType param);
// This is a function to set each parameter in a prepared statement.
// This is accomplished with a combination of parameter packing and Fold Expressions.
// The constexpr if statement is used to prevent the compiler from trying to call SetParam with 0 arguments.
template<typename... Args>
void SetParams(UniquePreppedStmtRef stmt, Args&&... args) {
if constexpr (sizeof...(args) != 0) {
int i = 1;
(SetParam(stmt, i++, args), ...);
}
}
class MySQLDatabase : public GameDatabase {
public:
void Connect() override;
void Destroy(std::string source = "") override;
sql::PreparedStatement* CreatePreppedStmt(const std::string& query) override;
void Commit() override;
bool GetAutoCommit() override;
void SetAutoCommit(bool value) override;
void ExecuteCustomQuery(const std::string_view query) override;
// Overloaded queries
std::optional<IServers::MasterInfo> GetMasterInfo() override;
std::vector<std::string> GetApprovedCharacterNames() override;
std::vector<FriendData> GetFriendsList(uint32_t charID) override;
std::optional<IFriends::BestFriendStatus> GetBestFriendStatus(const uint32_t playerCharacterId, const uint32_t friendCharacterId) override;
void SetBestFriendStatus(const uint32_t playerAccountId, const uint32_t friendAccountId, const uint32_t bestFriendStatus) override;
void AddFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void RemoveFriend(const uint32_t playerAccountId, const uint32_t friendAccountId) override;
void UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) override;
void DeleteUgcModelData(const LWOOBJID& modelId) override;
void UpdateUgcModelData(const LWOOBJID& modelId, std::istringstream& lxfml) override;
std::vector<IUgc::Model> GetAllUgcModels() override;
void CreateMigrationHistoryTable() override;
bool IsMigrationRun(const std::string_view str) override;
void InsertMigration(const std::string_view str) override;
std::optional<ICharInfo::Info> GetCharacterInfo(const uint32_t charId) override;
std::optional<ICharInfo::Info> GetCharacterInfo(const std::string_view charId) override;
std::string GetCharacterXml(const uint32_t accountId) override;
void UpdateCharacterXml(const uint32_t characterId, const std::string_view lxfml) override;
std::optional<IAccounts::Info> GetAccountInfo(const std::string_view username) override;
void InsertNewCharacter(const ICharInfo::Info info) override;
void InsertCharacterXml(const uint32_t accountId, const std::string_view lxfml) override;
std::vector<uint32_t> GetAccountCharacterIds(uint32_t accountId) override;
void DeleteCharacter(const uint32_t characterId) override;
void SetCharacterName(const uint32_t characterId, const std::string_view name) override;
void SetPendingCharacterName(const uint32_t characterId, const std::string_view name) override;
void UpdateLastLoggedInCharacter(const uint32_t characterId) override;
void SetPetNameModerationStatus(const LWOOBJID& petId, const IPetNames::Info& info) override;
std::optional<IPetNames::Info> GetPetNameInfo(const LWOOBJID& petId) override;
std::optional<IProperty::Info> GetPropertyInfo(const LWOMAPID mapId, const LWOCLONEID cloneId) override;
void UpdatePropertyModerationInfo(const IProperty::Info& info) override;
void UpdatePropertyDetails(const IProperty::Info& info) override;
void InsertNewProperty(const IProperty::Info& info, const uint32_t templateId, const LWOZONEID& zoneId) override;
std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) override;
void RemoveUnreferencedUgcModels() override;
void InsertNewPropertyModel(const LWOOBJID& propertyId, const IPropertyContents::Model& model, const std::string_view name) override;
void UpdateModelPositionRotation(const LWOOBJID& propertyId, const NiPoint3& position, const NiQuaternion& rotation) override;
void RemoveModel(const LWOOBJID& modelId) override;
void UpdatePerformanceCost(const LWOZONEID& zoneId, const float performanceCost) override;
void InsertNewBugReport(const IBugReports::Info& info) override;
void InsertCheatDetection(const IPlayerCheatDetections::Info& info) override;
void InsertNewMail(const IMail::MailInfo& mail) override;
void InsertNewUgcModel(
std::istringstream& sd0Data,
const uint32_t blueprintId,
const uint32_t accountId,
const uint32_t characterId) override;
std::vector<IMail::MailInfo> GetMailForPlayer(const uint32_t characterId, const uint32_t numberOfMail) override;
std::optional<IMail::MailInfo> GetMail(const uint64_t mailId) override;
uint32_t GetUnreadMailCount(const uint32_t characterId) override;
void MarkMailRead(const uint64_t mailId) override;
void DeleteMail(const uint64_t mailId) override;
void ClaimMailItem(const uint64_t mailId) override;
void InsertSlashCommandUsage(const uint32_t characterId, const std::string_view command) override;
void UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) override;
void UpdateAccountBan(const uint32_t accountId, const bool banned) override;
void UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) override;
void InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) override;
void SetMasterIp(const std::string_view ip, const uint32_t port) override;
std::optional<uint32_t> GetCurrentPersistentId() override;
void InsertDefaultPersistentId() override;
void UpdatePersistentId(const uint32_t id) override;
std::optional<uint32_t> GetDonationTotal(const uint32_t activityId) override;
std::optional<bool> IsPlaykeyActive(const int32_t playkeyId) override;
std::vector<IUgc::Model> GetUgcModels(const LWOOBJID& propertyId) override;
void AddIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override;
void RemoveIgnore(const uint32_t playerId, const uint32_t ignoredPlayerId) override;
std::vector<IIgnoreList::Info> GetIgnoreList(const uint32_t playerId) override;
void InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) override;
std::vector<uint32_t> GetRewardCodesByAccountID(const uint32_t account_id) override;
private:
// Generic query functions that can be used for any query.
// Return type may be different depending on the query, so it is up to the caller to check the return type.
// The first argument is the query string, and the rest are the parameters to bind to the query.
// The return type is a unique_ptr to the result set, which is deleted automatically when it goes out of scope
template<typename... Args>
inline std::unique_ptr<sql::ResultSet> ExecuteSelect(const std::string& query, Args&&... args) {
std::unique_ptr<sql::PreparedStatement> preppedStmt(CreatePreppedStmt(query));
SetParams(preppedStmt, std::forward<Args>(args)...);
DLU_SQL_TRY_CATCH_RETHROW(return std::unique_ptr<sql::ResultSet>(preppedStmt->executeQuery()));
}
template<typename... Args>
inline void ExecuteDelete(const std::string& query, Args&&... args) {
std::unique_ptr<sql::PreparedStatement> preppedStmt(CreatePreppedStmt(query));
SetParams(preppedStmt, std::forward<Args>(args)...);
DLU_SQL_TRY_CATCH_RETHROW(preppedStmt->execute());
}
template<typename... Args>
inline int32_t ExecuteUpdate(const std::string& query, Args&&... args) {
std::unique_ptr<sql::PreparedStatement> preppedStmt(CreatePreppedStmt(query));
SetParams(preppedStmt, std::forward<Args>(args)...);
DLU_SQL_TRY_CATCH_RETHROW(return preppedStmt->executeUpdate());
}
template<typename... Args>
inline bool ExecuteInsert(const std::string& query, Args&&... args) {
std::unique_ptr<sql::PreparedStatement> preppedStmt(CreatePreppedStmt(query));
SetParams(preppedStmt, std::forward<Args>(args)...);
DLU_SQL_TRY_CATCH_RETHROW(return preppedStmt->execute());
}
};
// Below are each of the definitions of SetParam for each supported type.
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string_view param) {
// LOG("%s", param.data());
stmt->setString(index, param.data());
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const char* param) {
// LOG("%s", param);
stmt->setString(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::string param) {
// LOG("%s", param.c_str());
stmt->setString(index, param.c_str());
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int8_t param) {
// LOG("%u", param);
stmt->setByte(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint8_t param) {
// LOG("%d", param);
stmt->setByte(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int16_t param) {
// LOG("%u", param);
stmt->setShort(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint16_t param) {
// LOG("%d", param);
stmt->setShort(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint32_t param) {
// LOG("%u", param);
stmt->setUInt(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int32_t param) {
// LOG("%d", param);
stmt->setInt(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const int64_t param) {
// LOG("%llu", param);
stmt->setInt64(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const uint64_t param) {
// LOG("%llu", param);
stmt->setUInt64(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const float param) {
// LOG("%f", param);
stmt->setFloat(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const double param) {
// LOG("%f", param);
stmt->setDouble(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const bool param) {
// LOG("%d", param);
stmt->setBoolean(index, param);
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::istream* param) {
// LOG("Blob");
// This is the one time you will ever see me use const_cast.
stmt->setBlob(index, const_cast<std::istream*>(param));
}
template<>
inline void SetParam(UniquePreppedStmtRef stmt, const int index, const std::optional<uint32_t> param) {
if (param) {
// LOG("%d", param.value());
stmt->setInt(index, param.value());
} else {
// LOG("Null");
stmt->setNull(index, sql::DataType::SQLNULL);
}
}
#endif //!__MYSQLDATABASE__H__

View File

@@ -1,37 +0,0 @@
#include "MySQLDatabase.h"
#include "eGameMasterLevel.h"
std::optional<IAccounts::Info> MySQLDatabase::GetAccountInfo(const std::string_view username) {
auto result = ExecuteSelect("SELECT id, password, banned, locked, play_key_id, gm_level FROM accounts WHERE name = ? LIMIT 1;", username);
if (!result->next()) {
return std::nullopt;
}
IAccounts::Info toReturn;
toReturn.id = result->getUInt("id");
toReturn.maxGmLevel = static_cast<eGameMasterLevel>(result->getInt("gm_level"));
toReturn.bcryptPassword = result->getString("password").c_str();
toReturn.banned = result->getBoolean("banned");
toReturn.locked = result->getBoolean("locked");
toReturn.playKeyId = result->getUInt("play_key_id");
return toReturn;
}
void MySQLDatabase::UpdateAccountUnmuteTime(const uint32_t accountId, const uint64_t timeToUnmute) {
ExecuteUpdate("UPDATE accounts SET mute_expire = ? WHERE id = ?;", timeToUnmute, accountId);
}
void MySQLDatabase::UpdateAccountBan(const uint32_t accountId, const bool banned) {
ExecuteUpdate("UPDATE accounts SET banned = ? WHERE id = ?;", banned, accountId);
}
void MySQLDatabase::UpdateAccountPassword(const uint32_t accountId, const std::string_view bcryptpassword) {
ExecuteUpdate("UPDATE accounts SET password = ? WHERE id = ?;", bcryptpassword, accountId);
}
void MySQLDatabase::InsertNewAccount(const std::string_view username, const std::string_view bcryptpassword) {
ExecuteInsert("INSERT INTO accounts (name, password, gm_level) VALUES (?, ?, ?);", username, bcryptpassword, static_cast<int32_t>(eGameMasterLevel::OPERATOR));
}

View File

@@ -1,17 +0,0 @@
#include "MySQLDatabase.h"
void MySQLDatabase::InsertRewardCode(const uint32_t account_id, const uint32_t reward_code) {
ExecuteInsert("INSERT IGNORE INTO accounts_rewardcodes (account_id, rewardcode) VALUES (?, ?);", account_id, reward_code);
}
std::vector<uint32_t> MySQLDatabase::GetRewardCodesByAccountID(const uint32_t account_id) {
auto result = ExecuteSelect("SELECT rewardcode FROM accounts_rewardcodes WHERE account_id = ?;", account_id);
std::vector<uint32_t> toReturn;
toReturn.reserve(result->rowsCount());
while (result->next()) {
toReturn.push_back(result->getUInt("rewardcode"));
}
return toReturn;
}

View File

@@ -1,6 +0,0 @@
#include "MySQLDatabase.h"
void MySQLDatabase::UpdateActivityLog(const uint32_t characterId, const eActivityType activityType, const LWOMAPID mapId) {
ExecuteInsert("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);",
characterId, static_cast<uint32_t>(activityType), static_cast<uint32_t>(time(NULL)), mapId);
}

View File

@@ -1,6 +0,0 @@
#include "MySQLDatabase.h"
void MySQLDatabase::InsertNewBugReport(const IBugReports::Info& info) {
ExecuteInsert("INSERT INTO `bug_reports`(body, client_version, other_player_id, selection, reporter_id) VALUES (?, ?, ?, ?, ?)",
info.body, info.clientVersion, info.otherPlayer, info.selection, info.characterId);
}

Some files were not shown because too many files have changed in this diff Show More