Compare commits

..

2 Commits

Author SHA1 Message Date
0e8af24572 Make better 2023-05-09 22:37:57 -05:00
051395e4c9 fix: prevent negative imagination
And fail switch if we don't have enough imagination
2023-05-09 22:14:46 -05:00
523 changed files with 7970 additions and 10220 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

View File

@@ -36,16 +36,22 @@ jobs:
testPreset: "ci-${{matrix.os}}"
- name: artifacts
uses: actions/upload-artifact@v3
if: ${{ github.ref == 'ref/head/main' }}
with:
name: build-${{matrix.os}}
path: |
build/*Server*
build/*.ini
build/*.so
build/*.dll
build/vanity/
build/navmeshes/
build/migrations/
build/*.dcf
!build/*.pdb
!build/d*/
build
!build/tests
!build/Testing
!build/CMakeFiles
!build/DartConfiguration.tcl
!build/CTestTestfile.cmake
!build/CMakeCache.txt
!build/build.ninja
!build/_deps
!build/cmake_install.cmake
!build/*.a
!build/*.lib
!build/*.dir
!build/*.vcxproj
!build/*.vcxproj.filters

View File

@@ -97,56 +97,15 @@ make_directory(${CMAKE_BINARY_DIR}/logs)
# Copy resource files on first build
set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blacklist.dcf")
message(STATUS "Checking resource file integrity")
foreach (resource_file ${RESOURCE_FILES})
set(file_size 0)
if (EXISTS ${PROJECT_BINARY_DIR}/${resource_file})
file(SIZE ${PROJECT_BINARY_DIR}/${resource_file} file_size)
endif()
if (${file_size} EQUAL 0)
foreach(resource_file ${RESOURCE_FILES})
if (NOT EXISTS ${PROJECT_BINARY_DIR}/${resource_file})
configure_file(
${CMAKE_SOURCE_DIR}/resources/${resource_file} ${PROJECT_BINARY_DIR}/${resource_file}
COPYONLY
)
message(STATUS "Moved " ${resource_file} " to project binary directory")
elseif (resource_file MATCHES ".ini")
message(STATUS "Checking " ${resource_file} " for missing config options")
file(READ ${PROJECT_BINARY_DIR}/${resource_file} current_file_contents)
string(REPLACE "\\\n" "" current_file_contents ${current_file_contents})
string(REPLACE "\n" ";" current_file_contents ${current_file_contents})
set(parsed_current_file_contents "")
# Remove comment lines so they do not interfere with the variable parsing
foreach (line ${current_file_contents})
string(FIND ${line} "#" is_comment)
if (NOT ${is_comment} EQUAL 0)
string(APPEND parsed_current_file_contents ${line})
endif()
endforeach()
file(READ ${CMAKE_SOURCE_DIR}/resources/${resource_file} depot_file_contents)
string(REPLACE "\\\n" "" depot_file_contents ${depot_file_contents})
string(REPLACE "\n" ";" depot_file_contents ${depot_file_contents})
set(line_to_add "")
foreach (line ${depot_file_contents})
string(FIND ${line} "#" is_comment)
if (NOT ${is_comment} EQUAL 0)
string(REPLACE "=" ";" line_split ${line})
list(GET line_split 0 variable_name)
if (NOT ${parsed_current_file_contents} MATCHES ${variable_name})
message(STATUS "Adding missing config option " ${variable_name} " to " ${resource_file})
set(line_to_add ${line_to_add} ${line})
foreach (line_to_append ${line_to_add})
file(APPEND ${PROJECT_BINARY_DIR}/${resource_file} "\n" ${line_to_append})
endforeach()
file(APPEND ${PROJECT_BINARY_DIR}/${resource_file} "\n")
endif()
set(line_to_add "")
else()
set(line_to_add ${line_to_add} ${line})
endif()
endforeach()
message("Moved ${resource_file} to project binary directory")
endif()
endforeach()
message(STATUS "Resource file integrity check complete")
# Copy navmesh data on first build and extract it
if (NOT EXISTS ${PROJECT_BINARY_DIR}/navmeshes/)

View File

@@ -1,8 +1,12 @@
PROJECT_VERSION_MAJOR=1
PROJECT_VERSION_MINOR=1
PROJECT_VERSION_PATCH=1
PROJECT_VERSION_MINOR=0
PROJECT_VERSION_PATCH=4
# 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

@@ -179,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.
@@ -338,7 +338,7 @@ This is a Work in Progress, but below are some quick links to documentaion for s
## Former Contributors
* TheMachine
* Matthew
* [Raine](https://github.com/uwainium)
* [Raine](https://github.com/Rainebannister)
* Bricknave
## Special Thanks

View File

@@ -29,7 +29,6 @@ namespace Game {
dServer* server = nullptr;
dConfig* config = nullptr;
bool shouldShutdown = false;
std::mt19937 randomEngine;
}
dLogger* SetupLogger();
@@ -55,12 +54,34 @@ int main(int argc, char** argv) {
Game::logger->Log("AuthServer", "Version: %i.%i", PROJECT_VERSION_MAJOR, PROJECT_VERSION_MINOR);
Game::logger->Log("AuthServer", "Compiled on: %s", __TIMESTAMP__);
Database::Connect(Game::config);
//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");
// Get Master server IP and port
SocketDescriptor masterSock = Database::Connection->GetMasterServerIP();
try {
Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password);
} catch (sql::SQLException& ex) {
Game::logger->Log("AuthServer", "Got an error while connecting to the database: %s", ex.what());
Database::Destroy("AuthServer");
delete Game::server;
delete Game::logger;
return EXIT_FAILURE;
}
Game::randomEngine = std::mt19937(time(0));
//Find out the master's IP:
std::string masterIP;
uint32_t masterPort = 1500;
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;
@@ -68,7 +89,7 @@ int main(int argc, char** argv) {
if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients"));
if (Game::config->GetValue("port") != "") ourPort = std::atoi(Game::config->GetValue("port").c_str());
Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterSock.hostAddress, masterSock.port, ServerType::Auth, Game::config, &Game::shouldShutdown);
Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterIP, masterPort, ServerType::Auth, Game::config, &Game::shouldShutdown);
//Run it until server gets a kill message from Master:
auto t = std::chrono::high_resolution_clock::now();
@@ -107,7 +128,18 @@ int main(int argc, char** argv) {
//Every 10 min we ping our sql server to keep it alive hopefully:
if (framesSinceLastSQLPing >= sqlPingTime) {
Database::Connection->GetMasterServerIP();
//Find out the master's IP for absolutely no reason:
std::string masterIP;
uint32_t masterPort;
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++;
@@ -116,7 +148,9 @@ int main(int argc, char** argv) {
t += std::chrono::milliseconds(authFrameDelta); //Auth can run at a lower "fps"
std::this_thread::sleep_until(t);
}
//Delete our objects here:
Database::Destroy("AuthServer");
delete Game::server;
delete Game::logger;
delete Game::config;

View File

@@ -31,13 +31,16 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) {
ReadWordlistDCF("blacklist.dcf", false);
}
// Read player names that are ok as well:
auto names = Database::Connection->GetAllCharacterNames();
for (auto& name : names) {
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
m_ApprovedWords.push_back(CalculateHash(name));
//Read player names that are ok as well:
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() {

View File

@@ -3,7 +3,6 @@
#include "Database.h"
#include <vector>
#include "PacketUtils.h"
#include "BitStreamUtils.h"
#include "Game.h"
#include "dServer.h"
#include "GeneralUtils.h"
@@ -76,11 +75,11 @@ void ChatPacketHandler::HandleFriendlistRequest(Packet* packet) {
//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)friends.size());
@@ -413,21 +412,21 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) {
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;
@@ -435,7 +434,7 @@ 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);
@@ -452,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;
@@ -475,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;
@@ -710,13 +709,13 @@ void ChatPacketHandler::HandleTeamStatusRequest(Packet* packet) {
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;
@@ -725,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:
@@ -752,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:
@@ -777,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:
@@ -794,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:
@@ -823,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:
@@ -849,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:
@@ -883,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());
@@ -922,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;
@@ -938,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);
@@ -963,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

@@ -33,7 +33,6 @@ namespace Game {
dChatFilter* chatFilter = nullptr;
AssetManager* assetManager = nullptr;
bool shouldShutdown = false;
std::mt19937 randomEngine;
}
@@ -77,10 +76,34 @@ int main(int argc, char** argv) {
return EXIT_FAILURE;
}
Database::Connect(Game::config);
//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");
// Get Master server IP and port
SocketDescriptor masterSock = Database::Connection->GetMasterServerIP();
try {
Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password);
} catch (sql::SQLException& ex) {
Game::logger->Log("ChatServer", "Got an error while connecting to the database: %s", ex.what());
Database::Destroy("ChatServer");
delete Game::server;
delete Game::logger;
return EXIT_FAILURE;
}
//Find out the master's IP:
std::string masterIP;
uint32_t masterPort = 1000;
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;
@@ -88,11 +111,9 @@ int main(int argc, char** argv) {
if (Game::config->GetValue("max_clients") != "") maxClients = std::stoi(Game::config->GetValue("max_clients"));
if (Game::config->GetValue("port") != "") ourPort = std::atoi(Game::config->GetValue("port").c_str());
Game::server = new dServer(Game::config->GetValue("external_ip"), ourPort, 0, maxClients, false, true, Game::logger, masterSock.hostAddress, masterSock.port, ServerType::Chat, Game::config, &Game::shouldShutdown);
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));
//Run it until server gets a kill message from Master:
auto t = std::chrono::high_resolution_clock::now();
@@ -131,7 +152,18 @@ int main(int argc, char** argv) {
//Every 10 min we ping our sql server to keep it alive hopefully:
if (framesSinceLastSQLPing >= sqlPingTime) {
Database::Connection->GetMasterServerIP();
//Find out the master's IP for absolutely no reason:
std::string masterIP;
uint32_t masterPort;
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++;
@@ -141,8 +173,8 @@ int main(int argc, char** argv) {
std::this_thread::sleep_until(t);
}
// Delete our objects here:
Database::Destroy();
//Delete our objects here:
Database::Destroy("ChatServer");
delete Game::server;
delete Game::logger;
delete Game::config;

View File

@@ -6,11 +6,10 @@
#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"
PlayerContainer::PlayerContainer() {
}
@@ -41,7 +40,14 @@ void PlayerContainer::InsertPlayer(Packet* packet) {
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::Connection->InsertIntoActivityLog(data->playerID, 0, time(nullptr), 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) {
@@ -78,7 +84,14 @@ void PlayerContainer::RemovePlayer(Packet* packet) {
Game::logger->Log("PlayerContainer", "Removed user: %llu", playerID);
mPlayers.erase(playerID);
Database::Connection->InsertIntoActivityLog(playerID, 1, time(nullptr), 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) {
@@ -133,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);
@@ -194,14 +207,6 @@ TeamData* PlayerContainer::GetTeam(LWOOBJID playerID) {
}
void PlayerContainer::AddMember(TeamData* team, LWOOBJID playerID) {
if (team->memberIDs.size() >= 4){
Game::logger->Log("PlayerContainer", "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;
@@ -340,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);

View File

@@ -1,79 +1,77 @@
#include "AMFDeserialize.h"
#include <stdexcept>
#include "Amf3.h"
#include "AMFFormat.h"
/**
* AMF3 Reference document https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf
* AMF3 Deserializer written by EmosewaMC
*/
AMFBaseValue* AMFDeserialize::Read(RakNet::BitStream* inStream) {
AMFValue* AMFDeserialize::Read(RakNet::BitStream* inStream) {
if (!inStream) return nullptr;
AMFBaseValue* returnValue = nullptr;
AMFValue* returnValue = nullptr;
// Read in the value type from the bitStream
eAmf marker;
int8_t marker;
inStream->Read(marker);
// Based on the typing, create the value associated with that and return the base value class
switch (marker) {
case eAmf::Undefined: {
returnValue = new AMFBaseValue();
case AMFValueType::AMFUndefined: {
returnValue = new AMFUndefinedValue();
break;
}
case eAmf::Null: {
case AMFValueType::AMFNull: {
returnValue = new AMFNullValue();
break;
}
case eAmf::False: {
returnValue = new AMFBoolValue(false);
case AMFValueType::AMFFalse: {
returnValue = new AMFFalseValue();
break;
}
case eAmf::True: {
returnValue = new AMFBoolValue(true);
case AMFValueType::AMFTrue: {
returnValue = new AMFTrueValue();
break;
}
case eAmf::Integer: {
case AMFValueType::AMFInteger: {
returnValue = ReadAmfInteger(inStream);
break;
}
case eAmf::Double: {
case AMFValueType::AMFDouble: {
returnValue = ReadAmfDouble(inStream);
break;
}
case eAmf::String: {
case AMFValueType::AMFString: {
returnValue = ReadAmfString(inStream);
break;
}
case eAmf::Array: {
case AMFValueType::AMFArray: {
returnValue = ReadAmfArray(inStream);
break;
}
// These values are unimplemented in the live client and will remain unimplemented
// unless someone modifies the client to allow serializing of these values.
case eAmf::XMLDoc:
case eAmf::Date:
case eAmf::Object:
case eAmf::XML:
case eAmf::ByteArray:
case eAmf::VectorInt:
case eAmf::VectorUInt:
case eAmf::VectorDouble:
case eAmf::VectorObject:
case eAmf::Dictionary: {
throw marker;
// TODO We do not need these values, but if someone wants to implement them
// then please do so and add the corresponding unit tests.
case AMFValueType::AMFXMLDoc:
case AMFValueType::AMFDate:
case AMFValueType::AMFObject:
case AMFValueType::AMFXML:
case AMFValueType::AMFByteArray:
case AMFValueType::AMFVectorInt:
case AMFValueType::AMFVectorUInt:
case AMFValueType::AMFVectorDouble:
case AMFValueType::AMFVectorObject:
case AMFValueType::AMFDictionary: {
throw static_cast<AMFValueType>(marker);
break;
}
default:
throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast<int32_t>(marker)));
throw static_cast<AMFValueType>(marker);
break;
}
return returnValue;
@@ -101,7 +99,7 @@ uint32_t AMFDeserialize::ReadU29(RakNet::BitStream* inStream) {
return actualNumber;
}
const std::string AMFDeserialize::ReadString(RakNet::BitStream* inStream) {
std::string AMFDeserialize::ReadString(RakNet::BitStream* inStream) {
auto length = ReadU29(inStream);
// Check if this is a reference
bool isReference = length % 2 == 1;
@@ -115,39 +113,48 @@ const std::string AMFDeserialize::ReadString(RakNet::BitStream* inStream) {
return value;
} else {
// Length is a reference to a previous index - use that as the read in value
return accessedElements.at(length);
return accessedElements[length];
}
}
AMFBaseValue* AMFDeserialize::ReadAmfDouble(RakNet::BitStream* inStream) {
AMFValue* AMFDeserialize::ReadAmfDouble(RakNet::BitStream* inStream) {
auto doubleValue = new AMFDoubleValue();
double value;
inStream->Read<double>(value);
return new AMFDoubleValue(value);
doubleValue->SetDoubleValue(value);
return doubleValue;
}
AMFBaseValue* AMFDeserialize::ReadAmfArray(RakNet::BitStream* inStream) {
AMFValue* AMFDeserialize::ReadAmfArray(RakNet::BitStream* inStream) {
auto arrayValue = new AMFArrayValue();
// Read size of dense array
auto sizeOfDenseArray = (ReadU29(inStream) >> 1);
// Then read associative portion
// Then read Key'd portion
while (true) {
auto key = ReadString(inStream);
// No more associative values when we encounter an empty string key
// No more values when we encounter an empty string
if (key.size() == 0) break;
arrayValue->Insert(key, Read(inStream));
arrayValue->InsertValue(key, Read(inStream));
}
// Finally read dense portion
for (uint32_t i = 0; i < sizeOfDenseArray; i++) {
arrayValue->Insert(i, Read(inStream));
arrayValue->PushBackValue(Read(inStream));
}
return arrayValue;
}
AMFBaseValue* AMFDeserialize::ReadAmfString(RakNet::BitStream* inStream) {
return new AMFStringValue(ReadString(inStream));
AMFValue* AMFDeserialize::ReadAmfString(RakNet::BitStream* inStream) {
auto stringValue = new AMFStringValue();
stringValue->SetStringValue(ReadString(inStream));
return stringValue;
}
AMFBaseValue* AMFDeserialize::ReadAmfInteger(RakNet::BitStream* inStream) {
return new AMFIntValue(ReadU29(inStream));
AMFValue* AMFDeserialize::ReadAmfInteger(RakNet::BitStream* inStream) {
auto integerValue = new AMFIntegerValue();
integerValue->SetIntegerValue(ReadU29(inStream));
return integerValue;
}

View File

@@ -5,8 +5,7 @@
#include <vector>
#include <string>
class AMFBaseValue;
class AMFValue;
class AMFDeserialize {
public:
/**
@@ -15,7 +14,7 @@ public:
* @param inStream inStream to read value from.
* @return Returns an AMFValue with all the information from the bitStream in it.
*/
AMFBaseValue* Read(RakNet::BitStream* inStream);
AMFValue* Read(RakNet::BitStream* inStream);
private:
/**
* @brief Private method to read a U29 integer from a bitstream
@@ -31,7 +30,7 @@ private:
* @param inStream bitStream to read data from
* @return The read string
*/
const std::string ReadString(RakNet::BitStream* inStream);
std::string ReadString(RakNet::BitStream* inStream);
/**
* @brief Read an AMFDouble value from a bitStream
@@ -39,7 +38,7 @@ private:
* @param inStream bitStream to read data from
* @return Double value represented as an AMFValue
*/
AMFBaseValue* ReadAmfDouble(RakNet::BitStream* inStream);
AMFValue* ReadAmfDouble(RakNet::BitStream* inStream);
/**
* @brief Read an AMFArray from a bitStream
@@ -47,7 +46,7 @@ private:
* @param inStream bitStream to read data from
* @return Array value represented as an AMFValue
*/
AMFBaseValue* ReadAmfArray(RakNet::BitStream* inStream);
AMFValue* ReadAmfArray(RakNet::BitStream* inStream);
/**
* @brief Read an AMFString from a bitStream
@@ -55,7 +54,7 @@ private:
* @param inStream bitStream to read data from
* @return String value represented as an AMFValue
*/
AMFBaseValue* ReadAmfString(RakNet::BitStream* inStream);
AMFValue* ReadAmfString(RakNet::BitStream* inStream);
/**
* @brief Read an AMFInteger from a bitStream
@@ -63,7 +62,7 @@ private:
* @param inStream bitStream to read data from
* @return Integer value represented as an AMFValue
*/
AMFBaseValue* ReadAmfInteger(RakNet::BitStream* inStream);
AMFValue* ReadAmfInteger(RakNet::BitStream* inStream);
/**
* List of strings read so far saved to be read by reference.

156
dCommon/AMFFormat.cpp Normal file
View File

@@ -0,0 +1,156 @@
#include "AMFFormat.h"
// AMFInteger
void AMFIntegerValue::SetIntegerValue(uint32_t value) {
this->value = value;
}
uint32_t AMFIntegerValue::GetIntegerValue() {
return this->value;
}
// AMFDouble
void AMFDoubleValue::SetDoubleValue(double value) {
this->value = value;
}
double AMFDoubleValue::GetDoubleValue() {
return this->value;
}
// AMFString
void AMFStringValue::SetStringValue(const std::string& value) {
this->value = value;
}
std::string AMFStringValue::GetStringValue() {
return this->value;
}
// AMFXMLDoc
void AMFXMLDocValue::SetXMLDocValue(const std::string& value) {
this->xmlData = value;
}
std::string AMFXMLDocValue::GetXMLDocValue() {
return this->xmlData;
}
// AMFDate
void AMFDateValue::SetDateValue(uint64_t value) {
this->millisecondTimestamp = value;
}
uint64_t AMFDateValue::GetDateValue() {
return this->millisecondTimestamp;
}
// AMFArray Insert Value
void AMFArrayValue::InsertValue(const std::string& key, AMFValue* value) {
this->associative.insert(std::make_pair(key, value));
}
// AMFArray Remove Value
void AMFArrayValue::RemoveValue(const std::string& key) {
_AMFArrayMap_::iterator it = this->associative.find(key);
if (it != this->associative.end()) {
this->associative.erase(it);
}
}
// AMFArray Get Associative Iterator Begin
_AMFArrayMap_::iterator AMFArrayValue::GetAssociativeIteratorValueBegin() {
return this->associative.begin();
}
// AMFArray Get Associative Iterator End
_AMFArrayMap_::iterator AMFArrayValue::GetAssociativeIteratorValueEnd() {
return this->associative.end();
}
// AMFArray Push Back Value
void AMFArrayValue::PushBackValue(AMFValue* value) {
this->dense.push_back(value);
}
// AMFArray Pop Back Value
void AMFArrayValue::PopBackValue() {
this->dense.pop_back();
}
// AMFArray Get Dense List Size
uint32_t AMFArrayValue::GetDenseValueSize() {
return (uint32_t)this->dense.size();
}
// AMFArray Get Dense Iterator Begin
_AMFArrayList_::iterator AMFArrayValue::GetDenseIteratorBegin() {
return this->dense.begin();
}
// AMFArray Get Dense Iterator End
_AMFArrayList_::iterator AMFArrayValue::GetDenseIteratorEnd() {
return this->dense.end();
}
AMFArrayValue::~AMFArrayValue() {
for (auto valueToDelete : GetDenseArray()) {
if (valueToDelete) delete valueToDelete;
}
for (auto valueToDelete : GetAssociativeMap()) {
if (valueToDelete.second) delete valueToDelete.second;
}
}
// AMFObject Constructor
AMFObjectValue::AMFObjectValue(std::vector<std::pair<std::string, AMFValueType>> traits) {
this->traits.reserve(traits.size());
std::vector<std::pair<std::string, AMFValueType>>::iterator it = traits.begin();
while (it != traits.end()) {
this->traits.insert(std::make_pair(it->first, std::make_pair(it->second, new AMFNullValue())));
it++;
}
}
// AMFObject Set Value
void AMFObjectValue::SetTraitValue(const std::string& trait, AMFValue* value) {
if (value) {
_AMFObjectTraits_::iterator it = this->traits.find(trait);
if (it != this->traits.end()) {
if (it->second.first == value->GetValueType()) {
it->second.second = value;
}
}
}
}
// AMFObject Get Value
AMFValue* AMFObjectValue::GetTraitValue(const std::string& trait) {
_AMFObjectTraits_::iterator it = this->traits.find(trait);
if (it != this->traits.end()) {
return it->second.second;
}
return nullptr;
}
// AMFObject Get Trait Iterator Begin
_AMFObjectTraits_::iterator AMFObjectValue::GetTraitsIteratorBegin() {
return this->traits.begin();
}
// AMFObject Get Trait Iterator End
_AMFObjectTraits_::iterator AMFObjectValue::GetTraitsIteratorEnd() {
return this->traits.end();
}
// AMFObject Get Trait Size
uint32_t AMFObjectValue::GetTraitArrayCount() {
return (uint32_t)this->traits.size();
}
AMFObjectValue::~AMFObjectValue() {
for (auto valueToDelete = GetTraitsIteratorBegin(); valueToDelete != GetTraitsIteratorEnd(); valueToDelete++) {
if (valueToDelete->second.second) delete valueToDelete->second.second;
}
}

413
dCommon/AMFFormat.h Normal file
View File

@@ -0,0 +1,413 @@
#pragma once
// Custom Classes
#include "dCommonVars.h"
// C++
#include <unordered_map>
#include <vector>
/*!
\file AMFFormat.hpp
\brief A class for managing AMF values
*/
class AMFValue; // Forward declaration
// Definitions
#define _AMFArrayMap_ std::unordered_map<std::string, AMFValue*>
#define _AMFArrayList_ std::vector<AMFValue*>
#define _AMFObjectTraits_ std::unordered_map<std::string, std::pair<AMFValueType, AMFValue*>>
#define _AMFObjectDynamicTraits_ std::unordered_map<std::string, AMFValue*>
//! An enum for each AMF value type
enum AMFValueType : unsigned char {
AMFUndefined = 0x00, //!< An undefined AMF Value
AMFNull = 0x01, //!< A null AMF value
AMFFalse = 0x02, //!< A false AMF value
AMFTrue = 0x03, //!< A true AMF value
AMFInteger = 0x04, //!< An integer AMF value
AMFDouble = 0x05, //!< A double AMF value
AMFString = 0x06, //!< A string AMF value
AMFXMLDoc = 0x07, //!< An XML Doc AMF value
AMFDate = 0x08, //!< A date AMF value
AMFArray = 0x09, //!< An array AMF value
AMFObject = 0x0A, //!< An object AMF value
AMFXML = 0x0B, //!< An XML AMF value
AMFByteArray = 0x0C, //!< A byte array AMF value
AMFVectorInt = 0x0D, //!< An integer vector AMF value
AMFVectorUInt = 0x0E, //!< An unsigned integer AMF value
AMFVectorDouble = 0x0F, //!< A double vector AMF value
AMFVectorObject = 0x10, //!< An object vector AMF value
AMFDictionary = 0x11 //!< A dictionary AMF value
};
//! An enum for the object value types
enum AMFObjectValueType : unsigned char {
AMFObjectAnonymous = 0x01,
AMFObjectTyped = 0x02,
AMFObjectDynamic = 0x03,
AMFObjectExternalizable = 0x04
};
//! The base AMF value class
class AMFValue {
public:
//! Returns the AMF value type
/*!
\return The AMF value type
*/
virtual AMFValueType GetValueType() = 0;
virtual ~AMFValue() {};
};
//! A typedef for a pointer to an AMF value
typedef AMFValue* NDGFxValue;
// The various AMF value types
//! The undefined value AMF type
class AMFUndefinedValue : public AMFValue {
private:
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFUndefined;
};
//! The null value AMF type
class AMFNullValue : public AMFValue {
private:
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFNull;
};
//! The false value AMF type
class AMFFalseValue : public AMFValue {
private:
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFFalse;
};
//! The true value AMF type
class AMFTrueValue : public AMFValue {
private:
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFTrue;
};
//! The integer value AMF type
class AMFIntegerValue : public AMFValue {
private:
uint32_t value; //!< The value of the AMF type
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFInteger;
//! Sets the integer value
/*!
\param value The value to set
*/
void SetIntegerValue(uint32_t value);
//! Gets the integer value
/*!
\return The integer value
*/
uint32_t GetIntegerValue();
};
//! The double value AMF type
class AMFDoubleValue : public AMFValue {
private:
double value; //!< The value of the AMF type
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFDouble;
//! Sets the double value
/*!
\param value The value to set to
*/
void SetDoubleValue(double value);
//! Gets the double value
/*!
\return The double value
*/
double GetDoubleValue();
};
//! The string value AMF type
class AMFStringValue : public AMFValue {
private:
std::string value; //!< The value of the AMF type
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFString;
//! Sets the string value
/*!
\param value The string value to set to
*/
void SetStringValue(const std::string& value);
//! Gets the string value
/*!
\return The string value
*/
std::string GetStringValue();
};
//! The XML doc value AMF type
class AMFXMLDocValue : public AMFValue {
private:
std::string xmlData; //!< The value of the AMF type
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFXMLDoc;
//! Sets the XML Doc value
/*!
\param value The value to set to
*/
void SetXMLDocValue(const std::string& value);
//! Gets the XML Doc value
/*!
\return The XML Doc value
*/
std::string GetXMLDocValue();
};
//! The date value AMF type
class AMFDateValue : public AMFValue {
private:
uint64_t millisecondTimestamp; //!< The time in milliseconds since the ephoch
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() { return ValueType; }
public:
static const AMFValueType ValueType = AMFDate;
//! Sets the date time
/*!
\param value The value to set to
*/
void SetDateValue(uint64_t value);
//! Gets the date value
/*!
\return The date value in milliseconds since the epoch
*/
uint64_t GetDateValue();
};
//! The array value AMF type
// This object will manage it's own memory map and list. Do not delete its values.
class AMFArrayValue : public AMFValue {
private:
_AMFArrayMap_ associative; //!< The array map (associative part)
_AMFArrayList_ dense; //!< The array list (dense part)
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() override { return ValueType; }
public:
static const AMFValueType ValueType = AMFArray;
~AMFArrayValue() override;
//! Inserts an item into the array map for a specific key
/*!
\param key The key to set
\param value The value to add
*/
void InsertValue(const std::string& key, AMFValue* value);
//! Removes an item for a specific key
/*!
\param key The key to remove
*/
void RemoveValue(const std::string& key);
//! Finds an AMF value
/*!
\return The AMF value if found, nullptr otherwise
*/
template <typename T>
T* FindValue(const std::string& key) const {
_AMFArrayMap_::const_iterator it = this->associative.find(key);
if (it != this->associative.end() && T::ValueType == it->second->GetValueType()) {
return dynamic_cast<T*>(it->second);
}
return nullptr;
};
//! Returns where the associative iterator begins
/*!
\return Where the array map iterator begins
*/
_AMFArrayMap_::iterator GetAssociativeIteratorValueBegin();
//! Returns where the associative iterator ends
/*!
\return Where the array map iterator ends
*/
_AMFArrayMap_::iterator GetAssociativeIteratorValueEnd();
//! Pushes back a value into the array list
/*!
\param value The value to push back
*/
void PushBackValue(AMFValue* value);
//! Pops back the last value in the array list
void PopBackValue();
//! Gets the count of the dense list
/*!
\return The dense list size
*/
uint32_t GetDenseValueSize();
//! Gets a specific value from the list for the specified index
/*!
\param index The index to get
*/
template <typename T>
T* GetValueAt(uint32_t index) {
if (index >= this->dense.size()) return nullptr;
AMFValue* foundValue = this->dense.at(index);
return T::ValueType == foundValue->GetValueType() ? dynamic_cast<T*>(foundValue) : nullptr;
};
//! Returns where the dense iterator begins
/*!
\return Where the iterator begins
*/
_AMFArrayList_::iterator GetDenseIteratorBegin();
//! Returns where the dense iterator ends
/*!
\return Where the iterator ends
*/
_AMFArrayList_::iterator GetDenseIteratorEnd();
//! Returns the associative map
/*!
\return The associative map
*/
_AMFArrayMap_ GetAssociativeMap() { return this->associative; };
//! Returns the dense array
/*!
\return The dense array
*/
_AMFArrayList_ GetDenseArray() { return this->dense; };
};
//! The anonymous object value AMF type
class AMFObjectValue : public AMFValue {
private:
_AMFObjectTraits_ traits; //!< The object traits
//! Returns the AMF value type
/*!
\return The AMF value type
*/
AMFValueType GetValueType() override { return ValueType; }
~AMFObjectValue() override;
public:
static const AMFValueType ValueType = AMFObject;
//! Constructor
/*!
\param traits The traits to set
*/
AMFObjectValue(std::vector<std::pair<std::string, AMFValueType>> traits);
//! Gets the object value type
/*!
\return The object value type
*/
virtual AMFObjectValueType GetObjectValueType() { return AMFObjectAnonymous; }
//! Sets the value of a trait
/*!
\param trait The trait to set the value for
\param value The AMF value to set
*/
void SetTraitValue(const std::string& trait, AMFValue* value);
//! Gets a trait value
/*!
\param trait The trait to get the value for
\return The trait value
*/
AMFValue* GetTraitValue(const std::string& trait);
//! Gets the beginning of the object traits iterator
/*!
\return The AMF trait array iterator begin
*/
_AMFObjectTraits_::iterator GetTraitsIteratorBegin();
//! Gets the end of the object traits iterator
/*!
\return The AMF trait array iterator begin
*/
_AMFObjectTraits_::iterator GetTraitsIteratorEnd();
//! Gets the amount of traits
/*!
\return The amount of traits
*/
uint32_t GetTraitArrayCount();
};

View File

@@ -0,0 +1,259 @@
#include "AMFFormat_BitStream.h"
// Writes an AMFValue pointer to a RakNet::BitStream
template<>
void RakNet::BitStream::Write<AMFValue*>(AMFValue* value) {
if (value != nullptr) {
AMFValueType type = value->GetValueType();
switch (type) {
case AMFUndefined: {
AMFUndefinedValue* v = (AMFUndefinedValue*)value;
this->Write(*v);
break;
}
case AMFNull: {
AMFNullValue* v = (AMFNullValue*)value;
this->Write(*v);
break;
}
case AMFFalse: {
AMFFalseValue* v = (AMFFalseValue*)value;
this->Write(*v);
break;
}
case AMFTrue: {
AMFTrueValue* v = (AMFTrueValue*)value;
this->Write(*v);
break;
}
case AMFInteger: {
AMFIntegerValue* v = (AMFIntegerValue*)value;
this->Write(*v);
break;
}
case AMFDouble: {
AMFDoubleValue* v = (AMFDoubleValue*)value;
this->Write(*v);
break;
}
case AMFString: {
AMFStringValue* v = (AMFStringValue*)value;
this->Write(*v);
break;
}
case AMFXMLDoc: {
AMFXMLDocValue* v = (AMFXMLDocValue*)value;
this->Write(*v);
break;
}
case AMFDate: {
AMFDateValue* v = (AMFDateValue*)value;
this->Write(*v);
break;
}
case AMFArray: {
this->Write((AMFArrayValue*)value);
break;
}
case AMFObject:
case AMFXML:
case AMFByteArray:
case AMFVectorInt:
case AMFVectorUInt:
case AMFVectorDouble:
case AMFVectorObject:
case AMFDictionary:
break;
}
}
}
/**
* A private function to write an value to a RakNet::BitStream
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteUInt29(RakNet::BitStream* bs, uint32_t v) {
unsigned char b4 = (unsigned char)v;
if (v < 0x00200000) {
b4 = b4 & 0x7F;
if (v > 0x7F) {
unsigned char b3;
v = v >> 7;
b3 = ((unsigned char)(v)) | 0x80;
if (v > 0x7F) {
unsigned char b2;
v = v >> 7;
b2 = ((unsigned char)(v)) | 0x80;
bs->Write(b2);
}
bs->Write(b3);
}
} else {
unsigned char b1;
unsigned char b2;
unsigned char b3;
v = v >> 8;
b3 = ((unsigned char)(v)) | 0x80;
v = v >> 7;
b2 = ((unsigned char)(v)) | 0x80;
v = v >> 7;
b1 = ((unsigned char)(v)) | 0x80;
bs->Write(b1);
bs->Write(b2);
bs->Write(b3);
}
bs->Write(b4);
}
/**
* Writes a flag number to a RakNet::BitStream
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteFlagNumber(RakNet::BitStream* bs, uint32_t v) {
v = (v << 1) | 0x01;
WriteUInt29(bs, v);
}
/**
* Writes an AMFString to a RakNet::BitStream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFString(RakNet::BitStream* bs, const std::string& str) {
WriteFlagNumber(bs, (uint32_t)str.size());
bs->Write(str.c_str(), (uint32_t)str.size());
}
/**
* Writes an U16 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU16(RakNet::BitStream* bs, uint16_t value) {
bs->Write(value);
}
/**
* Writes an U32 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU32(RakNet::BitStream* bs, uint32_t value) {
bs->Write(value);
}
/**
* Writes an U64 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU64(RakNet::BitStream* bs, uint64_t value) {
bs->Write(value);
}
// Writes an AMFUndefinedValue to BitStream
template<>
void RakNet::BitStream::Write<AMFUndefinedValue>(AMFUndefinedValue value) {
this->Write(AMFUndefined);
}
// Writes an AMFNullValue to BitStream
template<>
void RakNet::BitStream::Write<AMFNullValue>(AMFNullValue value) {
this->Write(AMFNull);
}
// Writes an AMFFalseValue to BitStream
template<>
void RakNet::BitStream::Write<AMFFalseValue>(AMFFalseValue value) {
this->Write(AMFFalse);
}
// Writes an AMFTrueValue to BitStream
template<>
void RakNet::BitStream::Write<AMFTrueValue>(AMFTrueValue value) {
this->Write(AMFTrue);
}
// Writes an AMFIntegerValue to BitStream
template<>
void RakNet::BitStream::Write<AMFIntegerValue>(AMFIntegerValue value) {
this->Write(AMFInteger);
WriteUInt29(this, value.GetIntegerValue());
}
// Writes an AMFDoubleValue to BitStream
template<>
void RakNet::BitStream::Write<AMFDoubleValue>(AMFDoubleValue value) {
this->Write(AMFDouble);
double d = value.GetDoubleValue();
WriteAMFU64(this, *((unsigned long long*) & d));
}
// Writes an AMFStringValue to BitStream
template<>
void RakNet::BitStream::Write<AMFStringValue>(AMFStringValue value) {
this->Write(AMFString);
std::string v = value.GetStringValue();
WriteAMFString(this, v);
}
// Writes an AMFXMLDocValue to BitStream
template<>
void RakNet::BitStream::Write<AMFXMLDocValue>(AMFXMLDocValue value) {
this->Write(AMFXMLDoc);
std::string v = value.GetXMLDocValue();
WriteAMFString(this, v);
}
// Writes an AMFDateValue to BitStream
template<>
void RakNet::BitStream::Write<AMFDateValue>(AMFDateValue value) {
this->Write(AMFDate);
uint64_t date = value.GetDateValue();
WriteAMFU64(this, date);
}
// Writes an AMFArrayValue to BitStream
template<>
void RakNet::BitStream::Write<AMFArrayValue*>(AMFArrayValue* value) {
this->Write(AMFArray);
uint32_t denseSize = value->GetDenseValueSize();
WriteFlagNumber(this, denseSize);
_AMFArrayMap_::iterator it = value->GetAssociativeIteratorValueBegin();
_AMFArrayMap_::iterator end = value->GetAssociativeIteratorValueEnd();
while (it != end) {
WriteAMFString(this, it->first);
this->Write(it->second);
it++;
}
this->Write(AMFNull);
if (denseSize > 0) {
_AMFArrayList_::iterator it2 = value->GetDenseIteratorBegin();
_AMFArrayList_::iterator end2 = value->GetDenseIteratorEnd();
while (it2 != end2) {
this->Write(*it2);
it2++;
}
}
}

View File

@@ -0,0 +1,92 @@
#pragma once
// Custom Classes
#include "AMFFormat.h"
// RakNet
#include <BitStream.h>
/*!
\file AMFFormat_BitStream.h
\brief A class that implements native writing of AMF values to RakNet::BitStream
*/
// We are using the RakNet namespace
namespace RakNet {
//! Writes an AMFValue pointer to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFValue*>(AMFValue* value);
//! Writes an AMFUndefinedValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFUndefinedValue>(AMFUndefinedValue value);
//! Writes an AMFNullValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFNullValue>(AMFNullValue value);
//! Writes an AMFFalseValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFFalseValue>(AMFFalseValue value);
//! Writes an AMFTrueValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFTrueValue>(AMFTrueValue value);
//! Writes an AMFIntegerValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFIntegerValue>(AMFIntegerValue value);
//! Writes an AMFDoubleValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFDoubleValue>(AMFDoubleValue value);
//! Writes an AMFStringValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFStringValue>(AMFStringValue value);
//! Writes an AMFXMLDocValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFXMLDocValue>(AMFXMLDocValue value);
//! Writes an AMFDateValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFDateValue>(AMFDateValue value);
//! Writes an AMFArrayValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFArrayValue*>(AMFArrayValue* value);
} // namespace RakNet

View File

@@ -1,367 +0,0 @@
#ifndef __AMF3__H__
#define __AMF3__H__
#include "dCommonVars.h"
#include "dLogger.h"
#include "Game.h"
#include <unordered_map>
#include <vector>
enum class eAmf : uint8_t {
Undefined = 0x00, // An undefined AMF Value
Null = 0x01, // A null AMF value
False = 0x02, // A false AMF value
True = 0x03, // A true AMF value
Integer = 0x04, // An integer AMF value
Double = 0x05, // A double AMF value
String = 0x06, // A string AMF value
XMLDoc = 0x07, // Unused in the live client and cannot be serialized without modification. An XML Doc AMF value
Date = 0x08, // Unused in the live client and cannot be serialized without modification. A date AMF value
Array = 0x09, // An array AMF value
Object = 0x0A, // Unused in the live client and cannot be serialized without modification. An object AMF value
XML = 0x0B, // Unused in the live client and cannot be serialized without modification. An XML AMF value
ByteArray = 0x0C, // Unused in the live client and cannot be serialized without modification. A byte array AMF value
VectorInt = 0x0D, // Unused in the live client and cannot be serialized without modification. An integer vector AMF value
VectorUInt = 0x0E, // Unused in the live client and cannot be serialized without modification. An unsigned integer AMF value
VectorDouble = 0x0F, // Unused in the live client and cannot be serialized without modification. A double vector AMF value
VectorObject = 0x10, // Unused in the live client and cannot be serialized without modification. An object vector AMF value
Dictionary = 0x11 // Unused in the live client and cannot be serialized without modification. A dictionary AMF value
};
class AMFBaseValue {
public:
virtual eAmf GetValueType() { return eAmf::Undefined; };
AMFBaseValue() {};
virtual ~AMFBaseValue() {};
};
template<typename ValueType>
class AMFValue : public AMFBaseValue {
public:
AMFValue() {};
AMFValue(ValueType value) { SetValue(value); };
virtual ~AMFValue() override {};
eAmf GetValueType() override { return eAmf::Undefined; };
const ValueType& GetValue() { return data; };
void SetValue(ValueType value) { data = value; };
protected:
ValueType data;
};
// As a string this is much easier to write and read from a BitStream.
template<>
class AMFValue<const char*> : public AMFBaseValue {
public:
AMFValue() {};
AMFValue(const char* value) { SetValue(std::string(value)); };
virtual ~AMFValue() override {};
eAmf GetValueType() override { return eAmf::String; };
const std::string& GetValue() { return data; };
void SetValue(std::string value) { data = value; };
protected:
std::string data;
};
typedef AMFValue<std::nullptr_t> AMFNullValue;
typedef AMFValue<bool> AMFBoolValue;
typedef AMFValue<int32_t> AMFIntValue;
typedef AMFValue<std::string> AMFStringValue;
typedef AMFValue<double> AMFDoubleValue;
template<> inline eAmf AMFValue<std::nullptr_t>::GetValueType() { return eAmf::Null; };
template<> inline eAmf AMFValue<bool>::GetValueType() { return this->data ? eAmf::True : eAmf::False; };
template<> inline eAmf AMFValue<int32_t>::GetValueType() { return eAmf::Integer; };
template<> inline eAmf AMFValue<uint32_t>::GetValueType() { return eAmf::Integer; };
template<> inline eAmf AMFValue<std::string>::GetValueType() { return eAmf::String; };
template<> inline eAmf AMFValue<double>::GetValueType() { return eAmf::Double; };
/**
* The AMFArrayValue object holds 2 types of lists:
* An associative list where a key maps to a value
* A Dense list where elements are stored back to back
*
* Objects that are Registered are owned by this object
* and are not to be deleted by a caller.
*/
class AMFArrayValue : public AMFBaseValue {
typedef std::unordered_map<std::string, AMFBaseValue*> AMFAssociative;
typedef std::vector<AMFBaseValue*> AMFDense;
public:
eAmf GetValueType() override { return eAmf::Array; };
~AMFArrayValue() override {
for (auto valueToDelete : GetDense()) {
if (valueToDelete) {
delete valueToDelete;
valueToDelete = nullptr;
}
}
for (auto valueToDelete : GetAssociative()) {
if (valueToDelete.second) {
delete valueToDelete.second;
valueToDelete.second = nullptr;
}
}
};
/**
* Returns the Associative portion of the object
*/
inline AMFAssociative& GetAssociative() { return this->associative; };
/**
* Returns the dense portion of the object
*/
inline AMFDense& GetDense() { return this->dense; };
/**
* Inserts an AMFValue into the associative portion with the given key.
* If a duplicate is attempted to be inserted, it is ignored and the
* first value with that key is kept in the map.
*
* These objects are not to be deleted by the caller as they are owned by
* the AMFArray object which manages its own memory.
*
* @param key The key to associate with the value
* @param value The value to insert
*
* @return The inserted element if the type matched,
* or nullptr if a key existed and was not the same type
*/
template<typename ValueType>
std::pair<AMFValue<ValueType>*, bool> Insert(const std::string& key, ValueType value) {
auto element = associative.find(key);
AMFValue<ValueType>* val = nullptr;
bool found = true;
if (element == associative.end()) {
val = new AMFValue<ValueType>(value);
associative.insert(std::make_pair(key, val));
} else {
val = dynamic_cast<AMFValue<ValueType>*>(element->second);
found = false;
}
return std::make_pair(val, found);
};
// Associates an array with a string key
std::pair<AMFBaseValue*, bool> Insert(const std::string& key) {
auto element = associative.find(key);
AMFArrayValue* val = nullptr;
bool found = true;
if (element == associative.end()) {
val = new AMFArrayValue();
associative.insert(std::make_pair(key, val));
} else {
val = dynamic_cast<AMFArrayValue*>(element->second);
found = false;
}
return std::make_pair(val, found);
};
// Associates an array with an integer key
std::pair<AMFBaseValue*, bool> Insert(const uint32_t& index) {
AMFArrayValue* val = nullptr;
bool inserted = false;
if (index >= dense.size()) {
dense.resize(index + 1);
val = new AMFArrayValue();
dense.at(index) = val;
inserted = true;
}
return std::make_pair(dynamic_cast<AMFArrayValue*>(dense.at(index)), inserted);
};
/**
* @brief Inserts an AMFValue into the AMFArray key'd by index.
* Attempting to insert the same key to the same value twice overwrites
* the previous value with the new one.
*
* @param index The index to associate with the value
* @param value The value to insert
* @return The inserted element, or nullptr if the type did not match
* what was at the index.
*/
template<typename ValueType>
std::pair<AMFValue<ValueType>*, bool> Insert(const uint32_t& index, ValueType value) {
AMFValue<ValueType>* val = nullptr;
bool inserted = false;
if (index >= this->dense.size()) {
this->dense.resize(index + 1);
val = new AMFValue<ValueType>(value);
this->dense.at(index) = val;
inserted = true;
}
return std::make_pair(dynamic_cast<AMFValue<ValueType>*>(this->dense.at(index)), inserted);
};
/**
* Inserts an AMFValue into the associative portion with the given key.
* If a duplicate is attempted to be inserted, it replaces the original
*
* The inserted element is now owned by this object and is not to be deleted
*
* @param key The key to associate with the value
* @param value The value to insert
*/
void Insert(const std::string& key, AMFBaseValue* value) {
auto element = associative.find(key);
if (element != associative.end() && element->second) {
delete element->second;
element->second = value;
} else {
associative.insert(std::make_pair(key, value));
}
};
/**
* Inserts an AMFValue into the associative portion with the given index.
* If a duplicate is attempted to be inserted, it replaces the original
*
* The inserted element is now owned by this object and is not to be deleted
*
* @param key The key to associate with the value
* @param value The value to insert
*/
void Insert(const uint32_t index, AMFBaseValue* value) {
if (index < dense.size()) {
AMFDense::iterator itr = dense.begin() + index;
if (*itr) delete dense.at(index);
} else {
dense.resize(index + 1);
}
dense.at(index) = value;
};
/**
* Pushes an AMFValue into the back of the dense portion.
*
* These objects are not to be deleted by the caller as they are owned by
* the AMFArray object which manages its own memory.
*
* @param value The value to insert
*
* @return The inserted pointer, or nullptr should the key already be in use.
*/
template<typename ValueType>
inline AMFValue<ValueType>* Push(ValueType value) {
return Insert(this->dense.size(), value).first;
};
/**
* Removes the key from the associative portion
*
* The pointer removed is now no longer managed by this container
*
* @param key The key to remove from the associative portion
*/
void Remove(const std::string& key, bool deleteValue = true) {
AMFAssociative::iterator it = this->associative.find(key);
if (it != this->associative.end()) {
if (deleteValue) delete it->second;
this->associative.erase(it);
}
}
/**
* Pops the last element in the dense portion, deleting it in the process.
*/
void Remove(const uint32_t index) {
if (!this->dense.empty() && index < this->dense.size()) {
auto itr = this->dense.begin() + index;
if (*itr) delete (*itr);
this->dense.erase(itr);
}
}
void Pop() {
if (!this->dense.empty()) Remove(this->dense.size() - 1);
}
AMFArrayValue* GetArray(const std::string& key) {
AMFAssociative::const_iterator it = this->associative.find(key);
if (it != this->associative.end()) {
return dynamic_cast<AMFArrayValue*>(it->second);
}
return nullptr;
};
AMFArrayValue* GetArray(const uint32_t index) {
return index >= this->dense.size() ? nullptr : dynamic_cast<AMFArrayValue*>(this->dense.at(index));
};
inline AMFArrayValue* InsertArray(const std::string& key) {
return static_cast<AMFArrayValue*>(Insert(key).first);
};
inline AMFArrayValue* InsertArray(const uint32_t index) {
return static_cast<AMFArrayValue*>(Insert(index).first);
};
inline AMFArrayValue* PushArray() {
return static_cast<AMFArrayValue*>(Insert(this->dense.size()).first);
};
/**
* Gets an AMFValue by the key from the associative portion and converts it
* to the AmfValue template type. If the key did not exist, it is inserted.
*
* @tparam The target object type
* @param key The key to lookup
*
* @return The AMFValue
*/
template <typename AmfType>
AMFValue<AmfType>* Get(const std::string& key) const {
AMFAssociative::const_iterator it = this->associative.find(key);
return it != this->associative.end() ?
dynamic_cast<AMFValue<AmfType>*>(it->second) :
nullptr;
};
// Get from the array but dont cast it
AMFBaseValue* Get(const std::string& key) const {
AMFAssociative::const_iterator it = this->associative.find(key);
return it != this->associative.end() ? it->second : nullptr;
};
/**
* @brief Get an AMFValue object at a position in the dense portion.
* Gets an AMFValue by the index from the dense portion and converts it
* to the AmfValue template type. If the index did not exist, it is inserted.
*
* @tparam The target object type
* @param index The index to get
* @return The casted object, or nullptr.
*/
template <typename AmfType>
AMFValue<AmfType>* Get(uint32_t index) const {
return index < this->dense.size() ?
dynamic_cast<AMFValue<AmfType>*>(this->dense.at(index)) :
nullptr;
};
// Get from the dense but dont cast it
AMFBaseValue* Get(const uint32_t index) const {
return index < this->dense.size() ? this->dense.at(index) : nullptr;
};
private:
/**
* The associative portion. These values are key'd with strings to an AMFValue.
*/
AMFAssociative associative;
/**
* The dense portion. These AMFValue's are stored one after
* another with the most recent addition being at the back.
*/
AMFDense dense;
};
#endif //!__AMF3__H__

View File

@@ -1,184 +0,0 @@
#include "AmfSerialize.h"
#include "Game.h"
#include "dLogger.h"
// Writes an AMFValue pointer to a RakNet::BitStream
template<>
void RakNet::BitStream::Write<AMFBaseValue&>(AMFBaseValue& value) {
eAmf type = value.GetValueType();
this->Write(type);
switch (type) {
case eAmf::Integer: {
this->Write<AMFIntValue&>(*static_cast<AMFIntValue*>(&value));
break;
}
case eAmf::Double: {
this->Write<AMFDoubleValue&>(*static_cast<AMFDoubleValue*>(&value));
break;
}
case eAmf::String: {
this->Write<AMFStringValue&>(*static_cast<AMFStringValue*>(&value));
break;
}
case eAmf::Array: {
this->Write<AMFArrayValue&>(*static_cast<AMFArrayValue*>(&value));
break;
}
default: {
Game::logger->Log("AmfSerialize", "Encountered unwritable AMFType %i!", type);
}
case eAmf::Undefined:
case eAmf::Null:
case eAmf::False:
case eAmf::True:
case eAmf::Date:
case eAmf::Object:
case eAmf::XML:
case eAmf::XMLDoc:
case eAmf::ByteArray:
case eAmf::VectorInt:
case eAmf::VectorUInt:
case eAmf::VectorDouble:
case eAmf::VectorObject:
case eAmf::Dictionary:
break;
}
}
/**
* A private function to write an value to a RakNet::BitStream
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteUInt29(RakNet::BitStream* bs, uint32_t v) {
unsigned char b4 = (unsigned char)v;
if (v < 0x00200000) {
b4 = b4 & 0x7F;
if (v > 0x7F) {
unsigned char b3;
v = v >> 7;
b3 = ((unsigned char)(v)) | 0x80;
if (v > 0x7F) {
unsigned char b2;
v = v >> 7;
b2 = ((unsigned char)(v)) | 0x80;
bs->Write(b2);
}
bs->Write(b3);
}
} else {
unsigned char b1;
unsigned char b2;
unsigned char b3;
v = v >> 8;
b3 = ((unsigned char)(v)) | 0x80;
v = v >> 7;
b2 = ((unsigned char)(v)) | 0x80;
v = v >> 7;
b1 = ((unsigned char)(v)) | 0x80;
bs->Write(b1);
bs->Write(b2);
bs->Write(b3);
}
bs->Write(b4);
}
/**
* Writes a flag number to a RakNet::BitStream
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteFlagNumber(RakNet::BitStream* bs, uint32_t v) {
v = (v << 1) | 0x01;
WriteUInt29(bs, v);
}
/**
* Writes an AMFString to a RakNet::BitStream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFString(RakNet::BitStream* bs, const std::string& str) {
WriteFlagNumber(bs, (uint32_t)str.size());
bs->Write(str.c_str(), (uint32_t)str.size());
}
/**
* Writes an U16 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU16(RakNet::BitStream* bs, uint16_t value) {
bs->Write(value);
}
/**
* Writes an U32 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU32(RakNet::BitStream* bs, uint32_t value) {
bs->Write(value);
}
/**
* Writes an U64 to a bitstream
*
* RakNet writes in the correct byte order - do not reverse this.
*/
void WriteAMFU64(RakNet::BitStream* bs, uint64_t value) {
bs->Write(value);
}
// Writes an AMFIntegerValue to BitStream
template<>
void RakNet::BitStream::Write<AMFIntValue&>(AMFIntValue& value) {
WriteUInt29(this, value.GetValue());
}
// Writes an AMFDoubleValue to BitStream
template<>
void RakNet::BitStream::Write<AMFDoubleValue&>(AMFDoubleValue& value) {
double d = value.GetValue();
WriteAMFU64(this, *reinterpret_cast<uint64_t*>(&d));
}
// Writes an AMFStringValue to BitStream
template<>
void RakNet::BitStream::Write<AMFStringValue&>(AMFStringValue& value) {
WriteAMFString(this, value.GetValue());
}
// Writes an AMFArrayValue to BitStream
template<>
void RakNet::BitStream::Write<AMFArrayValue&>(AMFArrayValue& value) {
uint32_t denseSize = value.GetDense().size();
WriteFlagNumber(this, denseSize);
auto it = value.GetAssociative().begin();
auto end = value.GetAssociative().end();
while (it != end) {
WriteAMFString(this, it->first);
this->Write<AMFBaseValue&>(*it->second);
it++;
}
this->Write(eAmf::Null);
if (denseSize > 0) {
auto it2 = value.GetDense().begin();
auto end2 = value.GetDense().end();
while (it2 != end2) {
this->Write<AMFBaseValue&>(**it2);
it2++;
}
}
}

View File

@@ -1,50 +0,0 @@
#pragma once
// Custom Classes
#include "Amf3.h"
// RakNet
#include <BitStream.h>
/*!
\file AmfSerialize.h
\brief A class that implements native writing of AMF values to RakNet::BitStream
*/
// We are using the RakNet namespace
namespace RakNet {
//! Writes an AMFValue pointer to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFBaseValue&>(AMFBaseValue& value);
//! Writes an AMFIntegerValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFIntValue&>(AMFIntValue& value);
//! Writes an AMFDoubleValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFDoubleValue&>(AMFDoubleValue& value);
//! Writes an AMFStringValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFStringValue&>(AMFStringValue& value);
//! Writes an AMFArrayValue to a RakNet::BitStream
/*!
\param value The value to write
*/
template <>
void RakNet::BitStream::Write<AMFArrayValue&>(AMFArrayValue& value);
} // namespace RakNet

View File

@@ -1,93 +1,35 @@
#include "MySQLMigration.h"
#include "BrickByBrickFix.h"
#include <istream>
#include <memory>
#include <iostream>
#include <sstream>
#include "CDClientDatabase.h"
#include "Game.h"
#include "GeneralUtils.h"
#include "dLogger.h"
#include "BinaryPathFinder.h"
#include "ZCompression.h"
#include "tinyxml2.h"
#include "../MySQL.h"
#include "Database.h"
#include "Game.h"
#include "ZCompression.h"
#include "dLogger.h"
void MigrationRunner::RunMigrations(DatabaseBase* db) {
auto database = dynamic_cast<MySQLDatabase*>(db);
//! Forward declarations
auto* stmt = database->CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());");
stmt->execute();
delete stmt;
std::unique_ptr<sql::ResultSet> GetModelsFromDatabase();
void WriteSd0Magic(char* input, uint32_t chunkSize);
bool CheckSd0Magic(sql::Blob* streamToCheck);
sql::SQLString finalSQL = "";
bool runSd0Migrations = false;
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/mysql").string())) {
auto migration = Migration::LoadMigration("dlu", "mysql", entry);
if (migration.data.empty()) {
continue;
}
stmt = database->CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
stmt->setString(1, migration.name.c_str());
auto* res = stmt->executeQuery();
bool doExit = res->next();
delete res;
delete stmt;
if (doExit) continue;
Game::logger->Log("MigrationRunner", "Running migration: %s", migration.name.c_str());
if (migration.name == "dlu/5_brick_model_sd0.sql") {
runSd0Migrations = true;
} else {
finalSQL.append(migration.data.c_str());
}
stmt = database->CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
stmt->setString(1, migration.name.c_str());
stmt->execute();
delete stmt;
}
if (finalSQL.empty() && !runSd0Migrations) {
Game::logger->Log("MigrationRunner", "Server database is up to date.");
return;
}
if (!finalSQL.empty()) {
auto migration = GeneralUtils::SplitString(static_cast<std::string>(finalSQL), ';');
std::unique_ptr<sql::Statement> simpleStatement(Database::CreateStmt());
for (auto& query : migration) {
try {
if (query.empty()) continue;
simpleStatement->execute(query.c_str());
} catch (sql::SQLException& e) {
Game::logger->Log("MigrationRunner", "Encountered error running migration: %s", e.what());
}
}
}
// Do this last on the off chance none of the other migrations have been run yet.
if (runSd0Migrations) {
uint32_t numberOfUpdatedModels = UpdateBrickByBrickModelsToSd0();
Game::logger->Log("MasterServer", "%i models were updated from zlib to sd0.", numberOfUpdatedModels);
uint32_t numberOfTruncatedModels = TruncateBrokenBrickByBrickXml();
Game::logger->Log("MasterServer", "%i models were truncated from the database.", numberOfTruncatedModels);
}
}
uint32_t TruncateBrokenBrickByBrickXml(MySQLDatabase* database) {
/**
* @brief Truncates all models with broken data from the database.
*
* @return The number of models deleted
*/
uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() {
uint32_t modelsTruncated{};
auto modelsToTruncate = GetModelsFromDatabase(database);
bool previousCommitValue = database->GetAutoCommit();
database->SetAutoCommit(false);
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::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);
@@ -153,8 +95,8 @@ uint32_t TruncateBrokenBrickByBrickXml(MySQLDatabase* database) {
}
}
database->Commit();
database->SetAutoCommit(previousCommitValue);
Database::Commit();
Database::SetAutoCommit(previousCommitValue);
return modelsTruncated;
}
@@ -164,14 +106,12 @@ uint32_t TruncateBrokenBrickByBrickXml(MySQLDatabase* database) {
*
* @return The number of models updated to SD0
*/
uint32_t UpdateBrickByBrickModelsToSd0(MySQLDatabase* database) {
uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() {
uint32_t updatedModels = 0;
auto modelsToUpdate = GetModelsFromDatabase();
auto previousAutoCommitState = database->GetAutoCommit();
database->SetAutoCommit(false);
std::unique_ptr<sql::PreparedStatement> insertionStatement(database->CreatePreppedStmt("UPDATE ugc SET lxfml = ? WHERE id = ?;"));
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));
@@ -210,14 +150,13 @@ uint32_t UpdateBrickByBrickModelsToSd0(MySQLDatabase* database) {
}
}
}
database->Commit();
database->SetAutoCommit(previousAutoCommitState);
Database::Commit();
Database::SetAutoCommit(previousAutoCommitState);
return updatedModels;
}
std::unique_ptr<sql::ResultSet> GetModelsFromDatabase(MySQLDatabase* database) {
std::unique_ptr<sql::PreparedStatement> modelsRawDataQuery(database->CreatePreppedStmt("SELECT id, lxfml FROM ugc;"));
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());
}

20
dCommon/BrickByBrickFix.h Normal file
View File

@@ -0,0 +1,20 @@
#pragma once
#include <cstdint>
namespace BrickByBrickFix {
/**
* @brief Deletes all broken BrickByBrick models that have invalid XML
*
* @return The number of BrickByBrick models that were truncated
*/
uint32_t TruncateBrokenBrickByBrickXml();
/**
* @brief Updates all BrickByBrick models in the database to be
* in the sd0 format as opposed to a zlib compressed format.
*
* @return The number of BrickByBrick models that were updated
*/
uint32_t UpdateBrickByBrickModelsToSd0();
};

View File

@@ -1,6 +1,6 @@
set(DCOMMON_SOURCES
set(DCOMMON_SOURCES "AMFFormat.cpp"
"AMFDeserialize.cpp"
"AmfSerialize.cpp"
"AMFFormat_BitStream.cpp"
"BinaryIO.cpp"
"dConfig.cpp"
"Diagnostics.cpp"
@@ -12,8 +12,9 @@ set(DCOMMON_SOURCES
"NiPoint3.cpp"
"NiQuaternion.cpp"
"SHA512.cpp"
"Demangler.cpp"
"Type.cpp"
"ZCompression.cpp"
"BrickByBrickFix.cpp"
"BinaryPathFinder.cpp"
"FdbToSqlite.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

@@ -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());
@@ -122,43 +122,41 @@ void CatchUnhandled(int sig) {
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);
// 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());
}
} else {
Game::logger->Log("Diagnostics", "[%02zu] %s", i, functionName.c_str());
}
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
FILE* file = fopen(fileName.c_str(), "w+");
if (file != NULL) {
@@ -168,7 +166,7 @@ void CatchUnhandled(int sig) {
fclose(file);
}
#else // __include_backtrace__
#else
struct backtrace_state* state = backtrace_create_state(
Diagnostics::GetProcessFileName().c_str(),
@@ -179,7 +177,7 @@ void CatchUnhandled(int sig) {
struct bt_ctx ctx = { state, 0 };
Bt(state);
#endif // __include_backtrace__
#endif
exit(EXIT_FAILURE);
}

View File

@@ -1,12 +0,0 @@
#ifndef __DLUASSERT__H__
#define __DLUASSERT__H__
#include <assert.h>
#ifdef _DEBUG
# define DluAssert(expression) assert(expression)
#else
# define DluAssert(expression)
#endif
#endif //!__DLUASSERT__H__

View File

@@ -10,8 +10,6 @@ class dConfig;
class RakPeerInterface;
class AssetManager;
struct SystemAddress;
class EntityManager;
class dZoneManager;
namespace Game {
extern dLogger* logger;
@@ -24,6 +22,4 @@ namespace Game {
extern AssetManager* assetManager;
extern SystemAddress chatSysAddr;
extern bool shouldShutdown;
extern EntityManager* entityManager;
extern dZoneManager* zoneManager;
}

View File

@@ -111,6 +111,29 @@ namespace GeneralUtils {
*/
bool CheckBit(int64_t value, uint32_t index);
// MARK: Random Number Generation
//! Generates a random number
/*!
\param min The minimum the generate from
\param max The maximum to generate to
*/
template <typename T>
inline T GenerateRandomNumber(std::size_t min, std::size_t max) {
// Make sure it is a numeric type
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
if constexpr (std::is_integral_v<T>) { // constexpr only necessary on first statement
std::uniform_int_distribution<T> distribution(min, max);
return distribution(Game::randomEngine);
} else if (std::is_floating_point_v<T>) {
std::uniform_real_distribution<T> distribution(min, max);
return distribution(Game::randomEngine);
}
return T();
}
bool ReplaceInString(std::string& str, const std::string& from, const std::string& to);
std::u16string ReadWString(RakNet::BitStream* inStream);
@@ -126,11 +149,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);
@@ -205,42 +223,4 @@ namespace GeneralUtils {
std::hash<T> h;
s ^= h(v) + 0x9e3779b9 + (s << 6) + (s >> 2);
}
// MARK: Random Number Generation
//! Generates a random number
/*!
\param min The minimum the generate from
\param max The maximum to generate to
*/
template <typename T>
inline T GenerateRandomNumber(std::size_t min, std::size_t max) {
// Make sure it is a numeric type
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
if constexpr (std::is_integral_v<T>) { // constexpr only necessary on first statement
std::uniform_int_distribution<T> distribution(min, max);
return distribution(Game::randomEngine);
} else if (std::is_floating_point_v<T>) {
std::uniform_real_distribution<T> distribution(min, max);
return distribution(Game::randomEngine);
}
return T();
}
// on Windows we need to undef these or else they conflict with our numeric limits calls
// DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS
#ifdef _WIN32
#undef min
#undef max
#endif
template <typename T>
inline T GenerateRandomNumber() {
// Make sure it is a numeric type
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
return GenerateRandomNumber<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
}
}

View File

@@ -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)) {
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)) {
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)) {
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,7 +102,9 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
} else if (ldfTypeAndValue.second == "false") {
data = 0;
} else {
if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), 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,7 +122,9 @@ LDFBaseData* LDFBaseData::DataFromString(const std::string_view& format) {
} else if (ldfTypeAndValue.second == "false") {
data = false;
} else {
if (!GeneralUtils::TryParse(ldfTypeAndValue.second.data(), 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)) {
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)) {
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;
}

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

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

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

@@ -101,13 +101,13 @@ 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,
COMBAT_MEDIATOR,
COMMENDATION_VENDOR,
GATE_RUSH_CONTROL,
UNKNOWN_103,
RAIL_ACTIVATOR,
ROLLER,
PLAYER_FORCED_MOVEMENT,
@@ -119,7 +119,7 @@ enum class eReplicaComponentType : uint32_t {
UNKNOWN_112,
PROPERTY_PLAQUE,
BUILD_BORDER,
UNKNOWN_115,
UNKOWN_115,
CULLING_PLANE,
DESTROYABLE = 1000 // Actually 7
};

View File

@@ -13,7 +13,18 @@
#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
// Enable this to skip some unused columns in some tables
#define UNUSED(v)
/*!
\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

@@ -38,53 +38,43 @@
#include "CDFeatureGatingTable.h"
#include "CDRailActivatorComponent.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();
CDRewardsTable::Instance().LoadValuesFromDatabase();
CDScriptComponentTable::Instance().LoadValuesFromDatabase();
CDSkillBehaviorTable::Instance().LoadValuesFromDatabase();
CDVendorComponentTable::Instance().LoadValuesFromDatabase();
CDZoneTableTable::Instance().LoadValuesFromDatabase();
CDActivityRewardsTable::Instance();
UNUSED(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();
}

View File

@@ -4,8 +4,6 @@
#include "Singleton.h"
#define UNUSED_TABLE(v)
/**
* Initialize the CDClient tables so they are all loaded into memory.
*/

View File

@@ -1,6 +1,7 @@
set(DDATABASE_SOURCES "CDClientDatabase.cpp"
"CDClientManager.cpp"
"Database.cpp")
"Database.cpp"
"MigrationRunner.cpp")
add_subdirectory(Tables)
@@ -8,17 +9,5 @@ foreach(file ${DDATABASE_TABLES_SOURCES})
set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "Tables/${file}")
endforeach()
add_subdirectory(Databases)
foreach (file ${DDATABASE_DATABASES_SOURCES})
set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "Databases/${file}")
endforeach()
add_subdirectory(Databases/Migrations)
foreach (file ${DDATABASE_DATABASES_MIGRATIONS_SOURCES})
set(DDATABASE_SOURCES ${DDATABASE_SOURCES} "Databases/Migrations/${file}")
endforeach()
add_library(dDatabase STATIC ${DDATABASE_SOURCES})
target_link_libraries(dDatabase sqlite3 mariadbConnCpp)

View File

@@ -1,26 +1,118 @@
#include "Database.h"
#include "Game.h"
#include "dConfig.h"
#include "dLogger.h"
using namespace std;
#include "Databases/MySQL.h"
#pragma warning (disable:4251) //Disables SQL warnings
void Database::Connect(dConfig* config) {
bool useSqlite = true;
if (config->GetValue("mysql_host") != "" && config->GetValue("mysql_database") != "" && config->GetValue("mysql_username") != "" && config->GetValue("mysql_password") != "") {
useSqlite = false;
}
sql::Driver* Database::driver;
sql::Connection* Database::con;
sql::Properties Database::props;
std::string Database::database;
if (useSqlite) {
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 {
Database::Connection = new MySQLDatabase(config->GetValue("mysql_host"), config->GetValue("mysql_database"), config->GetValue("mysql_username"), config->GetValue("mysql_password"));
Database::ConnectionType = eConnectionTypes::MYSQL;
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!");
}
Database::Connection->Connect();
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();
}
void Database::Destroy() {
Database::Connection->Destroy();
delete Database::Connection;
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);
}

View File

@@ -1,16 +1,31 @@
#pragma once
#include <string>
#include <conncpp.hpp>
#include "Databases/DatabaseBase.h"
class dConfig;
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 DatabaseBase* Connection;
static eConnectionTypes ConnectionType;
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 void Connect(dConfig* config);
static void Destroy();
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 +0,0 @@
set(DDATABASE_DATABASES_SOURCES "MySQL.cpp")

View File

@@ -1,100 +0,0 @@
#pragma once
#include <string>
#include <vector>
#include "RakNetTypes.h"
#include "Structures.h"
enum eConnectionTypes {
NONE,
MYSQL,
SQLITE
};
class DatabaseBase {
public:
virtual void Connect() = 0;
virtual void Destroy() = 0;
// Server Get
virtual SocketDescriptor GetMasterServerIP() = 0;
// Server Set
virtual void CreateServer(const std::string& name, const std::string& ip, uint16_t port, uint32_t state, uint32_t version) = 0;
virtual void SetServerIpAndPortByName(const std::string& name, const std::string& ip, uint16_t port) = 0;
// Misc
virtual void InsertIntoActivityLog(uint32_t playerId, uint32_t activityId, uint32_t timestamp, uint32_t zoneId) = 0;
virtual void InsertIntoCommandLog(uint32_t playerId, const std::string& command) = 0;
// Character Get
virtual CharacterInfo GetCharacterInfoByID(uint32_t id) = 0;
virtual CharacterInfo GetCharacterInfoByName(const std::string& name) = 0;
virtual std::string GetCharacterXMLByID(uint32_t id) = 0;
virtual std::vector<std::string> GetAllCharacterNames() = 0;
virtual std::vector<CharacterInfo> GetAllCharactersByAccountID(uint32_t accountId) = 0;
virtual bool IsCharacterNameAvailable(const std::string& name) = 0;
// Charater Write
virtual void CreateCharacterXML(uint32_t id, const std::string& xml) = 0;
virtual void UpdateCharacterXML(uint32_t id, const std::string& xml) = 0;
virtual void CreateCharacter(uint32_t id, uint32_t account_id, const std::string& name, const std::string& pending_name, bool needs_rename, uint64_t last_login) = 0;
virtual void ApproveCharacterName(uint32_t id, const std::string& newName) = 0;
virtual void SetPendingCharacterName(uint32_t id, const std::string& pendingName) = 0;
virtual void UpdateCharacterLastLogin(uint32_t id, uint64_t time) = 0;
// Character Delete
virtual void DeleteCharacter(uint32_t id) = 0;
// Friends Get
virtual bool AreBestFriends(uint32_t charId1, uint32_t charId2) = 0;
// Account Get
virtual AccountInfo GetAccountByName(const std::string& name) = 0;
virtual AccountInfo GetAccountByID(uint32_t id) = 0;
virtual uint32_t GetLatestCharacterOfAccount(uint32_t id) = 0;
// Account Set
virtual void BanAccount(uint32_t id) = 0;
virtual void MuteAccount(uint32_t id, uint64_t muteExpireDate) = 0;
// Pet Write
virtual void CreatePetName(uint64_t id, const std::string& name, bool approved) = 0;
// Pet Delete
virtual void DeletePetName(uint64_t id) = 0;
// Pet Get
virtual PetName GetPetName(uint64_t id) = 0;
// Keys Get
virtual bool IsKeyActive(uint32_t id) = 0;
// Object ID tracker Get
virtual uint32_t GetObjectIDTracker() = 0;
// Object ID tracker Set
virtual void SetObjectIDTracker(uint32_t id) = 0;
// Mail Get
virtual MailInfo GetMailByID(uint64_t id) = 0;
virtual std::vector<MailInfo> GetAllRecentMailOfUser(uint32_t id) = 0;
virtual uint32_t GetUnreadMailCountForUser(uint32_t id) = 0;
// Mail Write
virtual void WriteMail(uint32_t senderId, const std::string& senderName, uint32_t receiverId, const std::string& receiverName, uint64_t sendTime, const std::string& subject, const std::string& body, uint32_t attachmentId = 0, uint32_t attachmentLot = 0, uint64_t attachmentSubkey = 0, uint32_t attachmentCount = 0, bool wasRead = false) = 0;
virtual void SetMailAsRead(uint64_t id) = 0;
virtual void RemoveAttachmentFromMail(uint64_t id) = 0;
// Mail Delete
virtual void DeleteMail(uint64_t id) = 0;
// Property Get
virtual uint64_t GetPropertyFromTemplateAndClone(uint32_t templateId, uint32_t cloneId) = 0;
virtual std::vector<uint32_t> GetBBBModlesForProperty(uint32_t propertyId) = 0;
virtual std::istream GetLXFMLFromID(uint32_t id) = 0;
private:
};

View File

@@ -1 +0,0 @@
set(DDATABASE_DATABASES_MIGRATIONS_SOURCES "MigrationManager.cpp" "MySQLMigration.cpp")

View File

@@ -1,90 +0,0 @@
#include "MigrationManager.h"
#include <fstream>
#include <string>
#include "dLogger.h"
#include "GeneralUtils.h"
#include "CDClientDatabase.h"
#include "BinaryPathFinder.h"
Migration Migration::LoadMigration(const std::string& type, const std::string& dbType, const std::string& name) {
Migration migration{};
std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / type / dbType / name);
if (file.is_open()) {
std::string line;
std::string total = "";
while (std::getline(file, line)) {
total += line;
}
file.close();
migration.name = ;
migration.data = total;
}
return migration;
}
void MigrationManager::RunSQLiteMigrations() {
auto cdstmt = CDClientDatabase::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);");
cdstmt.execQuery().finalize();
cdstmt.finalize();
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) {
auto migration = Migration::LoadMigration("cdserver", "", entry);
if (migration.data.empty()) continue;
// Check if there is an entry in the migration history table on the cdclient database.
cdstmt = CDClientDatabase::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
cdstmt.bind((int32_t)1, migration.name.c_str());
auto cdres = cdstmt.execQuery();
bool doExit = !cdres.eof();
cdres.finalize();
cdstmt.finalize();
if (doExit) continue;
// Check first if there is entry in the migration history table on the main database.
stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
stmt->setString(1, migration.name.c_str());
auto* res = stmt->executeQuery();
doExit = res->next();
delete res;
delete stmt;
if (doExit) {
// Insert into cdclient database if there is an entry in the main database but not the cdclient database.
cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
cdstmt.bind((int32_t)1, migration.name.c_str());
cdstmt.execQuery().finalize();
cdstmt.finalize();
continue;
}
// Doing these 1 migration at a time since one takes a long time and some may think it is crashing.
// This will at the least guarentee that the full migration needs to be run in order to be counted as "migrated".
Game::logger->Log("MigrationRunner", "Executing migration: %s. This may take a while. Do not shut down server.", migration.name.c_str());
CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;");
for (const auto& dml : GeneralUtils::SplitString(migration.data, ';')) {
if (dml.empty()) continue;
try {
CDClientDatabase::ExecuteDML(dml.c_str());
} catch (CppSQLite3Exception& e) {
Game::logger->Log("MigrationRunner", "Encountered error running DML command: (%i) : %s", e.errorCode(), e.errorMessage());
}
}
// Insert into cdclient database.
cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
cdstmt.bind((int32_t)1, migration.name.c_str());
cdstmt.execQuery().finalize();
cdstmt.finalize();
CDClientDatabase::ExecuteQuery("COMMIT;");
}
Game::logger->Log("MigrationRunner", "CDServer database is up to date.");
}

View File

@@ -1,17 +0,0 @@
#pragma once
class DatabaseBase;
struct Migration {
std::string data;
std::string name;
static Migration LoadMigration(const std::string& type, const std::string& dbType, const std::string& name);
};
class MigrationManager {
public:
void RunMigrations(DatabaseBase* db);
void RunSQLiteMigrations();
private:
};

View File

@@ -1,13 +0,0 @@
#pragma once
#include <string>
#include "MigrationManager.h"
class MySQLMigrationManager : public MigrationManager {
public:
void RunMigrations(DatabaseBase* db) override;
void RunSQLiteMigrations() override;
private:
};

View File

@@ -1,651 +0,0 @@
#include "MySQL.h"
#pragma warning (disable:4251)
#include "Game.h"
#include "dConfig.h"
#include "dLogger.h"
MySQLDatabase::MySQLDatabase(const std::string& host, const std::string& database, const std::string& username, const std::string& password) {
this->m_Host = host;
this->m_Database = database;
this->m_Username = username;
this->m_Password = password;
m_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 (this->m_Host.find(UNIX_PROTO) == 0) {
properties["hostName"] = "unix://localhost";
properties["localSocket"] = this->m_Host.substr(UNIX_PROTO.length()).c_str();
} else if (this->m_Host.find(PIPE_PROTO) == 0) {
properties["hostName"] = "pipe://localhost";
properties["pipe"] = this->m_Host.substr(PIPE_PROTO.length()).c_str();
} else {
properties["hostName"] = this->m_Host.c_str();
}
properties["user"] = this->m_Username.c_str();
properties["password"] = this->m_Password.c_str();
properties["autoReconnect"] = "true";
this->m_Properties = properties;
this->m_Database = database;
}
MySQLDatabase::~MySQLDatabase() {
this->Destroy();
}
void MySQLDatabase::Connect() {
if (this->m_Properties.find("localSocket") != this->m_Properties.end() || this->m_Properties.find("pipe") != this->m_Properties.end()) {
this->m_Connection = m_Driver->connect(this->m_Properties);
} else {
this->m_Connection = m_Driver->connect(
this->m_Properties["hostName"].c_str(),
this->m_Properties["user"].c_str(),
this->m_Properties["password"].c_str()
);
}
this->m_Connection->setSchema(this->m_Database.c_str());
}
void MySQLDatabase::Destroy() {
if (this->m_Connection != nullptr) {
this->m_Connection->close();
delete this->m_Connection;
this->m_Connection = nullptr;
}
}
sql::Statement* MySQLDatabase::CreateStmt() {
sql::Statement* toReturn = this->m_Connection->createStatement();
return toReturn;
}
sql::PreparedStatement* MySQLDatabase::CreatePreppedStmt(const std::string& query) {
const char* test = query.c_str();
size_t size = query.length();
sql::SQLString str(test, size);
if (!this->m_Connection) {
Connect();
Game::logger->Log("Database", "Trying to reconnect to MySQL");
}
if (!this->m_Connection->isValid() || this->m_Connection->isClosed()) {
delete this->m_Connection;
this->m_Connection = nullptr;
Connect();
Game::logger->Log("Database", "Trying to reconnect to MySQL from invalid or closed connection");
}
auto* stmt = this->m_Connection->prepareStatement(str);
return stmt;
}
std::unique_ptr<sql::PreparedStatement> MySQLDatabase::CreatePreppedStmtUnique(const std::string& query) {
std::unique_ptr<sql::PreparedStatement> stmt(CreatePreppedStmt(query));
return stmt;
}
std::unique_ptr<sql::ResultSet> MySQLDatabase::GetResultsOfStatement(sql::Statement* stmt) {
auto* res = stmt->executeQuery();
std::unique_ptr<sql::ResultSet> result(res);
return result;
}
void MySQLDatabase::Commit() {
this->m_Connection->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 m_Connection->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.
m_Connection->setAutoCommit(value);
}
SocketDescriptor MySQLDatabase::GetMasterServerIP() {
auto stmt = CreatePreppedStmtUnique("SELECT ip, port FROM servers WHERE name = 'master';");
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
return SocketDescriptor(res->getInt("port"), res->getString("ip"));
}
return SocketDescriptor(0, "");
}
void MySQLDatabase::CreateServer(const std::string& name, const std::string& ip, uint16_t port, uint32_t state, uint32_t version) {
auto stmt = CreatePreppedStmtUnique("INSERT INTO servers (name, ip, port, state, version) VALUES (?, ?, ?, ?, ?);");
stmt->setString(1, name);
stmt->setString(2, ip);
stmt->setInt(3, port);
stmt->setInt(4, state);
stmt->setInt(5, version);
stmt->execute();
}
void MySQLDatabase::SetServerIpAndPortByName(const std::string& name, const std::string& ip, uint16_t port) {
auto stmt = CreatePreppedStmtUnique("UPDATE servers SET ip = ?, port = ? WHERE name = ?;");
stmt->setString(1, ip);
stmt->setInt(2, port);
stmt->setString(3, name);
stmt->execute();
}
std::vector<std::string> MySQLDatabase::GetAllCharacterNames() {
auto stmt = CreatePreppedStmtUnique("SELECT name FROM charinfo;");
auto res = GetResultsOfStatement(stmt.get());
std::vector<std::string> names;
while (res->next()) {
names.push_back(res->getString("name").c_str());
}
return names;
}
bool MySQLDatabase::IsCharacterNameAvailable(const std::string& name) {
auto stmt = CreatePreppedStmtUnique("SELECT name FROM charinfo WHERE name = ? OR pending_name = ?;");
stmt->setString(1, name);
stmt->setString(2, name);
auto res = GetResultsOfStatement(stmt.get());
return !res->next();
}
void MySQLDatabase::InsertIntoActivityLog(uint32_t playerId, uint32_t activityId, uint32_t timestamp, uint32_t zoneId) {
auto stmt = CreatePreppedStmtUnique("INSERT INTO activity_log (character_id, activity, time, map_id) VALUES (?, ?, ?, ?);");
stmt->setUInt(1, playerId);
stmt->setUInt(2, activityId);
stmt->setUInt(3, timestamp);
stmt->setUInt(4, zoneId);
stmt->executeUpdate();
}
void MySQLDatabase::InsertIntoCommandLog(uint32_t playerId, const std::string& command) {
auto stmt = CreatePreppedStmtUnique("INSERT INTO command_log (character_id, command) VALUES (?, ?);");
stmt->setUInt(1, playerId);
stmt->setString(2, command);
stmt->executeUpdate();
}
CharacterInfo MySQLDatabase::GetCharacterInfoByID(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("SELECT account_id, name, pending_name, needs_rename, prop_clone_id, permission_map FROM charinfo WHERE id = ? LIMIT 1;");
stmt->setUInt(1, id);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
CharacterInfo info{};
info.AccountID = res->getUInt("account_id");
info.ID = id;
info.Name = res->getString("name");
info.PendingName = res->getString("pending_name");
info.NameRejected = res->getBoolean("needs_rename");
info.PropertyCloneID = res->getUInt("prop_clone_id");
info.PermissionMap = (ePermissionMap)res->getUInt("permission_map");
return info;
}
return CharacterInfo{};
}
CharacterInfo MySQLDatabase::GetCharacterInfoByName(const std::string& name) {
auto stmt = CreatePreppedStmtUnique("SELECT id FROM charinfo WHERE name = ?");
stmt->setString(1, name);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
return GetCharacterInfoByID(res->getUInt("id"));
}
return CharacterInfo{};
}
uint32_t MySQLDatabase::GetLatestCharacterOfAccount(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("SELECT id FROM charinfo WHERE account_id = ? ORDER BY last_login DESC LIMIT 1;");
stmt->setUInt(1, id);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
return res->getUInt("id");
}
return 0;
}
std::string MySQLDatabase::GetCharacterXMLByID(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("SELECT xml_data FROM charxml WHERE id = ? LIMIT 1;");
stmt->setUInt(1, id);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
return res->getString("xml_data").c_str();
}
return "";
}
void MySQLDatabase::CreateCharacterXML(uint32_t id, const std::string& xml) {
auto stmt = CreatePreppedStmtUnique("INSERT INTO charxml (id, xml_data) VALUES (?, ?);");
stmt->setUInt(1, id);
stmt->setString(2, xml);
stmt->executeUpdate();
}
void MySQLDatabase::UpdateCharacterXML(uint32_t id, const std::string& xml) {
auto stmt = CreatePreppedStmtUnique("UPDATE charxml SET xml_data = ? WHERE id = ?;");
stmt->setString(1, xml);
stmt->setUInt(2, id);
stmt->executeUpdate();
}
void MySQLDatabase::CreateCharacter(uint32_t id, uint32_t account_id, const std::string& name, const std::string& pending_name, bool needs_rename, uint64_t last_login) {
auto stmt = CreatePreppedStmtUnique("INSERT INTO charinfo (id, account_id, name, pending_name, needs_rename, last_login) VALUES (?, ?, ?, ?, ?, ?);");
stmt->setUInt(1, id);
stmt->setUInt(2, account_id);
stmt->setString(3, name);
stmt->setString(4, pending_name);
stmt->setBoolean(5, needs_rename);
stmt->setUInt64(6, last_login);
stmt->execute();
}
void MySQLDatabase::ApproveCharacterName(uint32_t id, const std::string& newName) {
auto stmt = CreatePreppedStmtUnique("UPDATE charinfo SET name = ?, pending_name = '', needs_rename = 0, last_login = ? WHERE id = ? LIMIT 1");
stmt->setString(1, newName);
stmt->setUInt64(2, time(NULL));
stmt->setUInt(3, id);
stmt->execute();
}
void MySQLDatabase::SetPendingCharacterName(uint32_t id, const std::string& pendingName) {
auto stmt = CreatePreppedStmtUnique("UPDATE charinfo SET pending_name=?, needs_rename=0, last_login=? WHERE id=? LIMIT 1;");
stmt->setString(1, pendingName);
stmt->setUInt64(2, time(NULL));
stmt->setUInt(3, id);
stmt->execute();
}
void MySQLDatabase::UpdateCharacterLastLogin(uint32_t id, uint64_t time) {
auto stmt = CreatePreppedStmtUnique("UPDATE charinfo SET last_login = ? WHERE id = ? LIMIT 1;");
stmt->setUInt64(1, time);
stmt->setUInt(2, id);
stmt->execute();
}
void MySQLDatabase::DeleteCharacter(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("DELETE FROM charxml WHERE id = ?;");
stmt->setUInt(1, id);
stmt->execute();
stmt = CreatePreppedStmtUnique("DELETE FROM command_log WHERE character_id = ?;");
stmt->setUInt(1, id);
stmt->execute();
stmt = CreatePreppedStmtUnique("DELETE FROM friends WHERE player_id = ? OR friend_id = ?;");
stmt->setUInt(1, id);
stmt->setUInt(2, id);
stmt->execute();
stmt = CreatePreppedStmtUnique("DELETE FROM leaderboard WHERE character_id = ?;");
stmt->setUInt(1, id);
stmt->execute();
stmt = CreatePreppedStmtUnique("DELETE FROM properties_contents WHERE property_id IN (SELECT id FROM properties WHERE owner_id = ?)");
stmt->setUInt(1, id);
stmt->execute();
stmt = CreatePreppedStmtUnique("DELETE FROM properties WHERE owner_id = ?;");
stmt->setUInt(1, id);
stmt->execute();
stmt = CreatePreppedStmtUnique("DELETE FROM ugc WHERE character_id = ?;");
stmt->setUInt(1, id);
stmt->execute();
stmt = CreatePreppedStmtUnique("DELETE FROM activity_log WHERE character_id = ?;");
stmt->setUInt(1, id);
stmt->execute();
stmt = CreatePreppedStmtUnique("DELETE FROM mail WHERE receiver_id = ?;");
stmt->setUInt(1, id);
stmt->execute();
stmt = CreatePreppedStmtUnique("DELETE FROM charinfo WHERE id = ?;");
stmt->setUInt(1, id);
stmt->execute();
}
AccountInfo MySQLDatabase::GetAccountByName(const std::string& name) {
auto stmt = CreatePreppedStmtUnique("SELECT id FROM accounts WHERE name = ? LIMIT 1;");
stmt->setString(1, name);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
return GetAccountByID(res->getUInt("id"));
}
return AccountInfo{};
}
bool MySQLDatabase::AreBestFriends(uint32_t goon1, uint32_t goon2) {
auto stmt = CreatePreppedStmtUnique("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;");
stmt->setUInt(1, goon1);
stmt->setUInt(2, goon2);
stmt->setUInt(3, goon2);
stmt->setUInt(4, goon1);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
return res->getInt("best_friend") == 3;
}
return false;
}
AccountInfo MySQLDatabase::GetAccountByID(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("SELECT name, password, gm_level, locked, banned, play_key_id, created_at, mute_expire FROM accounts WHERE id = ? LIMIT 1;");
stmt->setUInt(1, id);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
AccountInfo info{};
info.ID = id;
info.Name = res->getString("name");
info.Password = res->getString("password");
info.MaxGMLevel = res->getUInt("gm_level");
info.Locked = res->getBoolean("locked");
info.Banned = res->getBoolean("banned");
info.PlayKeyID = res->getUInt("play_key_id");
info.CreatedAt = res->getUInt64("created_at");
info.MuteExpire = res->getUInt64("mute_expire");
return info;
}
return AccountInfo{};
}
void MySQLDatabase::BanAccount(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("UPDATE accounts SET banned = 1 WHERE id = ?;");
stmt->setUInt(1, id);
stmt->execute();
}
void MySQLDatabase::MuteAccount(uint32_t id, uint64_t muteExpireDate) {
auto stmt = CreatePreppedStmtUnique("UPDATE accounts SET mute_expire = ? WHERE id = ?;");
stmt->setUInt64(1, muteExpireDate);
stmt->setUInt(2, id);
stmt->execute();
}
std::vector<CharacterInfo> MySQLDatabase::GetAllCharactersByAccountID(uint32_t accountId) {
auto stmt = CreatePreppedStmtUnique("SELECT id FROM charinfo WHERE account_id = ? LIMIT 4;");
stmt->setUInt(1, accountId);
auto res = GetResultsOfStatement(stmt.get());
std::vector<CharacterInfo> characters;
while (res->next()) {
characters.push_back(GetCharacterInfoByID(res->getUInt("id")));
}
return characters;
}
void MySQLDatabase::CreatePetName(uint64_t id, const std::string& name, bool approved) {
auto stmt = CreatePreppedStmtUnique("INSERT INTO pet_names (id, name, approved) VALUES (?, ?, ?);");
stmt->setUInt64(1, id);
stmt->setString(2, name);
stmt->setBoolean(3, approved);
stmt->execute();
}
void MySQLDatabase::DeletePetName(uint64_t id) {
auto stmt = CreatePreppedStmtUnique("DELETE FROM pet_names WHERE id = ?;");
stmt->setUInt64(1, id);
stmt->execute();
}
PetName MySQLDatabase::GetPetName(uint64_t id) {
auto stmt = CreatePreppedStmtUnique("SELECT name, approved FROM pet_names WHERE id = ? LIMIT 1;");
stmt->setUInt64(1, id);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
PetName name{};
name.ID = id;
name.Name = res->getString("name");
name.Approved = res->getBoolean("approved");
return name;
}
return PetName{};
}
bool MySQLDatabase::IsKeyActive(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("SELECT * FROM play_keys WHERE id = ? AND active = 1 LIMIT 1;");
stmt->setUInt(1, id);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
return true;
}
return false;
}
uint32_t MySQLDatabase::GetObjectIDTracker() {
auto stmt = CreatePreppedStmtUnique("SELECT last_object_id FROM object_id_tracker;");
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
uint32_t id = res->getUInt("last_object_id");
return id;
}
auto stmt = CreatePreppedStmtUnique("INSERT INTO object_id_tracker (last_object_id) VALUES (1);");
stmt->execute();
return 1;
}
void MySQLDatabase::SetObjectIDTracker(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("UPDATE object_id_tracker SET last_object_id = ?;");
stmt->setUInt(1, id);
stmt->execute();
}
MailInfo MySQLDatabase::GetMailByID(uint64_t id) {
auto stmt = CreatePreppedStmtUnique("SELECT * FROM mail WHERE id = ?;");
stmt->setUInt64(1, id);
auto res = GetResultsOfStatement(stmt.get());
if (res->next()) {
MailInfo mail{};
mail.ID = id;
mail.SenderID = res->getUInt("sender_id");
mail.SenderName = res->getString("sender_name");
mail.ReceiverID = res->getUInt("receiver_id");
mail.ReceiverName = res->getString("receiver_name");
mail.TimeSent = res->getUInt64("time_sent");
mail.Subject = res->getString("subject");
mail.Body = res->getString("body");
mail.AttachmentID = res->getUInt("attachment_id");
mail.AttachmentLOT = res->getUInt("attachment_lot");
mail.AttachmentSubkey = res->getUInt64("attachment_subkey");
mail.AttachmentCount = res->getUInt("attachment_count");
mail.WasRead = res->getBoolean("was_read");
return mail;
}
return MailInfo{};
}
std::vector<MailInfo> MySQLDatabase::GetAllRecentMailOfUser(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("SELECT * FROM mail WHERE receiver_id = ? LIMIT 20;");
stmt->setUInt(1, id);
auto res = GetResultsOfStatement(stmt.get());
std::vector<MailInfo> mail;
while (res->next()) {
MailInfo mailInfo{};
mailInfo.ID = res->getUInt64("id");
mailInfo.SenderID = res->getUInt("sender_id");
mailInfo.SenderName = res->getString("sender_name");
mailInfo.ReceiverID = res->getUInt("receiver_id");
mailInfo.ReceiverName = res->getString("receiver_name");
mailInfo.TimeSent = res->getUInt64("time_sent");
mailInfo.Subject = res->getString("subject");
mailInfo.Body = res->getString("body");
mailInfo.AttachmentID = res->getUInt("attachment_id");
mailInfo.AttachmentLOT = res->getUInt("attachment_lot");
mailInfo.AttachmentSubkey = res->getUInt64("attachment_subkey");
mailInfo.AttachmentCount = res->getUInt("attachment_count");
mailInfo.WasRead = res->getBoolean("was_read");
mail.push_back(mailInfo);
}
return mail;
}
uint32_t MySQLDatabase::GetUnreadMailCountForUser(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("SELECT COUNT(*) FROM mail WHERE receiver_id = ? AND was_read = 0;");
stmt->setUInt(1, id);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
return res->getUInt(1);
}
return 0;
}
void MySQLDatabase::WriteMail(uint32_t senderId, const std::string& senderName, uint32_t receiverId, const std::string& receiverName, uint64_t sendTime, const std::string& subject, const std::string& body, uint32_t attachmentId, uint32_t attachmentLot, uint64_t attachmentSubkey, uint32_t attachmentCount, bool wasRead) {
auto stmt = CreatePreppedStmtUnique("INSERT INTO mail (sender_id, sender_name, receiver_id, receiver_name, time_sent, subject, body, attachment_id, attachment_lot, attachment_subkey, attachment_count, was_read) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);");
stmt->setUInt(1, senderId);
stmt->setString(2, senderName);
stmt->setUInt(3, receiverId);
stmt->setString(4, receiverName);
stmt->setUInt64(5, sendTime);
stmt->setString(6, subject);
stmt->setString(7, body);
stmt->setUInt(8, attachmentId);
stmt->setUInt(9, attachmentLot);
stmt->setUInt64(10, attachmentSubkey);
stmt->setUInt(11, attachmentCount);
stmt->setBoolean(12, wasRead);
stmt->execute();
}
void MySQLDatabase::DeleteMail(uint64_t id) {
auto stmt = CreatePreppedStmtUnique("DELETE FROM mail WHERE id = ?;");
stmt->setUInt64(1, id);
stmt->execute();
}
void MySQLDatabase::SetMailAsRead(uint64_t id) {
auto stmt = CreatePreppedStmtUnique("UPDATE mail SET was_read = 1 WHERE id = ?;");
stmt->setUInt64(1, id);
stmt->execute();
}
void MySQLDatabase::RemoveAttachmentFromMail(uint64_t id) {
auto stmt = CreatePreppedStmtUnique("UPDATE mail SET attachment_lot = 0 WHERE id = ?;");
stmt->setUInt64(1, id);
stmt->execute();
}
uint64_t MySQLDatabase::GetPropertyFromTemplateAndClone(uint32_t templateId, uint32_t cloneId) {
auto stmt = CreatePreppedStmtUnique("SELECT id FROM properties WHERE template_id = ? AND clone_id = ? LIMIT 1;");
stmt->setUInt(1, templateId);
stmt->setUInt(2, cloneId);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
return res->getUInt64("id");
}
return 0;
}
std::vector<uint32_t> MySQLDatabase::GetBBBModlesForProperty(uint32_t propertyId) {
auto stmt = CreatePreppedStmtUnique("SELECT ugc_id FROM properties_contents WHERE lot = 14 AND property_id = ?;");
stmt->setUInt(1, propertyId);
auto res = GetResultsOfStatement(stmt.get());
std::vector<uint32_t> models;
while (res->next()) {
models.push_back(res->getUInt("ugc_id"));
}
return models;
}
std::istream* MySQLDatabase::GetLXFMLFromID(uint32_t id) {
auto stmt = CreatePreppedStmtUnique("SELECT lxfml FROM ugc WHERE id = ? LIMIT 1;");
stmt->setUInt(1, id);
auto res = GetResultsOfStatement(stmt.get());
while (res->next()) {
std::istream* blob = res->getBlob("lxfml");
return blob;
}
return new std::istream();
}

