From 366c3db7fe1b50852df5deebe8cbcec3cacc0bc6 Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Sun, 23 Oct 2022 21:54:59 -0500 Subject: [PATCH 1/8] fix reading respawn for older maps (#784) --- dZoneManager/Level.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dZoneManager/Level.cpp b/dZoneManager/Level.cpp index ebfca4cc..f12adc56 100644 --- a/dZoneManager/Level.cpp +++ b/dZoneManager/Level.cpp @@ -266,7 +266,7 @@ void Level::ReadSceneObjectDataChunk(std::ifstream& file, Header& header) { spawnInfo.respawnTime = std::stof(data->GetValueAsString()); } else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms? { - spawnInfo.respawnTime = std::stoi(data->GetValueAsString()) / 1000; + spawnInfo.respawnTime = std::stoul(data->GetValueAsString()) / 1000; } } if (data->GetKey() == u"spawnsGroupOnSmash") { From 8d44f2f5da762b245afcadffbc5c1d6b8ab9fe5d Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Mon, 24 Oct 2022 00:54:21 -0500 Subject: [PATCH 2/8] Add missing property path checks for new zones (#783) Co-authored-by: Jett <55758076+Jettford@users.noreply.github.com> --- dZoneManager/Zone.cpp | 56 ++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/dZoneManager/Zone.cpp b/dZoneManager/Zone.cpp index 9072080a..b670849b 100644 --- a/dZoneManager/Zone.cpp +++ b/dZoneManager/Zone.cpp @@ -403,30 +403,42 @@ void Zone::LoadPath(std::ifstream& file) { BinaryIO::BinaryRead(file, path.property.price); BinaryIO::BinaryRead(file, path.property.rentalTime); BinaryIO::BinaryRead(file, path.property.associatedZone); - uint8_t count1; - BinaryIO::BinaryRead(file, count1); - for (uint8_t i = 0; i < count1; ++i) { - uint16_t character; - BinaryIO::BinaryRead(file, character); - path.property.displayName.push_back(character); + + if (path.pathVersion >= 5) { + uint8_t count1; + BinaryIO::BinaryRead(file, count1); + for (uint8_t i = 0; i < count1; ++i) { + uint16_t character; + BinaryIO::BinaryRead(file, character); + path.property.displayName.push_back(character); + } + uint32_t count2; + BinaryIO::BinaryRead(file, count2); + for (uint8_t i = 0; i < count2; ++i) { + uint16_t character; + BinaryIO::BinaryRead(file, character); + path.property.displayDesc.push_back(character); + } } - uint32_t count2; - BinaryIO::BinaryRead(file, count2); - for (uint8_t i = 0; i < count2; ++i) { - uint16_t character; - BinaryIO::BinaryRead(file, character); - path.property.displayDesc.push_back(character); + + if (path.pathVersion >= 6) { + int32_t unknown1; + BinaryIO::BinaryRead(file, unknown1); + } + + if (path.pathVersion >= 7) { + BinaryIO::BinaryRead(file, path.property.cloneLimit); + BinaryIO::BinaryRead(file, path.property.repMultiplier); + BinaryIO::BinaryRead(file, path.property.rentalTimeUnit); + } + + if (path.pathVersion >= 8) { + BinaryIO::BinaryRead(file, path.property.achievementRequired); + BinaryIO::BinaryRead(file, path.property.playerZoneCoords.x); + BinaryIO::BinaryRead(file, path.property.playerZoneCoords.y); + BinaryIO::BinaryRead(file, path.property.playerZoneCoords.z); + BinaryIO::BinaryRead(file, path.property.maxBuildHeight); } - int32_t unknown1; - BinaryIO::BinaryRead(file, unknown1); - BinaryIO::BinaryRead(file, path.property.cloneLimit); - BinaryIO::BinaryRead(file, path.property.repMultiplier); - BinaryIO::BinaryRead(file, path.property.rentalTimeUnit); - BinaryIO::BinaryRead(file, path.property.achievementRequired); - BinaryIO::BinaryRead(file, path.property.playerZoneCoords.x); - BinaryIO::BinaryRead(file, path.property.playerZoneCoords.y); - BinaryIO::BinaryRead(file, path.property.playerZoneCoords.z); - BinaryIO::BinaryRead(file, path.property.maxBuildHeight); } else if (path.pathType == PathType::Camera) { uint8_t count; BinaryIO::BinaryRead(file, count); From c13937bd1f00f1c747b3ba6614bce884a9cc5f9a Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Mon, 24 Oct 2022 15:20:36 -0700 Subject: [PATCH 3/8] Address Brick-By-Brick builds not properly saving and make migrations automatic (#725) * Properly store BBB in database Store the BBB data in the database as the received SD0 packet as opposed to just the raw lxfml. Addressed several memory leaks as well. * Add Sd0Conversion Add brick by brick conversion commands with 2 parameters to tell the program what to do with the data. Add zlib -> sd0 conversion. Files look good at a glance but should be tested in game to ensure stability. Tests to come. * moving to laptop ignore this commit. I need to move this to my laptop * Add functionality to delete bad models Adds functionality to delete bad models. Models are batched together and deleted in one commit. More testing is needed to ensure data safety. Positive tests on a live database reveal the broken models were truncated and complete ones were kept around successfully. Tests should be done to ensure larger sd0 models are properly saved and not truncated since this command should be able to be run any time. Valgrind tests need to be run as well to ensure no memory leaks exist. * Delete from query change Changed from delete to delete cascade and instead deleting from properties_contents as opposed to ugc. * Address numerous bugs DELETE CASCADE is not a valid SQL command so this was changed to a better delete statement. Added user confirmation before deleting a broken model. Address appending the string model appending bad data, causing excess deletion. Addressed memory leaks with sql::Blob * Error handling for string * Even more proper handling... * Add bounds check for cli command Output a message if a bad command is used. Update MasterServer.cpp * Remove user interference -Add back in mariadb build jobs so i dont nuke others systems - Remove all user interference and consolidate work into one command since 1 depends on the next. * Add comments test Revert "test" This reverts commit fb831f268b7a2f0ccd20595aff64902ab4f4b4ee. * Update CMakeMariaDBLists.txt Test * Improve migration runner Migration runner now runs automatically. - Resolved an issue where extremely large sql queries caused the database to go into an invalid state. - Made migrations run automatically on server start. - Resolved a tiny memory leak in migration runner? (discarded returned pointer) - Moved sd0 migrations of brick models to be run automatically with migration runner. - Created dummy file to tell when brick migrations have been run. * Update README Updated the README to reflect the new server migration state. * Make model deleter actually delete models My complicated sql actually did nothing... Tested that this new SQL properly gets rid of bad data. * Revert "Update CMakeMariaDBLists.txt" This reverts commit 8b859d8529755d7132cef072b2532589c098f683. --- CMakeVariables.txt | 2 +- README.md | 7 +- dCommon/BrickByBrickFix.cpp | 180 +++++++++++++++++++++++++++ dCommon/BrickByBrickFix.h | 26 ++++ dCommon/CMakeLists.txt | 6 +- dDatabase/Database.cpp | 12 ++ dDatabase/Database.h | 2 + dDatabase/MigrationRunner.cpp | 47 ++++--- dGame/dGameMessages/GameMessages.cpp | 106 ++++------------ dMasterServer/MasterServer.cpp | 110 +++++++--------- dWorldServer/WorldServer.cpp | 9 -- migrations/dlu/5_brick_model_sd0.sql | 1 + 12 files changed, 334 insertions(+), 174 deletions(-) create mode 100644 dCommon/BrickByBrickFix.cpp create mode 100644 dCommon/BrickByBrickFix.h create mode 100644 migrations/dlu/5_brick_model_sd0.sql diff --git a/CMakeVariables.txt b/CMakeVariables.txt index 7a8d6b71..e68e826e 100644 --- a/CMakeVariables.txt +++ b/CMakeVariables.txt @@ -15,6 +15,6 @@ __dynamic=1 # __include_backtrace__=1 # Set __include_backtrace__ to 1 to includes the backtrace library for better crashlogs. # __compile_backtrace__=1 -# Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries. +# Set __compile_backtrace__ to 1 to compile the backtrace library instead of using system libraries. __maria_db_connector_compile_jobs__=1 # Set to the number of jobs (make -j equivalent) to compile the mariadbconn files with. diff --git a/README.md b/README.md index de20e7ad..dfae2bab 100644 --- a/README.md +++ b/README.md @@ -204,15 +204,16 @@ certutil -hashfile SHA256 Darkflame Universe utilizes a MySQL/MariaDB database for account and character information. Initial setup can vary drastically based on which operating system or distribution you are running; there are instructions out there for most setups, follow those and come back here when you have a database up and running. -* Create a database for Darkflame Universe to use + +* All that you need to do is create a database to connect to. As long as the server can connect to the database, the schema will always be kept up to date when you start the server. #### Configuration After the server has been built there should be four `ini` files in the build director: `authconfig.ini`, `chatconfig.ini`, `masterconfig.ini`, and `worldconfig.ini`. Go through them and fill in the database credentials and configure other settings if necessary. -#### Setup and Migrations +#### Migrations -Use the command `./MasterServer -m` to setup the tables in the database. The first time this command is run on a database, the tables will be up to date with the most recent version. To update your database tables, run this command again. Multiple invocations will not affect any functionality. +The database is automatically setup and migrated to what it should look like for the latest commit whenever you start the server. #### Verify diff --git a/dCommon/BrickByBrickFix.cpp b/dCommon/BrickByBrickFix.cpp new file mode 100644 index 00000000..f0c4e824 --- /dev/null +++ b/dCommon/BrickByBrickFix.cpp @@ -0,0 +1,180 @@ +#include "BrickByBrickFix.h" + +#include +#include +#include + +#include "tinyxml2.h" + +#include "Database.h" +#include "Game.h" +#include "ZCompression.h" +#include "dLogger.h" + +//! Forward declarations + +std::unique_ptr GetModelsFromDatabase(); +void WriteSd0Magic(char* input, uint32_t chunkSize); +bool CheckSd0Magic(sql::Blob* streamToCheck); + +/** + * @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(); + bool previousCommitValue = Database::GetAutoCommit(); + Database::SetAutoCommit(false); + while (modelsToTruncate->next()) { + std::unique_ptr ugcModelToDelete(Database::CreatePreppedStmt("DELETE FROM ugc WHERE ugc.id = ?;")); + std::unique_ptr pcModelToDelete(Database::CreatePreppedStmt("DELETE FROM properties_contents WHERE ugc_id = ?;")); + std::string completeUncompressedModel{}; + uint32_t chunkCount{}; + uint64_t modelId = modelsToTruncate->getInt(1); + std::unique_ptr modelAsSd0(modelsToTruncate->getBlob(2)); + // Check that header is sd0 by checking for the sd0 magic. + if (CheckSd0Magic(modelAsSd0.get())) { + while (true) { + uint32_t chunkSize{}; + modelAsSd0->read(reinterpret_cast(&chunkSize), sizeof(uint32_t)); // Extract chunk size from istream + + // Check if good here since if at the end of an sd0 file, this will have eof flagged. + if (!modelAsSd0->good()) break; + + std::unique_ptr compressedChunk(new uint8_t[chunkSize]); + for (uint32_t i = 0; i < chunkSize; i++) { + compressedChunk[i] = modelAsSd0->get(); + } + + // Ignore the valgrind warning about uninitialized values. These are discarded later when we know the actual uncompressed size. + std::unique_ptr uncompressedChunk(new uint8_t[MAX_SD0_CHUNK_SIZE]); + int32_t err{}; + int32_t actualUncompressedSize = ZCompression::Decompress( + compressedChunk.get(), chunkSize, uncompressedChunk.get(), MAX_SD0_CHUNK_SIZE, err); + + if (actualUncompressedSize != -1) { + uint32_t previousSize = completeUncompressedModel.size(); + completeUncompressedModel.append((char*)uncompressedChunk.get()); + completeUncompressedModel.resize(previousSize + actualUncompressedSize); + } else { + Game::logger->Log("BrickByBrickFix", "Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, modelId, err); + break; + } + chunkCount++; + } + std::unique_ptr document = std::make_unique(); + if (!document) { + Game::logger->Log("BrickByBrickFix", "Failed to initialize tinyxml document. Aborting."); + return 0; + } + + if (!(document->Parse(completeUncompressedModel.c_str(), completeUncompressedModel.size()) == tinyxml2::XML_SUCCESS)) { + if (completeUncompressedModel.find( + "", + completeUncompressedModel.length() >= 15 ? completeUncompressedModel.length() - 15 : 0) == std::string::npos + ) { + Game::logger->Log("BrickByBrickFix", + "Brick-by-brick model %llu will be deleted!", modelId); + ugcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1)); + pcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1)); + ugcModelToDelete->execute(); + pcModelToDelete->execute(); + modelsTruncated++; + } + } + } else { + Game::logger->Log("BrickByBrickFix", + "Brick-by-brick model %llu will be deleted!", modelId); + ugcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1)); + pcModelToDelete->setInt64(1, modelsToTruncate->getInt64(1)); + ugcModelToDelete->execute(); + pcModelToDelete->execute(); + modelsTruncated++; + } + } + + Database::Commit(); + Database::SetAutoCommit(previousCommitValue); + return modelsTruncated; +} + +/** + * @brief Updates all current models in the database to have the Segmented Data 0 (SD0) format. + * Any models that do not start with zlib and best compression magic will not be updated. + * + * @return The number of models updated to SD0 + */ +uint32_t BrickByBrickFix::UpdateBrickByBrickModelsToSd0() { + uint32_t updatedModels = 0; + auto modelsToUpdate = GetModelsFromDatabase(); + auto previousAutoCommitState = Database::GetAutoCommit(); + Database::SetAutoCommit(false); + std::unique_ptr insertionStatement(Database::CreatePreppedStmt("UPDATE ugc SET lxfml = ? WHERE id = ?;")); + while (modelsToUpdate->next()) { + int64_t modelId = modelsToUpdate->getInt64(1); + std::unique_ptr oldLxfml(modelsToUpdate->getBlob(2)); + // Check if the stored blob starts with zlib magic (0x78 0xDA - best compression of zlib) + // If it does, convert it to sd0. + if (oldLxfml->get() == 0x78 && oldLxfml->get() == 0xDA) { + + // Get and save size of zlib compressed chunk. + oldLxfml->seekg(0, std::ios::end); + uint32_t oldLxfmlSize = static_cast(oldLxfml->tellg()); + oldLxfml->seekg(0); + + // Allocate 9 extra bytes. 5 for sd0 magic, 4 for the only zlib compressed size. + uint32_t oldLxfmlSizeWithHeader = oldLxfmlSize + 9; + std::unique_ptr sd0ConvertedModel(new char[oldLxfmlSizeWithHeader]); + + WriteSd0Magic(sd0ConvertedModel.get(), oldLxfmlSize); + for (uint32_t i = 9; i < oldLxfmlSizeWithHeader; i++) { + sd0ConvertedModel.get()[i] = oldLxfml->get(); + } + + std::string outputString(sd0ConvertedModel.get(), oldLxfmlSizeWithHeader); + std::istringstream outputStringStream(outputString); + + insertionStatement->setBlob(1, static_cast(&outputStringStream)); + insertionStatement->setInt64(2, modelId); + try { + insertionStatement->executeUpdate(); + Game::logger->Log("BrickByBrickFix", "Updated model %i to sd0", modelId); + updatedModels++; + } catch (sql::SQLException exception) { + Game::logger->Log( + "BrickByBrickFix", + "Failed to update model %i. This model should be inspected manually to see why." + "The database error is %s", modelId, exception.what()); + } + } + } + Database::Commit(); + Database::SetAutoCommit(previousAutoCommitState); + return updatedModels; +} + +std::unique_ptr GetModelsFromDatabase() { + std::unique_ptr modelsRawDataQuery(Database::CreatePreppedStmt("SELECT id, lxfml FROM ugc;")); + return std::unique_ptr(modelsRawDataQuery->executeQuery()); +} + +/** + * @brief Writes sd0 magic at the front of a char* + * + * @param input the char* to write at the front of + * @param chunkSize The size of the first chunk to write the size of + */ +void WriteSd0Magic(char* input, uint32_t chunkSize) { + input[0] = 's'; + input[1] = 'd'; + input[2] = '0'; + input[3] = 0x01; + input[4] = 0xFF; + *reinterpret_cast(input + 5) = chunkSize; // Write the integer to the character array +} + +bool CheckSd0Magic(sql::Blob* streamToCheck) { + return streamToCheck->get() == 's' && streamToCheck->get() == 'd' && streamToCheck->get() == '0' && streamToCheck->get() == 0x01 && streamToCheck->get() == 0xFF; +} diff --git a/dCommon/BrickByBrickFix.h b/dCommon/BrickByBrickFix.h new file mode 100644 index 00000000..0c7e314c --- /dev/null +++ b/dCommon/BrickByBrickFix.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +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(); + + /** + * @brief Max size of an inflated sd0 zlib chunk + * + */ + constexpr uint32_t MAX_SD0_CHUNK_SIZE = 1024 * 256; +}; diff --git a/dCommon/CMakeLists.txt b/dCommon/CMakeLists.txt index 18fe582c..cb73bf6a 100644 --- a/dCommon/CMakeLists.txt +++ b/dCommon/CMakeLists.txt @@ -13,13 +13,15 @@ set(DCOMMON_SOURCES "AMFFormat.cpp" "NiQuaternion.cpp" "SHA512.cpp" "Type.cpp" - "ZCompression.cpp") + "ZCompression.cpp" + "BrickByBrickFix.cpp" +) include_directories(${PROJECT_SOURCE_DIR}/dCommon/) add_library(dCommon STATIC ${DCOMMON_SOURCES}) -target_link_libraries(dCommon bcrypt) +target_link_libraries(dCommon bcrypt dDatabase tinyxml2) if (UNIX) find_package(ZLIB REQUIRED) diff --git a/dDatabase/Database.cpp b/dDatabase/Database.cpp index f955bd43..c301cbd9 100644 --- a/dDatabase/Database.cpp +++ b/dDatabase/Database.cpp @@ -83,3 +83,15 @@ sql::PreparedStatement* Database::CreatePreppedStmt(const std::string& query) { void Database::Commit() { Database::con->commit(); } + +bool Database::GetAutoCommit() { + // TODO This should not just access a pointer. A future PR should update this + // to check for null and throw an error if the connection is not valid. + return con->getAutoCommit(); +} + +void Database::SetAutoCommit(bool value) { + // TODO This should not just access a pointer. A future PR should update this + // to check for null and throw an error if the connection is not valid. + Database::con->setAutoCommit(value); +} diff --git a/dDatabase/Database.h b/dDatabase/Database.h index a15ba856..f4d13da3 100644 --- a/dDatabase/Database.h +++ b/dDatabase/Database.h @@ -23,6 +23,8 @@ public: 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; } diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index 30a63896..1eb03887 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -1,5 +1,6 @@ #include "MigrationRunner.h" +#include "BrickByBrickFix.h" #include "GeneralUtils.h" #include @@ -7,13 +8,13 @@ #include 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->executeQuery(); + 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 = ""; Migration checkMigration{}; - + bool runSd0Migrations = false; for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/")) { auto migration = LoadMigration(entry); @@ -25,16 +26,18 @@ void MigrationRunner::RunMigrations() { stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); stmt->setString(1, migration.name); - auto res = stmt->executeQuery(); + 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()); - - finalSQL.append(migration.data); - finalSQL.append('\n'); + if (migration.name == "5_brick_model_sd0.sql") { + runSd0Migrations = true; + } else { + finalSQL.append(migration.data); + } stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); stmt->setString(1, entry); @@ -42,15 +45,31 @@ void MigrationRunner::RunMigrations() { delete stmt; } + if (finalSQL.empty() && !runSd0Migrations) { + Game::logger->Log("MigrationRunner", "Server database is up to date."); + return; + } + if (!finalSQL.empty()) { - try { - auto simpleStatement = Database::CreateStmt(); - simpleStatement->execute(finalSQL); - delete simpleStatement; - } catch (sql::SQLException e) { - Game::logger->Log("MigrationRunner", "Encountered error running migration: %s", e.what()); + auto migration = GeneralUtils::SplitString(static_cast(finalSQL), ';'); + std::unique_ptr simpleStatement(Database::CreateStmt()); + for (auto& query : migration) { + try { + if (query.empty()) continue; + simpleStatement->execute(query); + } 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); + } } Migration MigrationRunner::LoadMigration(std::string path) { @@ -58,8 +77,6 @@ Migration MigrationRunner::LoadMigration(std::string path) { std::ifstream file("./migrations/" + path); if (file.is_open()) { - std::hash hash; - std::string line; std::string total = ""; diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 772cb691..48b2a508 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2401,63 +2401,25 @@ void GameMessages::HandleControlBehaviors(RakNet::BitStream* inStream, Entity* e } void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { - /* - ___ ___ - /\ /\___ _ __ ___ / __\ ___ / \_ __ __ _ __ _ ___ _ __ ___ - / /_/ / _ \ '__/ _ \ /__\/// _ \ / /\ / '__/ _` |/ _` |/ _ \| '_ \/ __| - / __ / __/ | | __/ / \/ \ __/ / /_//| | | (_| | (_| | (_) | | | \__ \ - \/ /_/ \___|_| \___| \_____/\___| /___,' |_| \__,_|\__, |\___/|_| |_|___/ - |___/ - ___ _ - / __\ _____ ____ _ _ __ ___ / \ - /__\/// _ \ \ /\ / / _` | '__/ _ \/ / - / \/ \ __/\ V V / (_| | | | __/\_/ - \_____/\___| \_/\_/ \__,_|_| \___\/ - - <>=======() - (/\___ /|\\ ()==========<>_ - \_/ | \\ //|\ ______/ \) - \_| \\ // | \_/ - \|\/|\_ // /\/ - (oo)\ \_// / - //_/\_\/ / | - @@/ |=\ \ | - \_=\_ \ | - \==\ \|\_ snd - __(\===\( )\ - (((~) __(_/ | - (((~) \ / - ______/ / - '------' - */ - - //First, we have Wincent's clean methods of reading in the data received from the client. LWOOBJID localId; - uint32_t timeTaken; inStream->Read(localId); - uint32_t ld0Size; - inStream->Read(ld0Size); - for (auto i = 0; i < 5; ++i) { - uint8_t c; - inStream->Read(c); - } + uint32_t sd0Size; + inStream->Read(sd0Size); + std::shared_ptr sd0Data(new char[sd0Size]); - uint32_t lxfmlSize; - inStream->Read(lxfmlSize); - uint8_t* inData = static_cast(std::malloc(lxfmlSize)); - - if (inData == nullptr) { + if (sd0Data == nullptr) { return; } - for (uint32_t i = 0; i < lxfmlSize; ++i) { + for (uint32_t i = 0; i < sd0Size; ++i) { uint8_t c; inStream->Read(c); - inData[i] = c; + sd0Data[i] = c; } + uint32_t timeTaken; inStream->Read(timeTaken); /* @@ -2469,6 +2431,8 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent Note, in the live client it'll still display the bricks going out as they're being used, but on relog/world change, they reappear as we didn't take them. + + TODO Apparently the bricks are supposed to be taken via MoveInventoryBatch? */ ////Decompress the SD0 from the client so we can process the lxfml properly @@ -2513,9 +2477,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent auto result = query.execQuery(); - if (result.eof() || result.fieldIsNull(0)) { - return; - } + if (result.eof() || result.fieldIsNull(0)) return; int templateId = result.getIntField(0); @@ -2533,6 +2495,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent propertyId = propertyEntry->getUInt64(1); } + delete propertyEntry; delete propertyLookup; //Insert into ugc: @@ -2543,7 +2506,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent ugcs->setInt(4, 0); //whacky stream biz - std::string s((char*)inData, lxfmlSize); + std::string s(sd0Data.get(), sd0Size); std::istringstream iss(s); std::istream& stream = iss; @@ -2558,14 +2521,14 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent stmt->setUInt64(1, newIDL); stmt->setUInt64(2, propertyId); stmt->setUInt(3, blueprintIDSmall); - stmt->setUInt(4, 14); //14 is the lot the BBB models use - stmt->setDouble(5, 0.0f); //x - stmt->setDouble(6, 0.0f); //y - stmt->setDouble(7, 0.0f); //z - stmt->setDouble(8, 0.0f); - stmt->setDouble(9, 0.0f); - stmt->setDouble(10, 0.0f); - stmt->setDouble(11, 0.0f); + stmt->setUInt(4, 14); // 14 is the lot the BBB models use + stmt->setDouble(5, 0.0f); // x + stmt->setDouble(6, 0.0f); // y + stmt->setDouble(7, 0.0f); // z + stmt->setDouble(8, 0.0f); // rx + stmt->setDouble(9, 0.0f); // ry + stmt->setDouble(10, 0.0f); // rz + stmt->setDouble(11, 0.0f); // rw stmt->execute(); delete stmt; @@ -2591,38 +2554,22 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent //Tell the client their model is saved: (this causes us to actually pop out of our current state): CBITSTREAM; - PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE); + PacketUtils::WriteHeader(bitStream, CLIENT, CLIENT::MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE); bitStream.Write(localId); bitStream.Write(0); bitStream.Write(1); bitStream.Write(blueprintID); - bitStream.Write(lxfmlSize + 9); + bitStream.Write(sd0Size); - //Write a fake sd0 header: - bitStream.Write(0x73); //s - bitStream.Write(0x64); //d - bitStream.Write(0x30); //0 - bitStream.Write(0x01); //1 - bitStream.Write(0xFF); //end magic - - bitStream.Write(lxfmlSize); - - for (size_t i = 0; i < lxfmlSize; ++i) - bitStream.Write(inData[i]); + for (size_t i = 0; i < sd0Size; ++i) { + bitStream.Write(sd0Data[i]); + } SEND_PACKET; - PacketUtils::SavePacket("MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE.bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed()); + // PacketUtils::SavePacket("MSG_CLIENT_BLUEPRINT_SAVE_RESPONSE.bin", (char*)bitStream.GetData(), bitStream.GetNumberOfBytesUsed()); //Now we have to construct this object: - /* - * This needs to be sent as config data, but I don't know how to right now. - 'blueprintid': (9, 1152921508346399522), - 'componentWhitelist': (1, 1), - 'modelType': (1, 2), - 'propertyObjectID': (7, True), - 'userModelID': (9, 1152921510759098799) - */ EntityInfo info; info.lot = 14; @@ -2653,6 +2600,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent //there was an issue with builds not appearing since it was placed above ConstructEntity. PropertyManagementComponent::Instance()->AddModel(newEntity->GetObjectID(), newIDL); } + }); }); }); diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index a692e826..538fb749 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -85,71 +85,51 @@ int main(int argc, char** argv) { Game::logger->SetLogToConsole(bool(std::stoi(config.GetValue("log_to_console")))); Game::logger->SetLogDebugStatements(config.GetValue("log_debug_statements") == "1"); - if (argc > 1 && (strcmp(argv[1], "-m") == 0 || strcmp(argv[1], "--migrations") == 0)) { - //Connect to the MySQL Database - std::string mysql_host = config.GetValue("mysql_host"); - std::string mysql_database = config.GetValue("mysql_database"); - std::string mysql_username = config.GetValue("mysql_username"); - std::string mysql_password = config.GetValue("mysql_password"); + //Connect to the MySQL Database + std::string mysql_host = config.GetValue("mysql_host"); + std::string mysql_database = config.GetValue("mysql_database"); + std::string mysql_username = config.GetValue("mysql_username"); + std::string mysql_password = config.GetValue("mysql_password"); - try { - Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); - } catch (sql::SQLException& ex) { - Game::logger->Log("MasterServer", "Got an error while connecting to the database: %s", ex.what()); - Game::logger->Log("MigrationRunner", "Migrations not run"); - return EXIT_FAILURE; - } - - MigrationRunner::RunMigrations(); - Game::logger->Log("MigrationRunner", "Finished running migrations"); - - return EXIT_SUCCESS; - } else { - - //Check CDClient exists - const std::string cdclient_path = "./res/CDServer.sqlite"; - std::ifstream cdclient_fd(cdclient_path); - if (!cdclient_fd.good()) { - Game::logger->Log("WorldServer", "%s could not be opened", cdclient_path.c_str()); - return EXIT_FAILURE; - } - cdclient_fd.close(); - - //Connect to CDClient - try { - CDClientDatabase::Connect(cdclient_path); - } catch (CppSQLite3Exception& e) { - Game::logger->Log("WorldServer", "Unable to connect to CDServer SQLite Database"); - Game::logger->Log("WorldServer", "Error: %s", e.errorMessage()); - Game::logger->Log("WorldServer", "Error Code: %i", e.errorCode()); - return EXIT_FAILURE; - } - - //Get CDClient initial information - try { - CDClientManager::Instance()->Initialize(); - } catch (CppSQLite3Exception& e) { - Game::logger->Log("WorldServer", "Failed to initialize CDServer SQLite Database"); - Game::logger->Log("WorldServer", "May be caused by corrupted file: %s", cdclient_path.c_str()); - Game::logger->Log("WorldServer", "Error: %s", e.errorMessage()); - Game::logger->Log("WorldServer", "Error Code: %i", e.errorCode()); - return EXIT_FAILURE; - } - - //Connect to the MySQL Database - std::string mysql_host = config.GetValue("mysql_host"); - std::string mysql_database = config.GetValue("mysql_database"); - std::string mysql_username = config.GetValue("mysql_username"); - std::string mysql_password = config.GetValue("mysql_password"); - - try { - Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); - } catch (sql::SQLException& ex) { - Game::logger->Log("MasterServer", "Got an error while connecting to the database: %s", ex.what()); - return EXIT_FAILURE; - } + try { + Database::Connect(mysql_host, mysql_database, mysql_username, mysql_password); + } catch (sql::SQLException& ex) { + Game::logger->Log("MasterServer", "Got an error while connecting to the database: %s", ex.what()); + Game::logger->Log("MigrationRunner", "Migrations not run"); + return EXIT_FAILURE; } + MigrationRunner::RunMigrations(); + + //Check CDClient exists + const std::string cdclient_path = "./res/CDServer.sqlite"; + std::ifstream cdclient_fd(cdclient_path); + if (!cdclient_fd.good()) { + Game::logger->Log("WorldServer", "%s could not be opened", cdclient_path.c_str()); + return EXIT_FAILURE; + } + cdclient_fd.close(); + + //Connect to CDClient + try { + CDClientDatabase::Connect(cdclient_path); + } catch (CppSQLite3Exception& e) { + Game::logger->Log("WorldServer", "Unable to connect to CDServer SQLite Database"); + Game::logger->Log("WorldServer", "Error: %s", e.errorMessage()); + Game::logger->Log("WorldServer", "Error Code: %i", e.errorCode()); + return EXIT_FAILURE; + } + + //Get CDClient initial information + try { + CDClientManager::Instance()->Initialize(); + } catch (CppSQLite3Exception& e) { + Game::logger->Log("WorldServer", "Failed to initialize CDServer SQLite Database"); + Game::logger->Log("WorldServer", "May be caused by corrupted file: %s", cdclient_path.c_str()); + Game::logger->Log("WorldServer", "Error: %s", e.errorMessage()); + Game::logger->Log("WorldServer", "Error Code: %i", e.errorCode()); + return EXIT_FAILURE; + } //If the first command line argument is -a or --account then make the user //input a username and password, with the password being hidden. @@ -825,9 +805,9 @@ void ShutdownSequence() { int FinalizeShutdown() { //Delete our objects here: Database::Destroy("MasterServer"); - delete Game::im; - delete Game::server; - delete Game::logger; + if (Game::im) delete Game::im; + if (Game::server) delete Game::server; + if (Game::logger) delete Game::logger; exit(EXIT_SUCCESS); return EXIT_SUCCESS; diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 495053fa..89243d59 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -1069,15 +1069,6 @@ void HandlePacket(Packet* packet) { bitStream.Write(1); bitStream.Write(blueprintID); - bitStream.Write(lxfmlSize + 9); - - //Write a fake sd0 header: - bitStream.Write(0x73); //s - bitStream.Write(0x64); //d - bitStream.Write(0x30); //0 - bitStream.Write(0x01); //1 - bitStream.Write(0xFF); //end magic - bitStream.Write(lxfmlSize); for (size_t i = 0; i < lxfmlSize; ++i) diff --git a/migrations/dlu/5_brick_model_sd0.sql b/migrations/dlu/5_brick_model_sd0.sql new file mode 100644 index 00000000..895f8b34 --- /dev/null +++ b/migrations/dlu/5_brick_model_sd0.sql @@ -0,0 +1 @@ +# This file is here as a mock. The real migration is located in BrickByBrickFix.cpp From 2bdbf129cf6b801d4e95892103b83778d3bcd91b Mon Sep 17 00:00:00 2001 From: Demetri Van Sickle Date: Tue, 25 Oct 2022 13:32:46 -0700 Subject: [PATCH 4/8] removed migration runner from build script (#788) --- build.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/build.sh b/build.sh index e61c1cb4..4a476cc1 100755 --- a/build.sh +++ b/build.sh @@ -8,5 +8,3 @@ cmake .. # To build utilizing multiple cores, append `-j` and the amount of cores to utilize, for example `cmake --build . --config Release -j8' cmake --build . --config Release -# Run migrations -./MasterServer -m From d8e73def9dad6b130de9dec2c09fbdfa7b356d37 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sat, 29 Oct 2022 01:12:29 -0700 Subject: [PATCH 5/8] Update GameMessages.cpp (#793) --- dGame/dGameMessages/GameMessages.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 48b2a508..4efbd286 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2401,6 +2401,34 @@ void GameMessages::HandleControlBehaviors(RakNet::BitStream* inStream, Entity* e } void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* entity, const SystemAddress& sysAddr) { + /* + ___ ___ + /\ /\___ _ __ ___ / __\ ___ / \_ __ __ _ __ _ ___ _ __ ___ + / /_/ / _ \ '__/ _ \ /__\/// _ \ / /\ / '__/ _` |/ _` |/ _ \| '_ \/ __| + / __ / __/ | | __/ / \/ \ __/ / /_//| | | (_| | (_| | (_) | | | \__ \ + \/ /_/ \___|_| \___| \_____/\___| /___,' |_| \__,_|\__, |\___/|_| |_|___/ + |___/ + ___ _ + / __\ _____ ____ _ _ __ ___ / \ + /__\/// _ \ \ /\ / / _` | '__/ _ \/ / + / \/ \ __/\ V V / (_| | | | __/\_/ + \_____/\___| \_/\_/ \__,_|_| \___\/ + <>=======() + (/\___ /|\\ ()==========<>_ + \_/ | \\ //|\ ______/ \) + \_| \\ // | \_/ + \|\/|\_ // /\/ + (oo)\ \_// / + //_/\_\/ / | + @@/ |=\ \ | + \_=\_ \ | + \==\ \|\_ snd + __(\===\( )\ + (((~) __(_/ | + (((~) \ / + ______/ / + '------' + */ LWOOBJID localId; inStream->Read(localId); From a745cdb727c67c8a8fd27d1779adad68d68197b0 Mon Sep 17 00:00:00 2001 From: Jett <55758076+Jettford@users.noreply.github.com> Date: Sun, 30 Oct 2022 00:17:35 +0100 Subject: [PATCH 6/8] Implement a shared config between servers (#795) * Implement a shared config between servers * Auto move config file on CMake run --- CMakeLists.txt | 2 +- README.md | 2 +- dCommon/dConfig.cpp | 15 +++++++++++++++ resources/authconfig.ini | 21 --------------------- resources/chatconfig.ini | 24 ------------------------ resources/masterconfig.ini | 21 --------------------- resources/sharedconfig.ini | 23 +++++++++++++++++++++++ resources/worldconfig.ini | 23 +---------------------- 8 files changed, 41 insertions(+), 90 deletions(-) create mode 100644 resources/sharedconfig.ini diff --git a/CMakeLists.txt b/CMakeLists.txt index 721140a6..8a4c5140 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,7 +90,7 @@ make_directory(${CMAKE_BINARY_DIR}/locale) make_directory(${CMAKE_BINARY_DIR}/logs) # Copy resource files on first build -set(RESOURCE_FILES "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blacklist.dcf") +set(RESOURCE_FILES "sharedconfig.ini" "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blacklist.dcf") foreach(resource_file ${RESOURCE_FILES}) if (NOT EXISTS ${PROJECT_BINARY_DIR}/${resource_file}) configure_file( diff --git a/README.md b/README.md index dfae2bab..cdb1d343 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ Initial setup can vary drastically based on which operating system or distributi #### Configuration -After the server has been built there should be four `ini` files in the build director: `authconfig.ini`, `chatconfig.ini`, `masterconfig.ini`, and `worldconfig.ini`. Go through them and fill in the database credentials and configure other settings if necessary. +After the server has been built there should be four `ini` files in the build director: `sharedconfig.ini`, `authconfig.ini`, `chatconfig.ini`, `masterconfig.ini`, and `worldconfig.ini`. Go through them and fill in the database credentials and configure other settings if necessary. #### Migrations diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 9750238a..c22c91cc 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -12,6 +12,15 @@ dConfig::dConfig(const std::string& filepath) { if (line[0] != '#') ProcessLine(line); } } + + std::ifstream sharedConfig("sharedconfig.ini", std::ios::in); + if (!sharedConfig.good()) return; + + while (std::getline(sharedConfig, line)) { + if (line.length() > 0) { + if (line[0] != '#') ProcessLine(line); + } + } } dConfig::~dConfig(void) { @@ -40,6 +49,12 @@ void dConfig::ProcessLine(const std::string& line) { if (!seglist[1].empty() && seglist[1][seglist[1].size() - 1] == '\r') seglist[1].erase(seglist[1].size() - 1); + for (const auto& key : m_Keys) { + if (seglist[0] == key) { + return; // first loaded key is preferred due to loading shared config secondarily + } + } + m_Keys.push_back(seglist[0]); m_Values.push_back(seglist[1]); } diff --git a/resources/authconfig.ini b/resources/authconfig.ini index 40ca146e..ec414bc0 100644 --- a/resources/authconfig.ini +++ b/resources/authconfig.ini @@ -1,27 +1,6 @@ -# MySQL connection info: -mysql_host= -mysql_database= -mysql_username= -mysql_password= - -# The public facing IP address. Can be 'localhost' for locally hosted servers -external_ip=localhost - # Port number. The client has the authserver port hardcoded to 1001 port=1001 -# Where to put crashlogs -dump_folder= - -# How many clients can be connected to the server at once -max_clients=999 - -# 0 or 1, should log to console -log_to_console=1 - -# 0 or 1, should log debug (developer only) statements to console for debugging, not needed for normal operation -log_debug_statements=0 - # 0 or 1, should ignore playkeys # If 1 everyone with an account will be able to login, regardless of if they have a key or not dont_use_keys=0 diff --git a/resources/chatconfig.ini b/resources/chatconfig.ini index f30fb8f9..26b26cc7 100644 --- a/resources/chatconfig.ini +++ b/resources/chatconfig.ini @@ -1,26 +1,2 @@ -# MySQL connection info: -mysql_host= -mysql_database= -mysql_username= -mysql_password= - -# The public facing IP address. Can be 'localhost' for locally hosted servers -external_ip=localhost - # Port number port=2005 - -# Where to put crashlogs -dump_folder= - -# How many clients can be connected to the server at once -max_clients=999 - -# 0 or 1, should log to console -log_to_console=1 - -# 0 or 1, should log debug (developer only) statements to console for debugging, not needed for normal operation -log_debug_statements=0 - -# 0 or 1, should not compile chat hash map to file -dont_generate_dcf=0 diff --git a/resources/masterconfig.ini b/resources/masterconfig.ini index c2d884a5..4864d8cb 100644 --- a/resources/masterconfig.ini +++ b/resources/masterconfig.ini @@ -1,12 +1,3 @@ -# MySQL connection info: -mysql_host= -mysql_database= -mysql_username= -mysql_password= - -# The public facing IP address. Can be 'localhost' for locally hosted servers -external_ip=localhost - # The internal ip of the master server master_ip=localhost @@ -26,17 +17,5 @@ use_sudo_chat=0 # Use sudo when launching world servers use_sudo_world=0 -# Where to put crashlogs -dump_folder= - -# How many clients can be connected to the server at once -max_clients=999 - -# 0 or 1, should log to console -log_to_console=1 - -# 0 or 1, should log debug (developer only) statements to console for debugging, not needed for normal operation -log_debug_statements=0 - # 0 or 1, should autostart auth, chat, and char servers prestart_servers=1 diff --git a/resources/sharedconfig.ini b/resources/sharedconfig.ini new file mode 100644 index 00000000..1a377d28 --- /dev/null +++ b/resources/sharedconfig.ini @@ -0,0 +1,23 @@ +# MySQL connection info: +mysql_host= +mysql_database= +mysql_username= +mysql_password= + +# 0 or 1, should log to console +log_to_console=1 + +# 0 or 1, should log debug (developer only) statements to console for debugging, not needed for normal operation +log_debug_statements=0 + +# The public facing IP address. Can be 'localhost' for locally hosted servers +external_ip=localhost + +# 0 or 1, should not compile chat hash map to file +dont_generate_dcf=0 + +# How many clients can be connected to the server at once +max_clients=999 + +# Where to put crashlogs +dump_folder= diff --git a/resources/worldconfig.ini b/resources/worldconfig.ini index 931da28c..ee6b6651 100644 --- a/resources/worldconfig.ini +++ b/resources/worldconfig.ini @@ -1,9 +1,3 @@ -# MySQL connection info: -mysql_host= -mysql_database= -mysql_username= -mysql_password= - # URL to the code repository for the hosted server # If you fork this repository and/or make changes to the code, reflect that here to comply with AGPLv3 source=https://github.com/DarkflameUniverse/DarkflameServer @@ -11,21 +5,6 @@ source=https://github.com/DarkflameUniverse/DarkflameServer # Port to the chat server, same as in chatconfig.ini chat_server_port=2005 -# Where to put crashlogs -dump_folder= - -# How many clients can be connected to the server at once -max_clients=999 - -# 0 or 1, should log to console -log_to_console=1 - -# 0 or 1, should log debug (developer only) statements to console for debugging, not needed for normal operation -log_debug_statements=0 - -# 0 or 1, should not compile chat hash map to file -dont_generate_dcf=0 - # 0 or 1, should disable chat disable_chat=0 @@ -63,4 +42,4 @@ pets_take_imagination=1 # If you would like to increase the maximum number of best friends a player can have on the server # Change the value below to what you would like this to be (5 is live accurate) -max_number_of_best_friends=5 \ No newline at end of file +max_number_of_best_friends=5 From 906887bda9d2f0d0b115ba07b10bc27b3a1e6863 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 30 Oct 2022 00:38:43 -0700 Subject: [PATCH 7/8] Add automatic cdclient migration runner support and setup (#789) * Add automatic migrations for CDServer Add support to automatically migrate and update CDServers with new migrations. Also adds support to simplify the setup process by simply putting the fdb in the res folder and letting the server convert it to sqlite. This reduces the amount of back and forth when setting up a server. * Remove transaction language * Add DML execution `poggers` Add a way to execute DML commands through the sqlite connection on the server. * Make DML Commands more robust On the off chance the server is shutdown before the whole migration is run, lets just not add it to our "finished list" until the whole file is done. * Update README --- CMakeLists.txt | 18 +++++- README.md | 9 +-- dDatabase/CDClientDatabase.cpp | 5 ++ dDatabase/CDClientDatabase.h | 8 +++ dDatabase/MigrationRunner.cpp | 84 +++++++++++++++++++-------- dDatabase/MigrationRunner.h | 14 ++--- dMasterServer/MasterServer.cpp | 33 ++++++++++- migrations/cdserver/0_nt_footrace.sql | 4 -- 8 files changed, 127 insertions(+), 48 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a4c5140..b147b200 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,13 +108,25 @@ foreach(file ${VANITY_FILES}) endforeach() # Move our migrations for MasterServer to run -file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/) +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/dlu/) file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/dlu/*.sql) foreach(file ${SQL_FILES}) get_filename_component(file ${file} NAME) - if (NOT EXISTS ${PROJECT_BINARY_DIR}/migrations/${file}) + if (NOT EXISTS ${PROJECT_BINARY_DIR}/migrations/dlu/${file}) configure_file( - ${CMAKE_SOURCE_DIR}/migrations/dlu/${file} ${PROJECT_BINARY_DIR}/migrations/${file} + ${CMAKE_SOURCE_DIR}/migrations/dlu/${file} ${PROJECT_BINARY_DIR}/migrations/dlu/${file} + COPYONLY + ) + endif() +endforeach() + +file(MAKE_DIRECTORY ${PROJECT_BINARY_DIR}/migrations/cdserver/) +file(GLOB SQL_FILES ${CMAKE_SOURCE_DIR}/migrations/cdserver/*.sql) +foreach(file ${SQL_FILES}) + get_filename_component(file ${file} NAME) + if (NOT EXISTS ${PROJECT_BINARY_DIR}/migrations/cdserver/${file}) + configure_file( + ${CMAKE_SOURCE_DIR}/migrations/cdserver/${file} ${PROJECT_BINARY_DIR}/migrations/cdserver/${file} COPYONLY ) endif() diff --git a/README.md b/README.md index cdb1d343..8f9eaf8d 100644 --- a/README.md +++ b/README.md @@ -196,9 +196,10 @@ certutil -hashfile SHA256 * Copy over or create symlinks from `locale.xml` in your client `locale` directory to the `build/locale` directory #### Client database -* Use `fdb_to_sqlite.py` in lcdr's utilities on `res/cdclient.fdb` in the unpacked client to convert the client database to `cdclient.sqlite` -* Move and rename `cdclient.sqlite` into `build/res/CDServer.sqlite` -* Run each SQL file in the order at which they appear [here](migrations/cdserver/) on the SQLite database +* Move the file `res/cdclient.fdb` from the unpacked client to the `build/res` folder on the server. +* The server will automatically copy and convert the file from fdb to sqlite should `CDServer.sqlite` not already exist. +* You can also convert the database manually using `fdb_to_sqlite.py` using lcdr's utilities. Just make sure to rename the file to `CDServer.sqlite` instead of `cdclient.sqlite`. +* Migrations to the database are automatically run on server start. When migrations are needed to be ran, the server may take a bit longer to start. ### Database Darkflame Universe utilizes a MySQL/MariaDB database for account and character information. @@ -229,7 +230,7 @@ Your build directory should now look like this: * **locale/** * locale.xml * **res/** - * CDServer.sqlite + * cdclient.fdb * chatplus_en_us.txt * **macros/** * ... diff --git a/dDatabase/CDClientDatabase.cpp b/dDatabase/CDClientDatabase.cpp index bf8485d6..4c2df1d2 100644 --- a/dDatabase/CDClientDatabase.cpp +++ b/dDatabase/CDClientDatabase.cpp @@ -14,6 +14,11 @@ CppSQLite3Query CDClientDatabase::ExecuteQuery(const std::string& query) { return conn->execQuery(query.c_str()); } +//! Updates the CDClient file with Data Manipulation Language (DML) commands. +int CDClientDatabase::ExecuteDML(const std::string& query) { + return conn->execDML(query.c_str()); +} + //! Makes prepared statements CppSQLite3Statement CDClientDatabase::CreatePreppedStmt(const std::string& query) { return conn->compileStatement(query.c_str()); diff --git a/dDatabase/CDClientDatabase.h b/dDatabase/CDClientDatabase.h index 6b254ebb..96f67d64 100644 --- a/dDatabase/CDClientDatabase.h +++ b/dDatabase/CDClientDatabase.h @@ -40,6 +40,14 @@ namespace CDClientDatabase { */ CppSQLite3Query ExecuteQuery(const std::string& query); + //! Updates the CDClient file with Data Manipulation Language (DML) commands. + /*! + \param query The DML command to run. DML command can be multiple queries in one string but only + the last one will return its number of updated rows. + \return The number of updated rows. + */ + int ExecuteDML(const std::string& query); + //! Queries the CDClient and parses arguments /*! \param query The query with formatted arguments diff --git a/dDatabase/MigrationRunner.cpp b/dDatabase/MigrationRunner.cpp index 1eb03887..017ebe32 100644 --- a/dDatabase/MigrationRunner.cpp +++ b/dDatabase/MigrationRunner.cpp @@ -1,11 +1,34 @@ #include "MigrationRunner.h" #include "BrickByBrickFix.h" +#include "CDClientDatabase.h" +#include "Database.h" +#include "Game.h" #include "GeneralUtils.h" +#include "dLogger.h" -#include -#include -#include +#include + +Migration LoadMigration(std::string path) { + Migration migration{}; + std::ifstream file("./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());"); @@ -13,17 +36,14 @@ void MigrationRunner::RunMigrations() { delete stmt; sql::SQLString finalSQL = ""; - Migration checkMigration{}; bool runSd0Migrations = false; - for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/")) { - auto migration = LoadMigration(entry); + for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/dlu/")) { + auto migration = LoadMigration("dlu/" + entry); if (migration.data.empty()) { continue; } - checkMigration = migration; - stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); stmt->setString(1, migration.name); auto* res = stmt->executeQuery(); @@ -40,7 +60,7 @@ void MigrationRunner::RunMigrations() { } stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); - stmt->setString(1, entry); + stmt->setString(1, migration.name); stmt->execute(); delete stmt; } @@ -72,23 +92,39 @@ void MigrationRunner::RunMigrations() { } } -Migration MigrationRunner::LoadMigration(std::string path) { - Migration migration{}; - std::ifstream file("./migrations/" + path); +void MigrationRunner::RunSQLiteMigrations() { + 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; - if (file.is_open()) { - std::string line; - std::string total = ""; + for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/cdserver/")) { + auto migration = LoadMigration("cdserver/" + entry); - while (std::getline(file, line)) { - total += line; + if (migration.data.empty()) continue; + + stmt = Database::CreatePreppedStmt("SELECT name FROM migration_history WHERE name = ?;"); + stmt->setString(1, migration.name); + auto* res = stmt->executeQuery(); + bool doExit = res->next(); + delete res; + delete stmt; + if (doExit) 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()); + 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()); + } } - - file.close(); - - migration.name = path; - migration.data = total; + stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);"); + stmt->setString(1, migration.name); + stmt->execute(); + delete stmt; } - - return migration; + Game::logger->Log("MigrationRunner", "CDServer database is up to date."); } diff --git a/dDatabase/MigrationRunner.h b/dDatabase/MigrationRunner.h index f5d1325a..0cb36d53 100644 --- a/dDatabase/MigrationRunner.h +++ b/dDatabase/MigrationRunner.h @@ -1,19 +1,13 @@ #pragma once -#include "Database.h" - -#include "dCommonVars.h" -#include "Game.h" -#include "dCommonVars.h" -#include "dLogger.h" +#include struct Migration { std::string data; std::string name; }; -class MigrationRunner { -public: - static void RunMigrations(); - static Migration LoadMigration(std::string path); +namespace MigrationRunner { + void RunMigrations(); + void RunSQLiteMigrations(); }; diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index 538fb749..cd54913a 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -105,10 +105,34 @@ int main(int argc, char** argv) { const std::string cdclient_path = "./res/CDServer.sqlite"; std::ifstream cdclient_fd(cdclient_path); if (!cdclient_fd.good()) { - Game::logger->Log("WorldServer", "%s could not be opened", cdclient_path.c_str()); - return EXIT_FAILURE; + Game::logger->Log("WorldServer", "%s could not be opened. Looking for cdclient.fdb to convert to sqlite.", cdclient_path.c_str()); + cdclient_fd.close(); + + const std::string cdclientFdbPath = "./res/cdclient.fdb"; + cdclient_fd.open(cdclientFdbPath); + if (!cdclient_fd.good()) { + Game::logger->Log( + "WorldServer", "%s could not be opened." + "Please move a cdclient.fdb or an already converted database to build/res.", cdclientFdbPath.c_str()); + return EXIT_FAILURE; + } + Game::logger->Log("WorldServer", "Found %s. Clearing cdserver migration_history then copying and converting to sqlite.", cdclientFdbPath.c_str()); + auto stmt = Database::CreatePreppedStmt(R"#(DELETE FROM migration_history WHERE name LIKE "%cdserver%";)#"); + stmt->executeUpdate(); + delete stmt; + cdclient_fd.close(); + + std::string res = "python3 ../thirdparty/docker-utils/utils/fdb_to_sqlite.py " + cdclientFdbPath; + int r = system(res.c_str()); + if (r != 0) { + Game::logger->Log("MasterServer", "Failed to convert fdb to sqlite"); + return EXIT_FAILURE; + } + if (std::rename("./cdclient.sqlite", "./res/CDServer.sqlite") != 0) { + Game::logger->Log("MasterServer", "failed to move cdclient file."); + return EXIT_FAILURE; + } } - cdclient_fd.close(); //Connect to CDClient try { @@ -120,6 +144,9 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } + // Run migrations should any need to be run. + MigrationRunner::RunSQLiteMigrations(); + //Get CDClient initial information try { CDClientManager::Instance()->Initialize(); diff --git a/migrations/cdserver/0_nt_footrace.sql b/migrations/cdserver/0_nt_footrace.sql index fd37599e..0a40cfef 100644 --- a/migrations/cdserver/0_nt_footrace.sql +++ b/migrations/cdserver/0_nt_footrace.sql @@ -1,6 +1,2 @@ -BEGIN TRANSACTION; - UPDATE ComponentsRegistry SET component_id = 1901 WHERE id = 12916 AND component_type = 39; INSERT INTO ActivityRewards (objectTemplate, ActivityRewardIndex, activityRating, LootMatrixIndex, CurrencyIndex, ChallengeRating, description) VALUES (1901, 166, -1, 598, 1, 4, 'NT Foot Race'); - -COMMIT; From 89fb66c4a980020f6f0284ec60ed8d69ff773483 Mon Sep 17 00:00:00 2001 From: David Markowitz <39972741+EmosewaMC@users.noreply.github.com> Date: Sun, 30 Oct 2022 13:06:05 -0700 Subject: [PATCH 8/8] Change AMFArray getters to use Templates and fix CI halting when one matrix fails (#796) * Change AMFArray getters to use Templates Move Template definition to header * Add more tests Add tests for casting to wrong template type Add tests for going out of bounds in the array. * Try continue-on-error * Update build-and-test.yml * Try continue-on-error Update build-and-test.yml * change version * Update CMakeMariaDBLists.txt Update CMakeMariaDBLists.txt --- .github/workflows/build-and-test.yml | 1 + dCommon/AMFFormat.cpp | 15 ----- dCommon/AMFFormat.h | 55 ++++++++++++---- tests/AMFDeserializeTests.cpp | 93 ++++++++++++++++++++-------- thirdparty/CMakeMariaDBLists.txt | 4 +- 5 files changed, 111 insertions(+), 57 deletions(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 5a36df4b..8f65fd8e 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -10,6 +10,7 @@ jobs: build-and-test: name: Build & Test (${{ matrix.os }}) runs-on: ${{ matrix.os }} + continue-on-error: true strategy: matrix: os: [ windows-2022, ubuntu-20.04, macos-11 ] diff --git a/dCommon/AMFFormat.cpp b/dCommon/AMFFormat.cpp index 822f994d..4407b29c 100644 --- a/dCommon/AMFFormat.cpp +++ b/dCommon/AMFFormat.cpp @@ -58,16 +58,6 @@ void AMFArrayValue::RemoveValue(const std::string& key) { } } -// AMFArray Find Value -AMFValue* AMFArrayValue::FindValue(const std::string& key) { - _AMFArrayMap_::iterator it = this->associative.find(key); - if (it != this->associative.end()) { - return it->second; - } - - return nullptr; -} - // AMFArray Get Associative Iterator Begin _AMFArrayMap_::iterator AMFArrayValue::GetAssociativeIteratorValueBegin() { return this->associative.begin(); @@ -93,11 +83,6 @@ uint32_t AMFArrayValue::GetDenseValueSize() { return (uint32_t)this->dense.size(); } -// AMFArray Get value at index in Dense List -AMFValue* AMFArrayValue::GetValueAt(uint32_t index) { - return this->dense.at(index); -} - // AMFArray Get Dense Iterator Begin _AMFArrayList_::iterator AMFArrayValue::GetDenseIteratorBegin() { return this->dense.begin(); diff --git a/dCommon/AMFFormat.h b/dCommon/AMFFormat.h index eaa342cd..2b423abd 100644 --- a/dCommon/AMFFormat.h +++ b/dCommon/AMFFormat.h @@ -75,7 +75,9 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFUndefined; } + AMFValueType GetValueType() { return ValueType; } +public: + static const AMFValueType ValueType = AMFUndefined; }; //! The null value AMF type @@ -85,7 +87,9 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFNull; } + AMFValueType GetValueType() { return ValueType; } +public: + static const AMFValueType ValueType = AMFNull; }; //! The false value AMF type @@ -95,7 +99,9 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFFalse; } + AMFValueType GetValueType() { return ValueType; } +public: + static const AMFValueType ValueType = AMFFalse; }; //! The true value AMF type @@ -105,7 +111,9 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFTrue; } + AMFValueType GetValueType() { return ValueType; } +public: + static const AMFValueType ValueType = AMFTrue; }; //! The integer value AMF type @@ -117,9 +125,10 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFInteger; } + AMFValueType GetValueType() { return ValueType; } public: + static const AMFValueType ValueType = AMFInteger; //! Sets the integer value /*! \param value The value to set @@ -142,9 +151,10 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFDouble; } + AMFValueType GetValueType() { return ValueType; } public: + static const AMFValueType ValueType = AMFDouble; //! Sets the double value /*! \param value The value to set to @@ -167,9 +177,10 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFString; } + AMFValueType GetValueType() { return ValueType; } public: + static const AMFValueType ValueType = AMFString; //! Sets the string value /*! \param value The string value to set to @@ -192,9 +203,10 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFXMLDoc; } + AMFValueType GetValueType() { return ValueType; } public: + static const AMFValueType ValueType = AMFXMLDoc; //! Sets the XML Doc value /*! \param value The value to set to @@ -217,9 +229,10 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFDate; } + AMFValueType GetValueType() { return ValueType; } public: + static const AMFValueType ValueType = AMFDate; //! Sets the date time /*! \param value The value to set to @@ -244,9 +257,11 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFArray; } + AMFValueType GetValueType() { return ValueType; } public: + static const AMFValueType ValueType = AMFArray; + ~AMFArrayValue() override; //! Inserts an item into the array map for a specific key /*! @@ -265,7 +280,15 @@ public: /*! \return The AMF value if found, nullptr otherwise */ - AMFValue* FindValue(const std::string& key); + template + 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(it->second); + } + + return nullptr; + }; //! Returns where the associative iterator begins /*! @@ -298,7 +321,12 @@ public: /*! \param index The index to get */ - AMFValue* GetValueAt(uint32_t index); + template + 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(foundValue) : nullptr; + }; //! Returns where the dense iterator begins /*! @@ -334,10 +362,11 @@ private: /*! \return The AMF value type */ - AMFValueType GetValueType() { return AMFObject; } + AMFValueType GetValueType() { return ValueType; } ~AMFObjectValue() override; public: + static const AMFValueType ValueType = AMFObject; //! Constructor /*! \param traits The traits to set diff --git a/tests/AMFDeserializeTests.cpp b/tests/AMFDeserializeTests.cpp index 03941a85..8d4974a3 100644 --- a/tests/AMFDeserializeTests.cpp +++ b/tests/AMFDeserializeTests.cpp @@ -146,8 +146,8 @@ int ReadAMFArrayFromBitStream() { ASSERT_EQ(res->GetValueType(), AMFValueType::AMFArray); ASSERT_EQ(static_cast(res.get())->GetAssociativeMap().size(), 1); ASSERT_EQ(static_cast(res.get())->GetDenseArray().size(), 1); - ASSERT_EQ(static_cast(static_cast(res.get())->FindValue("BehaviorID"))->GetStringValue(), "10447"); - ASSERT_EQ(static_cast(static_cast(res.get())->GetDenseArray()[0])->GetStringValue(), "10447"); + ASSERT_EQ(static_cast(res.get())->FindValue("BehaviorID")->GetStringValue(), "10447"); + ASSERT_EQ(static_cast(res.get())->GetValueAt(0)->GetStringValue(), "10447"); } // Test a dense array return 0; @@ -219,102 +219,103 @@ int TestLiveCapture() { auto result = static_cast(resultFromFn.get()); // Test the outermost array - ASSERT_EQ(dynamic_cast(result->FindValue("BehaviorID"))->GetStringValue(), "10447"); - ASSERT_EQ(dynamic_cast(result->FindValue("objectID"))->GetStringValue(), "288300744895913279") + ASSERT_EQ(result->FindValue("BehaviorID")->GetStringValue(), "10447"); + ASSERT_EQ(result->FindValue("objectID")->GetStringValue(), "288300744895913279"); + + // Test the execution state array + auto executionState = result->FindValue("executionState"); - // Test the execution state array - auto executionState = dynamic_cast(result->FindValue("executionState")); ASSERT_NE(executionState, nullptr); - auto strips = dynamic_cast(executionState->FindValue("strips"))->GetDenseArray(); + auto strips = executionState->FindValue("strips")->GetDenseArray(); ASSERT_EQ(strips.size(), 1); auto stripsPosition0 = dynamic_cast(strips[0]); - auto actionIndex = dynamic_cast(stripsPosition0->FindValue("actionIndex")); + auto actionIndex = stripsPosition0->FindValue("actionIndex"); ASSERT_EQ(actionIndex->GetDoubleValue(), 0.0f); - auto stripIDExecution = dynamic_cast(stripsPosition0->FindValue("id")); + auto stripIDExecution = stripsPosition0->FindValue("id"); ASSERT_EQ(stripIDExecution->GetDoubleValue(), 0.0f); - auto stateIDExecution = dynamic_cast(executionState->FindValue("stateID")); + auto stateIDExecution = executionState->FindValue("stateID"); ASSERT_EQ(stateIDExecution->GetDoubleValue(), 0.0f); - auto states = dynamic_cast(result->FindValue("states"))->GetDenseArray(); + auto states = result->FindValue("states")->GetDenseArray(); ASSERT_EQ(states.size(), 1); auto firstState = dynamic_cast(states[0]); - auto stateID = dynamic_cast(firstState->FindValue("id")); + auto stateID = firstState->FindValue("id"); ASSERT_EQ(stateID->GetDoubleValue(), 0.0f); - auto stripsInState = dynamic_cast(firstState->FindValue("strips"))->GetDenseArray(); + auto stripsInState = firstState->FindValue("strips")->GetDenseArray(); ASSERT_EQ(stripsInState.size(), 1); auto firstStrip = dynamic_cast(stripsInState[0]); - auto actionsInFirstStrip = dynamic_cast(firstStrip->FindValue("actions"))->GetDenseArray(); + auto actionsInFirstStrip = firstStrip->FindValue("actions")->GetDenseArray(); ASSERT_EQ(actionsInFirstStrip.size(), 3); - auto actionID = dynamic_cast(firstStrip->FindValue("id")); + auto actionID = firstStrip->FindValue("id"); ASSERT_EQ(actionID->GetDoubleValue(), 0.0f) - auto uiArray = dynamic_cast(firstStrip->FindValue("ui")); + auto uiArray = firstStrip->FindValue("ui"); - auto xPos = dynamic_cast(uiArray->FindValue("x")); - auto yPos = dynamic_cast(uiArray->FindValue("y")); + auto xPos = uiArray->FindValue("x"); + auto yPos = uiArray->FindValue("y"); ASSERT_EQ(xPos->GetDoubleValue(), 103.0f); ASSERT_EQ(yPos->GetDoubleValue(), 82.0f); - auto stripID = dynamic_cast(firstStrip->FindValue("id")); + auto stripID = firstStrip->FindValue("id"); ASSERT_EQ(stripID->GetDoubleValue(), 0.0f) auto firstAction = dynamic_cast(actionsInFirstStrip[0]); - auto firstType = dynamic_cast(firstAction->FindValue("Type")); + auto firstType = firstAction->FindValue("Type"); ASSERT_EQ(firstType->GetStringValue(), "OnInteract"); - auto firstCallback = dynamic_cast(firstAction->FindValue("__callbackID__")); + auto firstCallback = firstAction->FindValue("__callbackID__"); ASSERT_EQ(firstCallback->GetStringValue(), ""); auto secondAction = dynamic_cast(actionsInFirstStrip[1]); - auto secondType = dynamic_cast(secondAction->FindValue("Type")); + auto secondType = secondAction->FindValue("Type"); ASSERT_EQ(secondType->GetStringValue(), "FlyUp"); - auto secondCallback = dynamic_cast(secondAction->FindValue("__callbackID__")); + auto secondCallback = secondAction->FindValue("__callbackID__"); ASSERT_EQ(secondCallback->GetStringValue(), ""); - auto secondDistance = dynamic_cast(secondAction->FindValue("Distance")); + auto secondDistance = secondAction->FindValue("Distance"); ASSERT_EQ(secondDistance->GetDoubleValue(), 25.0f); auto thirdAction = dynamic_cast(actionsInFirstStrip[2]); - auto thirdType = dynamic_cast(thirdAction->FindValue("Type")); + auto thirdType = thirdAction->FindValue("Type"); ASSERT_EQ(thirdType->GetStringValue(), "FlyDown"); - auto thirdCallback = dynamic_cast(thirdAction->FindValue("__callbackID__")); + auto thirdCallback = thirdAction->FindValue("__callbackID__"); ASSERT_EQ(thirdCallback->GetStringValue(), ""); - auto thirdDistance = dynamic_cast(thirdAction->FindValue("Distance")); + auto thirdDistance = thirdAction->FindValue("Distance"); ASSERT_EQ(thirdDistance->GetDoubleValue(), 25.0f); @@ -327,6 +328,42 @@ int TestNullStream() { return 0; } +int TestBadConversion() { + std::ifstream testFileStream; + testFileStream.open("AMFBitStreamTest.bin", std::ios::binary); + + // Read a test BitStream from a file + RakNet::BitStream testBitStream; + char byte = 0; + while (testFileStream.get(byte)) { + testBitStream.Write(byte); + } + + testFileStream.close(); + + auto resultFromFn = ReadFromBitStream(&testBitStream); + auto result = static_cast(resultFromFn.get()); + + // Actually a string value. + ASSERT_EQ(result->FindValue("BehaviorID"), nullptr); + + // Does not exist in the associative portion + ASSERT_EQ(result->FindValue("DOES_NOT_EXIST"), nullptr); + + result->PushBackValue(new AMFTrueValue()); + + // Exists and is correct type + ASSERT_NE(result->GetValueAt(0), nullptr); + + // Value exists but is wrong typing + ASSERT_EQ(result->GetValueAt(0), nullptr); + + // Value is out of bounds + ASSERT_EQ(result->GetValueAt(1), nullptr); + + return 0; +} + int AMFDeserializeTests(int argc, char** const argv) { std::cout << "Checking that using a null bitstream doesnt cause exception" << std::endl; if (TestNullStream()) return 1; @@ -343,6 +380,8 @@ int AMFDeserializeTests(int argc, char** const argv) { if (TestLiveCapture() != 0) return 1; std::cout << "Passed live capture, checking unimplemented amf values" << std::endl; if (TestUnimplementedAMFValues() != 0) return 1; + std::cout << "Passed unimplemented values, checking poor casting" << std::endl; + if (TestBadConversion() != 0) return 1; std::cout << "Passed all tests." << std::endl; return 0; } diff --git a/thirdparty/CMakeMariaDBLists.txt b/thirdparty/CMakeMariaDBLists.txt index 55e95c4d..cb1e28e7 100644 --- a/thirdparty/CMakeMariaDBLists.txt +++ b/thirdparty/CMakeMariaDBLists.txt @@ -43,7 +43,7 @@ if(WIN32 AND NOT MARIADB_BUILD_SOURCE) add_custom_target(mariadb_connector_cpp) add_custom_command(TARGET mariadb_connector_cpp POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${MARIADB_CPP_CONNECTOR_DIR}/mariadbcpp.dll" "${MARIADB_C_CONNECTOR_DIR}/lib/libmariadb.dll" "${PROJECT_BINARY_DIR}") @@ -118,7 +118,7 @@ else() # Build from source ${BINARY_DIR}/mariadbcpp/plugin ${MARIADB_SHARED_LIBRARY_COPY_LOCATION} - COMMAND ${CMAKE_COMMAND} -E copy_if_different + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${MARIADB_SHARED_LIBRARY_LOCATION} ${MARIADB_SHARED_LIBRARY_COPY_LOCATION}