View File

@@ -1,99 +0,0 @@
#pragma once
#include "DatabaseBase.h"
#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 MySQLDatabase : public DatabaseBase {
public:
MySQLDatabase(const std::string& host, const std::string& database, const std::string& username, const std::string& password);
~MySQLDatabase();
void Connect() override;
void Destroy() override;
sql::Statement* CreateStmt();
sql::PreparedStatement* CreatePreppedStmt(const std::string& query);
std::unique_ptr<sql::PreparedStatement> CreatePreppedStmtUnique(const std::string& query);
std::unique_ptr<sql::ResultSet> GetResultsOfStatement(sql::Statement* stmt);
void Commit();
bool GetAutoCommit();
void SetAutoCommit(bool value);
SocketDescriptor GetMasterServerIP() override;
void CreateServer(const std::string& name, const std::string& ip, uint16_t port, uint32_t state, uint32_t version) override;
void SetServerIpAndPortByName(const std::string& name, const std::string& ip, uint16_t port) override;
void InsertIntoActivityLog(uint32_t playerId, uint32_t activityId, uint32_t timestamp, uint32_t zoneId) override;
void InsertIntoCommandLog(uint32_t playerId, const std::string& command) override;
CharacterInfo GetCharacterInfoByID(uint32_t id) override;
CharacterInfo GetCharacterInfoByName(const std::string& name) override;
std::string GetCharacterXMLByID(uint32_t id) override;
std::vector<std::string> GetAllCharacterNames() override;
std::vector<CharacterInfo> GetAllCharactersByAccountID(uint32_t accountId) override;
bool IsCharacterNameAvailable(const std::string& name) override;
void CreateCharacterXML(uint32_t id, const std::string& xml) override;
void UpdateCharacterXML(uint32_t id, const std::string& xml) override;
void CreateCharacter(uint32_t id, uint32_t account_id, const std::string& name, const std::string& pending_name, bool needs_rename, uint64_t last_login) override;
void ApproveCharacterName(uint32_t id, const std::string& newName) override;
void SetPendingCharacterName(uint32_t id, const std::string& pendingName) override;
void UpdateCharacterLastLogin(uint32_t id, uint64_t time) override;
void DeleteCharacter(uint32_t id) override;
bool AreBestFriends(uint32_t charId1, uint32_t charId2) override;
AccountInfo GetAccountByName(const std::string& name) override;
AccountInfo GetAccountByID(uint32_t id) override;
uint32_t GetLatestCharacterOfAccount(uint32_t id) override;
void BanAccount(uint32_t id) override;
void MuteAccount(uint32_t id, uint64_t muteExpireDate) override;
void CreatePetName(uint64_t id, const std::string& name, bool approved) override;
void DeletePetName(uint64_t id) override;
PetName GetPetName(uint64_t id) override;
bool IsKeyActive(uint32_t id) override;
uint32_t GetObjectIDTracker() override;
void SetObjectIDTracker(uint32_t id) override;
MailInfo GetMailByID(uint64_t id) override;
std::vector<MailInfo> GetAllRecentMailOfUser(uint32_t id) override;
uint32_t GetUnreadMailCountForUser(uint32_t id) override;
void WriteMail(uint32_t senderId, const std::string& senderName, uint32_t receiverId, const std::string& receiverName, uint64_t sendTime, const std::string& subject, const std::string& body, uint32_t attachmentId = 0, uint32_t attachmentLot = 0, uint64_t attachmentSubkey = 0, uint32_t attachmentCount = 0, bool wasRead = false) override;
void SetMailAsRead(uint64_t id) override;
void RemoveAttachmentFromMail(uint64_t id) override;
void DeleteMail(uint64_t id) override;
uint64_t GetPropertyFromTemplateAndClone(uint32_t templateId, uint32_t cloneId) override;
std::vector<uint32_t> GetBBBModlesForProperty(uint32_t propertyId) override;
std::istream GetLXFMLFromID(uint32_t id) override;
private:
std::string m_Host;
std::string m_Database;
std::string m_Username;
std::string m_Password;
sql::Connection* m_Connection;
sql::Driver* m_Driver;
sql::Properties m_Properties;
};

View File

@@ -1,49 +0,0 @@
#pragma once
#include <string>
#include "ePermissionMap.h"
struct CharacterInfo {
uint32_t AccountID;
uint32_t ID;
std::string Name;
std::string PendingName;
bool NameRejected;
uint32_t PropertyCloneID;
ePermissionMap PermissionMap;
};
struct AccountInfo {
uint32_t ID;
std::string Name;
std::string Password;
uint32_t MaxGMLevel;
bool Locked;
bool Banned;
uint32_t PlayKeyID;
uint64_t CreatedAt;
uint64_t MuteExpire;
};
struct PetName {
uint64_t ID;
std::string Name;
bool Approved;
};
struct MailInfo {
uint64_t ID;
uint32_t SenderID;
std::string SenderName;
uint32_t ReceiverID;
std::string ReceiverName;
uint64_t TimeSent;
std::string Subject;
std::string Body;
uint32_t AttachmentID;
uint32_t AttachmentLOT;
uint64_t AttachmentSubkey;
uint32_t AttachmentCount;
bool WasRead;
};

View File

@@ -0,0 +1,158 @@
#include "MigrationRunner.h"
#include "BrickByBrickFix.h"
#include "CDClientDatabase.h"
#include "Database.h"
#include "Game.h"
#include "GeneralUtils.h"
#include "dLogger.h"
#include "BinaryPathFinder.h"
#include <istream>
Migration LoadMigration(std::string path) {
Migration migration{};
std::ifstream file(BinaryPathFinder::GetBinaryDir() / "migrations/" / path);
if (file.is_open()) {
std::string line;
std::string total = "";
while (std::getline(file, line)) {
total += line;
}
file.close();
migration.name = path;
migration.data = total;
}
return migration;
}
void MigrationRunner::RunMigrations() {
auto* stmt = Database::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());");
stmt->execute();
delete stmt;
sql::SQLString finalSQL = "";
bool runSd0Migrations = false;
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "./migrations/dlu/").string())) {
auto migration = LoadMigration("dlu/" + entry);
if (migration.data.empty()) {
continue;
}
stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
stmt->setString(1, migration.name.c_str());
auto* res = stmt->executeQuery();
bool doExit = res->next();
delete res;
delete stmt;
if (doExit) continue;
Game::logger->Log("MigrationRunner", "Running migration: %s", migration.name.c_str());
if (migration.name == "dlu/5_brick_model_sd0.sql") {
runSd0Migrations = true;
} else {
finalSQL.append(migration.data.c_str());
}
stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
stmt->setString(1, migration.name.c_str());
stmt->execute();
delete stmt;
}
if (finalSQL.empty() && !runSd0Migrations) {
Game::logger->Log("MigrationRunner", "Server database is up to date.");
return;
}
if (!finalSQL.empty()) {
auto migration = GeneralUtils::SplitString(static_cast<std::string>(finalSQL), ';');
std::unique_ptr<sql::Statement> simpleStatement(Database::CreateStmt());
for (auto& query : migration) {
try {
if (query.empty()) continue;
simpleStatement->execute(query.c_str());
} catch (sql::SQLException& e) {
Game::logger->Log("MigrationRunner", "Encountered error running migration: %s", e.what());
}
}
}
// Do this last on the off chance none of the other migrations have been run yet.
if (runSd0Migrations) {
uint32_t numberOfUpdatedModels = BrickByBrickFix::UpdateBrickByBrickModelsToSd0();
Game::logger->Log("MasterServer", "%i models were updated from zlib to sd0.", numberOfUpdatedModels);
uint32_t numberOfTruncatedModels = BrickByBrickFix::TruncateBrokenBrickByBrickXml();
Game::logger->Log("MasterServer", "%i models were truncated from the database.", numberOfTruncatedModels);
}
}
void MigrationRunner::RunSQLiteMigrations() {
auto cdstmt = CDClientDatabase::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP);");
cdstmt.execQuery().finalize();
cdstmt.finalize();
auto* stmt = Database::CreatePreppedStmt("CREATE TABLE IF NOT EXISTS migration_history (name TEXT NOT NULL, date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP());");
stmt->execute();
delete stmt;
for (const auto& entry : GeneralUtils::GetSqlFileNamesFromFolder((BinaryPathFinder::GetBinaryDir() / "migrations/cdserver/").string())) {
auto migration = LoadMigration("cdserver/" + entry);
if (migration.data.empty()) continue;
// Check if there is an entry in the migration history table on the cdclient database.
cdstmt = CDClientDatabase::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
cdstmt.bind((int32_t) 1, migration.name.c_str());
auto cdres = cdstmt.execQuery();
bool doExit = !cdres.eof();
cdres.finalize();
cdstmt.finalize();
if (doExit) continue;
// Check first if there is entry in the migration history table on the main database.
stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;");
stmt->setString(1, migration.name.c_str());
auto* res = stmt->executeQuery();
doExit = res->next();
delete res;
delete stmt;
if (doExit) {
// Insert into cdclient database if there is an entry in the main database but not the cdclient database.
cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
cdstmt.bind((int32_t) 1, migration.name.c_str());
cdstmt.execQuery().finalize();
cdstmt.finalize();
continue;
}
// Doing these 1 migration at a time since one takes a long time and some may think it is crashing.
// This will at the least guarentee that the full migration needs to be run in order to be counted as "migrated".
Game::logger->Log("MigrationRunner", "Executing migration: %s. This may take a while. Do not shut down server.", migration.name.c_str());
CDClientDatabase::ExecuteQuery("BEGIN TRANSACTION;");
for (const auto& dml : GeneralUtils::SplitString(migration.data, ';')) {
if (dml.empty()) continue;
try {
CDClientDatabase::ExecuteDML(dml.c_str());
} catch (CppSQLite3Exception& e) {
Game::logger->Log("MigrationRunner", "Encountered error running DML command: (%i) : %s", e.errorCode(), e.errorMessage());
}
}
// Insert into cdclient database.
cdstmt = CDClientDatabase::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
cdstmt.bind((int32_t) 1, migration.name.c_str());
cdstmt.execQuery().finalize();
cdstmt.finalize();
CDClientDatabase::ExecuteQuery("COMMIT;");
}
Game::logger->Log("MigrationRunner", "CDServer database is up to date.");
}

View File

@@ -0,0 +1,13 @@
#pragma once
#include <string>
struct Migration {
std::string data;
std::string name;
};
namespace MigrationRunner {
void RunMigrations();
void RunSQLiteMigrations();
};

View File

@@ -1,6 +1,7 @@
#include "CDActivitiesTable.h"
void CDActivitiesTable::LoadValuesFromDatabase() {
CDActivitiesTable::CDActivitiesTable(void) {
// First, get the size of the table
unsigned int size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM Activities");
@@ -54,3 +55,8 @@ std::vector<CDActivities> CDActivitiesTable::Query(std::function<bool(CDActiviti
return data;
}
std::vector<CDActivities> CDActivitiesTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -30,10 +30,9 @@ private:
std::vector<CDActivities> entries;
public:
void LoadValuesFromDatabase();
CDActivitiesTable();
// Queries the table with a custom "where" clause
std::vector<CDActivities> Query(std::function<bool(CDActivities)> predicate);
const std::vector<CDActivities>& GetEntries() const { return this->entries; }
std::vector<CDActivities> GetEntries(void) const;
};

View File

@@ -1,6 +1,6 @@
#include "CDActivityRewardsTable.h"
void CDActivityRewardsTable::LoadValuesFromDatabase() {
CDActivityRewardsTable::CDActivityRewardsTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -43,3 +43,8 @@ std::vector<CDActivityRewards> CDActivityRewardsTable::Query(std::function<bool(
return data;
}
std::vector<CDActivityRewards> CDActivityRewardsTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -18,10 +18,10 @@ private:
std::vector<CDActivityRewards> entries;
public:
void LoadValuesFromDatabase();
CDActivityRewardsTable();
// Queries the table with a custom "where" clause
std::vector<CDActivityRewards> Query(std::function<bool(CDActivityRewards)> predicate);
std::vector<CDActivityRewards> GetEntries() const;
std::vector<CDActivityRewards> GetEntries(void) const;
};

View File

@@ -1,112 +1,56 @@
#include "CDAnimationsTable.h"
#include "GeneralUtils.h"
#include "Game.h"
CDAnimationsTable::CDAnimationsTable(void) {
void CDAnimationsTable::LoadValuesFromDatabase() {
// First, get the size of the table
unsigned int size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM Animations");
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 Animations");
while (!tableData.eof()) {
std::string animation_type = tableData.getStringField("animation_type", "");
DluAssert(!animation_type.empty());
AnimationGroupID animationGroupID = tableData.getIntField("animationGroupID", -1);
DluAssert(animationGroupID != -1);
CDAnimation entry;
CDAnimations entry;
entry.animationGroupID = tableData.getIntField("animationGroupID", -1);
entry.animation_type = tableData.getStringField("animation_type", "");
entry.animation_name = tableData.getStringField("animation_name", "");
entry.chance_to_play = tableData.getFloatField("chance_to_play", 1.0f);
UNUSED_COLUMN(entry.min_loops = tableData.getIntField("min_loops", 0);)
UNUSED_COLUMN(entry.max_loops = tableData.getIntField("max_loops", 0);)
entry.animation_length = tableData.getFloatField("animation_length", 0.0f);
UNUSED_COLUMN(entry.hideEquip = tableData.getIntField("hideEquip", 0) == 1;)
UNUSED_COLUMN(entry.ignoreUpperBody = tableData.getIntField("ignoreUpperBody", 0) == 1;)
UNUSED_COLUMN(entry.restartable = tableData.getIntField("restartable", 0) == 1;)
UNUSED_COLUMN(entry.face_animation_name = tableData.getStringField("face_animation_name", "");)
UNUSED_COLUMN(entry.priority = tableData.getFloatField("priority", 0.0f);)
UNUSED_COLUMN(entry.blendTime = tableData.getFloatField("blendTime", 0.0f);)
entry.chance_to_play = tableData.getFloatField("chance_to_play", -1.0f);
entry.min_loops = tableData.getIntField("min_loops", -1);
entry.max_loops = tableData.getIntField("max_loops", -1);
entry.animation_length = tableData.getFloatField("animation_length", -1.0f);
entry.hideEquip = tableData.getIntField("hideEquip", -1) == 1 ? true : false;
entry.ignoreUpperBody = tableData.getIntField("ignoreUpperBody", -1) == 1 ? true : false;
entry.restartable = tableData.getIntField("restartable", -1) == 1 ? true : false;
entry.face_animation_name = tableData.getStringField("face_animation_name", "");
entry.priority = tableData.getFloatField("priority", -1.0f);
entry.blendTime = tableData.getFloatField("blendTime", -1.0f);
this->animations[CDAnimationKey(animation_type, animationGroupID)].push_back(entry);
this->entries.push_back(entry);
tableData.nextRow();
}
tableData.finalize();
}
bool CDAnimationsTable::CacheData(CppSQLite3Statement& queryToCache) {
auto tableData = queryToCache.execQuery();
// If we received a bad lookup, cache it anyways so we do not run the query again.
if (tableData.eof()) return false;
std::vector<CDAnimations> CDAnimationsTable::Query(std::function<bool(CDAnimations)> predicate) {
do {
std::string animation_type = tableData.getStringField("animation_type", "");
DluAssert(!animation_type.empty());
AnimationGroupID animationGroupID = tableData.getIntField("animationGroupID", -1);
DluAssert(animationGroupID != -1);
std::vector<CDAnimations> data = cpplinq::from(this->entries)
>> cpplinq::where(predicate)
>> cpplinq::to_vector();
CDAnimation entry;
entry.animation_name = tableData.getStringField("animation_name", "");
entry.chance_to_play = tableData.getFloatField("chance_to_play", 1.0f);
UNUSED_COLUMN(entry.min_loops = tableData.getIntField("min_loops", 0);)
UNUSED_COLUMN(entry.max_loops = tableData.getIntField("max_loops", 0);)
entry.animation_length = tableData.getFloatField("animation_length", 0.0f);
UNUSED_COLUMN(entry.hideEquip = tableData.getIntField("hideEquip", 0) == 1;)
UNUSED_COLUMN(entry.ignoreUpperBody = tableData.getIntField("ignoreUpperBody", 0) == 1;)
UNUSED_COLUMN(entry.restartable = tableData.getIntField("restartable", 0) == 1;)
UNUSED_COLUMN(entry.face_animation_name = tableData.getStringField("face_animation_name", "");)
UNUSED_COLUMN(entry.priority = tableData.getFloatField("priority", 0.0f);)
UNUSED_COLUMN(entry.blendTime = tableData.getFloatField("blendTime", 0.0f);)
this->animations[CDAnimationKey(animation_type, animationGroupID)].push_back(entry);
tableData.nextRow();
} while (!tableData.eof());
tableData.finalize();
return true;
return data;
}
void CDAnimationsTable::CacheAnimations(const CDAnimationKey animationKey) {
auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM Animations WHERE animationGroupID = ? and animation_type = ?");
query.bind(1, static_cast<int32_t>(animationKey.second));
query.bind(2, animationKey.first.c_str());
// If we received a bad lookup, cache it anyways so we do not run the query again.
if (!CacheData(query)) {
this->animations[animationKey];
}
std::vector<CDAnimations> CDAnimationsTable::GetEntries(void) const {
return this->entries;
}
void CDAnimationsTable::CacheAnimationGroup(AnimationGroupID animationGroupID) {
auto animationEntryCached = this->animations.find(CDAnimationKey("", animationGroupID));
if (animationEntryCached != this->animations.end()) {
return;
}
auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM Animations WHERE animationGroupID = ?");
query.bind(1, static_cast<int32_t>(animationGroupID));
// Cache the query so we don't run the query again.
CacheData(query);
this->animations[CDAnimationKey("", animationGroupID)];
}
CDAnimationLookupResult CDAnimationsTable::GetAnimation(const AnimationID& animationType, const std::string& previousAnimationName, const AnimationGroupID animationGroupID) {
CDAnimationKey animationKey(animationType, animationGroupID);
auto animationEntryCached = this->animations.find(animationKey);
if (animationEntryCached == this->animations.end()) {
this->CacheAnimations(animationKey);
}
auto animationEntry = this->animations.find(animationKey);
// If we have only one animation, return it regardless of the chance to play.
if (animationEntry->second.size() == 1) {
return CDAnimationLookupResult(animationEntry->second.front());
}
auto randomAnimation = GeneralUtils::GenerateRandomNumber<float>(0, 1);
for (auto& animationEntry : animationEntry->second) {
randomAnimation -= animationEntry.chance_to_play;
// This is how the client gets the random animation.
if (animationEntry.animation_name != previousAnimationName && randomAnimation <= 0.0f) return CDAnimationLookupResult(animationEntry);
}
return CDAnimationLookupResult();
}

View File

@@ -1,67 +1,33 @@
#pragma once
// Custom Classes
#include "CDTable.h"
#include <list>
struct CDAnimation {
// unsigned int animationGroupID;
// std::string animation_type;
// The above two are a pair to represent a primary key in the map.
struct CDAnimations {
unsigned int animationGroupID; //!< The animation group ID
std::string animation_type; //!< The animation type
std::string animation_name; //!< The animation name
float chance_to_play; //!< The chance to play the animation
UNUSED_COLUMN(unsigned int min_loops;) //!< The minimum number of loops
UNUSED_COLUMN(unsigned int max_loops;) //!< The maximum number of loops
unsigned int min_loops; //!< The minimum number of loops
unsigned int max_loops; //!< The maximum number of loops
float animation_length; //!< The animation length
UNUSED_COLUMN(bool hideEquip;) //!< Whether or not to hide the equip
UNUSED_COLUMN(bool ignoreUpperBody;) //!< Whether or not to ignore the upper body
UNUSED_COLUMN(bool restartable;) //!< Whether or not the animation is restartable
UNUSED_COLUMN(std::string face_animation_name;) //!< The face animation name
UNUSED_COLUMN(float priority;) //!< The priority
UNUSED_COLUMN(float blendTime;) //!< The blend time
bool hideEquip; //!< Whether or not to hide the equip
bool ignoreUpperBody; //!< Whether or not to ignore the upper body
bool restartable; //!< Whether or not the animation is restartable
std::string face_animation_name; //!< The face animation name
float priority; //!< The priority
float blendTime; //!< The blend time
};
typedef LookupResult<CDAnimation> CDAnimationLookupResult;
class CDAnimationsTable : public CDTable<CDAnimationsTable> {
typedef int32_t AnimationGroupID;
typedef std::string AnimationID;
typedef std::pair<std::string, AnimationGroupID> CDAnimationKey;
public:
void LoadValuesFromDatabase();
/**
* Given an animationType and the previousAnimationName played, return the next animationType to play.
* If there are more than 1 animationTypes that can be played, one is selected at random but also does not allow
* the previousAnimationName to be played twice.
*
* @param animationType The animationID to lookup
* @param previousAnimationName The previously played animation
* @param animationGroupID The animationGroupID to lookup
* @return CDAnimationLookupResult
*/
[[nodiscard]] CDAnimationLookupResult GetAnimation(const AnimationID& animationType, const std::string& previousAnimationName, const AnimationGroupID animationGroupID);
/**
* Cache a full AnimationGroup by its ID.
*/
void CacheAnimationGroup(AnimationGroupID animationGroupID);
private:
std::vector<CDAnimations> entries;
/**
* Cache all animations given a premade key
*/
void CacheAnimations(const CDAnimationKey animationKey);
public:
CDAnimationsTable();
// Queries the table with a custom "where" clause
std::vector<CDAnimations> Query(std::function<bool(CDAnimations)> predicate);
/**
* Run the query responsible for caching the data.
* @param queryToCache
* @return true
* @return false
*/
bool CacheData(CppSQLite3Statement& queryToCache);
/**
* Each animation is key'd by its animationName and its animationGroupID. Each
* animation has a possible list of animations. This is because there can be animations have a percent chance to play so one is selected at random.
*/
std::map<CDAnimationKey, std::list<CDAnimation>> animations;
std::vector<CDAnimations> GetEntries(void) const;
};

View File

@@ -1,30 +1,26 @@
#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() {
CDBehaviorParameterTable::CDBehaviorParameterTable(void) {
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM BehaviorParameter");
uint32_t uniqueParameterId = 0;
uint64_t hash = 0;
while (!tableData.eof()) {
uint32_t behaviorID = tableData.getIntField("behaviorID", -1);
CDBehaviorParameter entry;
entry.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;
entry.parameterID = parameter;
} else {
parameterId = m_ParametersList.insert(std::make_pair(candidateStringToAdd, m_ParametersList.size())).first->second;
entry.parameterID = m_ParametersList.insert(std::make_pair(candidateStringToAdd, uniqueParameterId)).first;
uniqueParameterId++;
}
uint64_t hash = GetKey(behaviorID, parameterId);
float value = tableData.getFloatField("value", -1.0f);
hash = entry.behaviorID;
hash = (hash << 31U) | entry.parameterID->second;
entry.value = tableData.getFloatField("value", -1.0f);
m_Entries.insert(std::make_pair(hash, value));
m_Entries.insert(std::make_pair(hash, entry));
tableData.nextRow();
}
@@ -34,22 +30,27 @@ void CDBehaviorParameterTable::LoadValuesFromDatabase() {
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);
uint64_t hash = behaviorID;
hash = (hash << 31U) | parameterID->second;
// Search for specific parameter
auto it = m_Entries.find(hash);
return it != m_Entries.end() ? it->second : defaultValue;
const auto& it = m_Entries.find(hash);
return it != m_Entries.end() ? it->second.value : 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);
uint64_t hash;
for (auto& parameterCandidate : m_ParametersList) {
hash = (hashBase << 31U) | parameterCandidate.second;
auto infoCandidate = m_Entries.find(hash);
if (infoCandidate != m_Entries.end()) {
returnInfo.insert(std::make_pair(parameterString, infoCandidate->second));
returnInfo.insert(std::make_pair(infoCandidate->second.parameterID->first, infoCandidate->second.value));
}
}
return returnInfo;
}

View File

@@ -5,15 +5,18 @@
#include <unordered_map>
#include <unordered_set>
struct CDBehaviorParameter {
unsigned int behaviorID; //!< The Behavior ID
std::unordered_map<std::string, uint32_t>::iterator parameterID; //!< The Parameter ID
float value; //!< The value of the behavior template
};
class CDBehaviorParameterTable : public CDTable<CDBehaviorParameterTable> {
private:
typedef uint64_t BehaviorParameterHash;
typedef float BehaviorParameterValue;
std::unordered_map<BehaviorParameterHash, BehaviorParameterValue> m_Entries;
std::unordered_map<uint64_t, CDBehaviorParameter> m_Entries;
std::unordered_map<std::string, uint32_t> m_ParametersList;
public:
void LoadValuesFromDatabase();
CDBehaviorParameterTable();
float GetValue(const uint32_t behaviorID, const std::string& name, const float defaultValue = 0);
std::map<std::string, float> GetParametersByBehaviorID(uint32_t behaviorID);

View File

@@ -1,6 +1,6 @@
#include "CDBehaviorTemplateTable.h"
void CDBehaviorTemplateTable::LoadValuesFromDatabase() {
CDBehaviorTemplateTable::CDBehaviorTemplateTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -48,7 +48,7 @@ std::vector<CDBehaviorTemplate> CDBehaviorTemplateTable::Query(std::function<boo
return data;
}
const std::vector<CDBehaviorTemplate>& CDBehaviorTemplateTable::GetEntries() const {
std::vector<CDBehaviorTemplate> CDBehaviorTemplateTable::GetEntries(void) const {
return this->entries;
}
@@ -64,3 +64,4 @@ const CDBehaviorTemplate CDBehaviorTemplateTable::GetByBehaviorID(uint32_t behav
return entry->second;
}
}

View File

@@ -19,12 +19,11 @@ private:
std::unordered_map<uint32_t, CDBehaviorTemplate> entriesMappedByBehaviorID;
std::unordered_set<std::string> m_EffectHandles;
public:
void LoadValuesFromDatabase();
CDBehaviorTemplateTable();
// Queries the table with a custom "where" clause
std::vector<CDBehaviorTemplate> Query(std::function<bool(CDBehaviorTemplate)> predicate);
const std::vector<CDBehaviorTemplate>& GetEntries(void) const;
std::vector<CDBehaviorTemplate> GetEntries(void) const;
const CDBehaviorTemplate GetByBehaviorID(uint32_t behaviorID);
};

View File

@@ -1,6 +1,6 @@
#include "CDBrickIDTableTable.h"
void CDBrickIDTableTable::LoadValuesFromDatabase() {
CDBrickIDTableTable::CDBrickIDTableTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -39,7 +39,7 @@ std::vector<CDBrickIDTable> CDBrickIDTableTable::Query(std::function<bool(CDBric
return data;
}
const std::vector<CDBrickIDTable>& CDBrickIDTableTable::GetEntries() const {
std::vector<CDBrickIDTable> CDBrickIDTableTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -21,9 +21,9 @@ private:
std::vector<CDBrickIDTable> entries;
public:
void LoadValuesFromDatabase();
CDBrickIDTableTable();
// Queries the table with a custom "where" clause
std::vector<CDBrickIDTable> Query(std::function<bool(CDBrickIDTable)> predicate);
const std::vector<CDBrickIDTable>& GetEntries() const;
std::vector<CDBrickIDTable> GetEntries(void) const;
};

View File

@@ -1,7 +1,25 @@
#include "CDComponentsRegistryTable.h"
#include "eReplicaComponentType.h"
void CDComponentsRegistryTable::LoadValuesFromDatabase() {
#define CDCLIENT_CACHE_ALL
CDComponentsRegistryTable::CDComponentsRegistryTable(void) {
#ifdef CDCLIENT_CACHE_ALL
// First, get the size of the table
unsigned int size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM ComponentsRegistry");
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 ComponentsRegistry");
while (!tableData.eof()) {
@@ -11,42 +29,65 @@ void CDComponentsRegistryTable::LoadValuesFromDatabase() {
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();
#endif
}
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;
const auto& iter = this->mappedEntries.find(((uint64_t)componentType) << 32 | ((uint64_t)id));
if (iter == this->mappedEntries.end()) {
return defaultValue;
}
// 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));
return iter->second;
auto tableData = query.execQuery();
#ifndef CDCLIENT_CACHE_ALL
// Now get the data
std::stringstream query;
query << "SELECT * FROM ComponentsRegistry WHERE id = " << std::to_string(id);
auto tableData = CDClientDatabase::ExecuteQuery(query.str());
while (!tableData.eof()) {
CDComponentsRegistry entry;
entry.id = tableData.getIntField("id", -1);
entry.component_type = static_cast<eReplicaComponentType>(tableData.getIntField("component_type", 0));
entry.component_type = tableData.getIntField("component_type", -1);
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->entries.push_back(entry);
//Darwin's stuff:
const auto& it = this->mappedEntries.find(entry.id);
if (it != mappedEntries.end()) {
const auto& iter = it->second.find(entry.component_type);
if (iter == it->second.end()) {
it->second.insert(std::make_pair(entry.component_type, entry.component_id));
}
} else {
std::map<unsigned int, unsigned int> map;
map.insert(std::make_pair(entry.component_type, entry.component_id));
this->mappedEntries.insert(std::make_pair(entry.id, map));
}
tableData.nextRow();
}
mappedEntries.insert_or_assign(id, 0);
tableData.finalize();
auto iter = this->mappedEntries.find(((uint64_t)componentType) << 32 | ((uint64_t)id));
const auto& it2 = this->mappedEntries.find(id);
if (it2 != mappedEntries.end()) {
const auto& iter = it2->second.find(componentType);
if (iter != it2->second.end()) {
return iter->second;
}
}
return iter == this->mappedEntries.end() ? defaultValue : iter->second;
return defaultValue;
#endif
}

View File

@@ -13,9 +13,9 @@ struct CDComponentsRegistry {
class CDComponentsRegistryTable : public CDTable<CDComponentsRegistryTable> {
private:
std::unordered_map<uint64_t, uint32_t> mappedEntries; //id, component_type, component_id
std::map<uint64_t, uint32_t> mappedEntries; //id, component_type, component_id
public:
void LoadValuesFromDatabase();
CDComponentsRegistryTable();
int32_t GetByIDAndType(uint32_t id, eReplicaComponentType componentType, int32_t defaultValue = 0);
};

View File

@@ -1,7 +1,7 @@
#include "CDCurrencyTableTable.h"
//! Constructor
void CDCurrencyTableTable::LoadValuesFromDatabase() {
CDCurrencyTableTable::CDCurrencyTableTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -43,7 +43,7 @@ std::vector<CDCurrencyTable> CDCurrencyTableTable::Query(std::function<bool(CDCu
return data;
}
const std::vector<CDCurrencyTable>& CDCurrencyTableTable::GetEntries() const {
std::vector<CDCurrencyTable> CDCurrencyTableTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -23,9 +23,9 @@ private:
std::vector<CDCurrencyTable> entries;
public:
void LoadValuesFromDatabase();
CDCurrencyTableTable();
// Queries the table with a custom "where" clause
std::vector<CDCurrencyTable> Query(std::function<bool(CDCurrencyTable)> predicate);
const std::vector<CDCurrencyTable>& GetEntries() const;
std::vector<CDCurrencyTable> GetEntries(void) const;
};

View File

@@ -1,6 +1,8 @@
#include "CDDestructibleComponentTable.h"
void CDDestructibleComponentTable::LoadValuesFromDatabase() {
//! Constructor
CDDestructibleComponentTable::CDDestructibleComponentTable(void) {
// First, get the size of the table
unsigned int size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM DestructibleComponent");
@@ -50,7 +52,7 @@ std::vector<CDDestructibleComponent> CDDestructibleComponentTable::Query(std::fu
return data;
}
const std::vector<CDDestructibleComponent>& CDDestructibleComponentTable::GetEntries() const {
std::vector<CDDestructibleComponent> CDDestructibleComponentTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -25,9 +25,9 @@ private:
std::vector<CDDestructibleComponent> entries;
public:
void LoadValuesFromDatabase();
CDDestructibleComponentTable();
// Queries the table with a custom "where" clause
std::vector<CDDestructibleComponent> Query(std::function<bool(CDDestructibleComponent)> predicate);
const std::vector<CDDestructibleComponent>& GetEntries(void) const;
std::vector<CDDestructibleComponent> GetEntries(void) const;
};

View File

@@ -1,26 +1,40 @@
#include "CDEmoteTable.h"
void CDEmoteTableTable::LoadValuesFromDatabase() {
//! Constructor
CDEmoteTableTable::CDEmoteTableTable(void) {
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", "");
CDEmoteTable* entry = new CDEmoteTable();
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));
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;
//! Destructor
CDEmoteTableTable::~CDEmoteTableTable(void) {
for (auto e : entries) {
if (e.second) delete e.second;
}
entries.clear();
}
CDEmoteTable* CDEmoteTableTable::GetEmote(int id) {
for (auto e : entries) {
if (e.first == id) return e.second;
}
return nullptr;
}

View File

@@ -28,10 +28,11 @@ struct CDEmoteTable {
class CDEmoteTableTable : public CDTable<CDEmoteTableTable> {
private:
std::map<int, CDEmoteTable> entries;
std::map<int, CDEmoteTable*> entries;
public:
void LoadValuesFromDatabase();
CDEmoteTableTable();
~CDEmoteTableTable();
// Returns an emote by ID
CDEmoteTable* GetEmote(int id);
};

View File

@@ -1,6 +1,7 @@
#include "CDFeatureGatingTable.h"
void CDFeatureGatingTable::LoadValuesFromDatabase() {
//! Constructor
CDFeatureGatingTable::CDFeatureGatingTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -52,7 +53,7 @@ bool CDFeatureGatingTable::FeatureUnlocked(const std::string& feature) const {
return false;
}
const std::vector<CDFeatureGating>& CDFeatureGatingTable::GetEntries() const {
std::vector<CDFeatureGating> CDFeatureGatingTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -16,12 +16,11 @@ private:
std::vector<CDFeatureGating> entries;
public:
void LoadValuesFromDatabase();
CDFeatureGatingTable();
// Queries the table with a custom "where" clause
std::vector<CDFeatureGating> Query(std::function<bool(CDFeatureGating)> predicate);
bool FeatureUnlocked(const std::string& feature) const;
const std::vector<CDFeatureGating>& GetEntries(void) const;
std::vector<CDFeatureGating> GetEntries(void) const;
};

View File

@@ -1,6 +1,7 @@
#include "CDInventoryComponentTable.h"
void CDInventoryComponentTable::LoadValuesFromDatabase() {
//! Constructor
CDInventoryComponentTable::CDInventoryComponentTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -41,7 +42,7 @@ std::vector<CDInventoryComponent> CDInventoryComponentTable::Query(std::function
return data;
}
const std::vector<CDInventoryComponent>& CDInventoryComponentTable::GetEntries() const {
std::vector<CDInventoryComponent> CDInventoryComponentTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -15,9 +15,9 @@ private:
std::vector<CDInventoryComponent> entries;
public:
void LoadValuesFromDatabase();
CDInventoryComponentTable();
// Queries the table with a custom "where" clause
std::vector<CDInventoryComponent> Query(std::function<bool(CDInventoryComponent)> predicate);
const std::vector<CDInventoryComponent>& GetEntries() const;
std::vector<CDInventoryComponent> GetEntries(void) const;
};

View File

@@ -3,7 +3,11 @@
CDItemComponent CDItemComponentTable::Default = {};
void CDItemComponentTable::LoadValuesFromDatabase() {
//! Constructor
CDItemComponentTable::CDItemComponentTable(void) {
Default = CDItemComponent();
#ifdef CDCLIENT_CACHE_ALL
// First, get the size of the table
unsigned int size = 0;
auto tableSize = CDClientDatabase::ExecuteQuery("SELECT COUNT(*) FROM ItemComponent");
@@ -51,13 +55,13 @@ void CDItemComponentTable::LoadValuesFromDatabase() {
entry.currencyLOT = tableData.getIntField("currencyLOT", -1);
entry.altCurrencyCost = tableData.getIntField("altCurrencyCost", -1);
entry.subItems = tableData.getStringField("subItems", "");
UNUSED_COLUMN(entry.audioEventUse = tableData.getStringField("audioEventUse", ""));
entry.audioEventUse = tableData.getStringField("audioEventUse", "");
entry.noEquipAnimation = tableData.getIntField("noEquipAnimation", -1) == 1 ? true : false;
entry.commendationLOT = tableData.getIntField("commendationLOT", -1);
entry.commendationCost = tableData.getIntField("commendationCost", -1);
UNUSED_COLUMN(entry.audioEquipMetaEventSet = tableData.getStringField("audioEquipMetaEventSet", ""));
entry.audioEquipMetaEventSet = tableData.getStringField("audioEquipMetaEventSet", "");
entry.currencyCosts = tableData.getStringField("currencyCosts", "");
UNUSED_COLUMN(entry.ingredientInfo = tableData.getStringField("ingredientInfo", ""));
entry.ingredientInfo = tableData.getStringField("ingredientInfo", "");
entry.locStatus = tableData.getIntField("locStatus", -1);
entry.forgeType = tableData.getIntField("forgeType", -1);
entry.SellMultiplier = tableData.getFloatField("SellMultiplier", -1.0f);
@@ -67,6 +71,7 @@ void CDItemComponentTable::LoadValuesFromDatabase() {
}
tableData.finalize();
#endif
}
const CDItemComponent& CDItemComponentTable::GetItemComponentByID(unsigned int skillID) {
@@ -75,10 +80,12 @@ const CDItemComponent& CDItemComponentTable::GetItemComponentByID(unsigned int s
return it->second;
}
auto query = CDClientDatabase::CreatePreppedStmt("SELECT * FROM ItemComponent WHERE id = ?;");
query.bind(1, static_cast<int32_t>(skillID));
#ifndef CDCLIENT_CACHE_ALL
std::stringstream query;
auto tableData = query.execQuery();
query << "SELECT * FROM ItemComponent WHERE id = " << std::to_string(skillID);
auto tableData = CDClientDatabase::ExecuteQuery(query.str());
if (tableData.eof()) {
entries.insert(std::make_pair(skillID, Default));
return Default;
@@ -137,6 +144,7 @@ const CDItemComponent& CDItemComponentTable::GetItemComponentByID(unsigned int s
if (it2 != this->entries.end()) {
return it2->second;
}
#endif
return Default;
}

View File

@@ -54,7 +54,7 @@ private:
std::map<unsigned int, CDItemComponent> entries;
public:
void LoadValuesFromDatabase();
CDItemComponentTable();
static std::map<LOT, uint32_t> ParseCraftingCurrencies(const CDItemComponent& itemComponent);
// Gets an entry by ID

View File

@@ -1,6 +1,7 @@
#include "CDItemSetSkillsTable.h"
void CDItemSetSkillsTable::LoadValuesFromDatabase() {
//! Constructor
CDItemSetSkillsTable::CDItemSetSkillsTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -40,7 +41,7 @@ std::vector<CDItemSetSkills> CDItemSetSkillsTable::Query(std::function<bool(CDIt
return data;
}
const std::vector<CDItemSetSkills>& CDItemSetSkillsTable::GetEntries() const {
std::vector<CDItemSetSkills> CDItemSetSkillsTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -14,11 +14,11 @@ private:
std::vector<CDItemSetSkills> entries;
public:
void LoadValuesFromDatabase();
CDItemSetSkillsTable();
// Queries the table with a custom "where" clause
std::vector<CDItemSetSkills> Query(std::function<bool(CDItemSetSkills)> predicate);
const std::vector<CDItemSetSkills>& GetEntries() const;
std::vector<CDItemSetSkills> GetEntries(void) const;
std::vector<CDItemSetSkills> GetBySkillID(unsigned int SkillSetID);
};

View File

@@ -1,6 +1,7 @@
#include "CDItemSetsTable.h"
void CDItemSetsTable::LoadValuesFromDatabase() {
//! Constructor
CDItemSetsTable::CDItemSetsTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -52,7 +53,7 @@ std::vector<CDItemSets> CDItemSetsTable::Query(std::function<bool(CDItemSets)> p
return data;
}
const std::vector<CDItemSets>& CDItemSetsTable::GetEntries() const {
std::vector<CDItemSets> CDItemSetsTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -26,10 +26,10 @@ private:
std::vector<CDItemSets> entries;
public:
void LoadValuesFromDatabase();
CDItemSetsTable();
// Queries the table with a custom "where" clause
std::vector<CDItemSets> Query(std::function<bool(CDItemSets)> predicate);
const std::vector<CDItemSets>& GetEntries(void) const;
std::vector<CDItemSets> GetEntries(void) const;
};

View File

@@ -1,6 +1,7 @@
#include "CDLevelProgressionLookupTable.h"
void CDLevelProgressionLookupTable::LoadValuesFromDatabase() {
//! Constructor
CDLevelProgressionLookupTable::CDLevelProgressionLookupTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -31,6 +32,7 @@ void CDLevelProgressionLookupTable::LoadValuesFromDatabase() {
tableData.finalize();
}
//! Queries the table with a custom "where" clause
std::vector<CDLevelProgressionLookup> CDLevelProgressionLookupTable::Query(std::function<bool(CDLevelProgressionLookup)> predicate) {
std::vector<CDLevelProgressionLookup> data = cpplinq::from(this->entries)
@@ -40,7 +42,8 @@ std::vector<CDLevelProgressionLookup> CDLevelProgressionLookupTable::Query(std::
return data;
}
const std::vector<CDLevelProgressionLookup>& CDLevelProgressionLookupTable::GetEntries() const {
//! Gets all the entries in the table
std::vector<CDLevelProgressionLookup> CDLevelProgressionLookupTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -14,10 +14,10 @@ private:
std::vector<CDLevelProgressionLookup> entries;
public:
void LoadValuesFromDatabase();
CDLevelProgressionLookupTable();
// Queries the table with a custom "where" clause
std::vector<CDLevelProgressionLookup> Query(std::function<bool(CDLevelProgressionLookup)> predicate);
const std::vector<CDLevelProgressionLookup>& GetEntries() const;
// Gets all the entries in the table
std::vector<CDLevelProgressionLookup> GetEntries(void) const;
};

View File

@@ -1,19 +1,7 @@
#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() {
//! Constructor
CDLootMatrixTable::CDLootMatrixTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -24,6 +12,8 @@ void CDLootMatrixTable::LoadValuesFromDatabase() {
tableSize.nextRow();
}
tableSize.finalize();
// Reserve the size
this->entries.reserve(size);
@@ -31,28 +21,33 @@ void CDLootMatrixTable::LoadValuesFromDatabase() {
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM LootMatrix");
while (!tableData.eof()) {
CDLootMatrix entry;
uint32_t lootMatrixIndex = tableData.getIntField("LootMatrixIndex", -1);
entry.LootMatrixIndex = tableData.getIntField("LootMatrixIndex", -1);
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.id = tableData.getIntField("id", -1);
entry.flagID = tableData.getIntField("flagID", -1);
UNUSED(entry.gate_version = tableData.getStringField("gate_version", ""));
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));
this->entries.push_back(entry);
tableData.nextRow();
}
return this->entries[matrixId];
tableData.finalize();
}
std::vector<CDLootMatrix> CDLootMatrixTable::Query(std::function<bool(CDLootMatrix)> predicate) {
std::vector<CDLootMatrix> data = cpplinq::from(this->entries)
>> cpplinq::where(predicate)
>> cpplinq::to_vector();
return data;
}
const std::vector<CDLootMatrix>& CDLootMatrixTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -4,26 +4,26 @@
#include "CDTable.h"
struct CDLootMatrix {
unsigned int LootMatrixIndex; //!< The Loot Matrix Index
unsigned int LootTableIndex; //!< The Loot Table Index
unsigned int RarityTableIndex; //!< The Rarity Table Index
float percent; //!< The percent that this matrix is used?
unsigned int minToDrop; //!< The minimum amount of loot from this matrix to drop
unsigned int maxToDrop; //!< The maximum amount of loot from this matrix to drop
unsigned int id; //!< The ID of the Loot Matrix
unsigned int flagID; //!< ???
UNUSED(std::string gate_version); //!< The Gate Version
};
typedef uint32_t LootMatrixIndex;
typedef std::vector<CDLootMatrix> LootMatrixEntries;
class CDLootMatrixTable : public CDTable<CDLootMatrixTable> {
public:
void LoadValuesFromDatabase();
// Gets a matrix by ID or inserts a blank one if none existed.
const LootMatrixEntries& GetMatrix(uint32_t matrixId);
private:
CDLootMatrix ReadRow(CppSQLite3Query& tableData) const;
std::unordered_map<LootMatrixIndex, LootMatrixEntries> entries;
std::vector<CDLootMatrix> entries;
public:
CDLootMatrixTable();
// Queries the table with a custom "where" clause
std::vector<CDLootMatrix> Query(std::function<bool(CDLootMatrix)> predicate);
const std::vector<CDLootMatrix>& GetEntries(void) const;
};

View File

@@ -1,15 +1,7 @@
#include "CDLootTableTable.h"
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() {
//! Constructor
CDLootTableTable::CDLootTableTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -20,6 +12,8 @@ void CDLootTableTable::LoadValuesFromDatabase() {
tableSize.nextRow();
}
tableSize.finalize();
// Reserve the size
this->entries.reserve(size);
@@ -27,28 +21,32 @@ void CDLootTableTable::LoadValuesFromDatabase() {
auto tableData = CDClientDatabase::ExecuteQuery("SELECT * FROM LootTable");
while (!tableData.eof()) {
CDLootTable entry;
uint32_t lootTableIndex = tableData.getIntField("LootTableIndex", -1);
entry.id = tableData.getIntField("id", -1);
entry.itemid = tableData.getIntField("itemid", -1);
entry.LootTableIndex = tableData.getIntField("LootTableIndex", -1);
entry.id = tableData.getIntField("id", -1);
entry.MissionDrop = tableData.getIntField("MissionDrop", -1) == 1 ? true : false;
entry.sortPriority = tableData.getIntField("sortPriority", -1);
this->entries[lootTableIndex].push_back(ReadRow(tableData));
tableData.nextRow();
}
}
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));
this->entries.push_back(entry);
tableData.nextRow();
}
return this->entries[tableId];
tableData.finalize();
}
//! Queries the table with a custom "where" clause
std::vector<CDLootTable> CDLootTableTable::Query(std::function<bool(CDLootTable)> predicate) {
std::vector<CDLootTable> data = cpplinq::from(this->entries)
>> cpplinq::where(predicate)
>> cpplinq::to_vector();
return data;
}
//! Gets all the entries in the table
const std::vector<CDLootTable>& CDLootTableTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -6,21 +6,20 @@
struct CDLootTable {
unsigned int itemid; //!< The LOT of the item
unsigned int LootTableIndex; //!< The Loot Table Index
unsigned int id; //!< The ID
bool MissionDrop; //!< Whether or not this loot table is a mission drop
unsigned int sortPriority; //!< The sorting priority
};
typedef uint32_t LootTableIndex;
typedef std::vector<CDLootTable> LootTableEntries;
class CDLootTableTable : public CDTable<CDLootTableTable> {
private:
CDLootTable ReadRow(CppSQLite3Query& tableData) const;
std::unordered_map<LootTableIndex, LootTableEntries> entries;
std::vector<CDLootTable> entries;
public:
void LoadValuesFromDatabase();
CDLootTableTable();
// Queries the table with a custom "where" clause
const LootTableEntries& GetTable(uint32_t tableId);
std::vector<CDLootTable> Query(std::function<bool(CDLootTable)> predicate);
const std::vector<CDLootTable>& GetEntries(void) const;
};

View File

@@ -1,6 +1,7 @@
#include "CDMissionEmailTable.h"
void CDMissionEmailTable::LoadValuesFromDatabase() {
//! Constructor
CDMissionEmailTable::CDMissionEmailTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -47,7 +48,7 @@ std::vector<CDMissionEmail> CDMissionEmailTable::Query(std::function<bool(CDMiss
}
//! Gets all the entries in the table
const std::vector<CDMissionEmail>& CDMissionEmailTable::GetEntries() const {
std::vector<CDMissionEmail> CDMissionEmailTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -20,9 +20,9 @@ private:
std::vector<CDMissionEmail> entries;
public:
void LoadValuesFromDatabase();
CDMissionEmailTable();
// Queries the table with a custom "where" clause
std::vector<CDMissionEmail> Query(std::function<bool(CDMissionEmail)> predicate);
const std::vector<CDMissionEmail>& GetEntries() const;
std::vector<CDMissionEmail> GetEntries(void) const;
};

View File

@@ -1,6 +1,7 @@
#include "CDMissionNPCComponentTable.h"
void CDMissionNPCComponentTable::LoadValuesFromDatabase() {
//! Constructor
CDMissionNPCComponentTable::CDMissionNPCComponentTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -44,7 +45,7 @@ std::vector<CDMissionNPCComponent> CDMissionNPCComponentTable::Query(std::functi
}
//! Gets all the entries in the table
const std::vector<CDMissionNPCComponent>& CDMissionNPCComponentTable::GetEntries() const {
std::vector<CDMissionNPCComponent> CDMissionNPCComponentTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -16,12 +16,12 @@ private:
std::vector<CDMissionNPCComponent> entries;
public:
void LoadValuesFromDatabase();
CDMissionNPCComponentTable();
// Queries the table with a custom "where" clause
std::vector<CDMissionNPCComponent> Query(std::function<bool(CDMissionNPCComponent)> predicate);
// Gets all the entries in the table
const std::vector<CDMissionNPCComponent>& GetEntries() const;
std::vector<CDMissionNPCComponent> GetEntries(void) const;
};

View File

@@ -1,6 +1,7 @@
#include "CDMissionTasksTable.h"
void CDMissionTasksTable::LoadValuesFromDatabase() {
//! Constructor
CDMissionTasksTable::CDMissionTasksTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -55,14 +56,16 @@ std::vector<CDMissionTasks*> CDMissionTasksTable::GetByMissionID(uint32_t missio
for (auto& entry : this->entries) {
if (entry.id == missionID) {
tasks.push_back(&entry);
CDMissionTasks* task = const_cast<CDMissionTasks*>(&entry);
tasks.push_back(task);
}
}
return tasks;
}
const std::vector<CDMissionTasks>& CDMissionTasksTable::GetEntries() const {
const std::vector<CDMissionTasks>& CDMissionTasksTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -24,12 +24,12 @@ private:
std::vector<CDMissionTasks> entries;
public:
void LoadValuesFromDatabase();
CDMissionTasksTable();
// Queries the table with a custom "where" clause
std::vector<CDMissionTasks> Query(std::function<bool(CDMissionTasks)> predicate);
std::vector<CDMissionTasks*> GetByMissionID(uint32_t missionID);
const std::vector<CDMissionTasks>& GetEntries() const;
const std::vector<CDMissionTasks>& GetEntries(void) const;
};

View File

@@ -2,7 +2,8 @@
CDMissions CDMissionsTable::Default = {};
void CDMissionsTable::LoadValuesFromDatabase() {
//! Constructor
CDMissionsTable::CDMissionsTable(void) {
// First, get the size of the table
unsigned int size = 0;

View File

@@ -65,12 +65,12 @@ private:
std::vector<CDMissions> entries;
public:
void LoadValuesFromDatabase();
CDMissionsTable();
// Queries the table with a custom "where" clause
std::vector<CDMissions> Query(std::function<bool(CDMissions)> predicate);
// Gets all the entries in the table
const std::vector<CDMissions>& GetEntries() const;
const std::vector<CDMissions>& GetEntries(void) const;
const CDMissions* GetPtrByMissionID(uint32_t missionID) const;

View File

@@ -1,6 +1,7 @@
#include "CDMovementAIComponentTable.h"
void CDMovementAIComponentTable::LoadValuesFromDatabase() {
//! Constructor
CDMovementAIComponentTable::CDMovementAIComponentTable(void) {
// First, get the size of the table
unsigned int size = 0;
@@ -36,6 +37,7 @@ void CDMovementAIComponentTable::LoadValuesFromDatabase() {
tableData.finalize();
}
//! Queries the table with a custom "where" clause
std::vector<CDMovementAIComponent> CDMovementAIComponentTable::Query(std::function<bool(CDMovementAIComponent)> predicate) {
std::vector<CDMovementAIComponent> data = cpplinq::from(this->entries)
@@ -45,7 +47,8 @@ std::vector<CDMovementAIComponent> CDMovementAIComponentTable::Query(std::functi
return data;
}
const std::vector<CDMovementAIComponent>& CDMovementAIComponentTable::GetEntries(void) const {
//! Gets all the entries in the table
std::vector<CDMovementAIComponent> CDMovementAIComponentTable::GetEntries(void) const {
return this->entries;
}

View File

@@ -19,10 +19,10 @@ private:
std::vector<CDMovementAIComponent> entries;
public:
void LoadValuesFromDatabase();
CDMovementAIComponentTable();
// Queries the table with a custom "where" clause
std::vector<CDMovementAIComponent> Query(std::function<bool(CDMovementAIComponent)> predicate);
// Gets all the entries in the table
const std::vector<CDMovementAIComponent>& GetEntries() const;
std::vector<CDMovementAIComponent> GetEntries(void) const;
};

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