mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2025-05-23 15:22:28 +00:00
Merge branch 'main' into npc-pathing
This commit is contained in:
commit
b3c4b5a75c
1
.github/workflows/build-and-test.yml
vendored
1
.github/workflows/build-and-test.yml
vendored
@ -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 ]
|
||||
|
@ -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(
|
||||
@ -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()
|
||||
|
@ -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.
|
||||
|
18
README.md
18
README.md
@ -196,23 +196,25 @@ certutil -hashfile <file> 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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
#### 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
|
||||
|
||||
@ -228,7 +230,7 @@ Your build directory should now look like this:
|
||||
* **locale/**
|
||||
* locale.xml
|
||||
* **res/**
|
||||
* CDServer.sqlite
|
||||
* cdclient.fdb
|
||||
* chatplus_en_us.txt
|
||||
* **macros/**
|
||||
* ...
|
||||
|
2
build.sh
2
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
|
||||
|
@ -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();
|
||||
|
@ -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 <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
|
||||
/*!
|
||||
@ -298,7 +321,12 @@ public:
|
||||
/*!
|
||||
\param index The index to get
|
||||
*/
|
||||
AMFValue* GetValueAt(uint32_t index);
|
||||
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
|
||||
/*!
|
||||
@ -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
|
||||
|
180
dCommon/BrickByBrickFix.cpp
Normal file
180
dCommon/BrickByBrickFix.cpp
Normal file
@ -0,0 +1,180 @@
|
||||
#include "BrickByBrickFix.h"
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "tinyxml2.h"
|
||||
|
||||
#include "Database.h"
|
||||
#include "Game.h"
|
||||
#include "ZCompression.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
//! Forward declarations
|
||||
|
||||
std::unique_ptr<sql::ResultSet> 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<sql::PreparedStatement> ugcModelToDelete(Database::CreatePreppedStmt("DELETE FROM ugc WHERE ugc.id = ?;"));
|
||||
std::unique_ptr<sql::PreparedStatement> pcModelToDelete(Database::CreatePreppedStmt("DELETE FROM properties_contents WHERE ugc_id = ?;"));
|
||||
std::string completeUncompressedModel{};
|
||||
uint32_t chunkCount{};
|
||||
uint64_t modelId = modelsToTruncate->getInt(1);
|
||||
std::unique_ptr<sql::Blob> modelAsSd0(modelsToTruncate->getBlob(2));
|
||||
// Check that header is sd0 by checking for the sd0 magic.
|
||||
if (CheckSd0Magic(modelAsSd0.get())) {
|
||||
while (true) {
|
||||
uint32_t chunkSize{};
|
||||
modelAsSd0->read(reinterpret_cast<char*>(&chunkSize), sizeof(uint32_t)); // Extract chunk size from istream
|
||||
|
||||
// Check if good here since if at the end of an sd0 file, this will have eof flagged.
|
||||
if (!modelAsSd0->good()) break;
|
||||
|
||||
std::unique_ptr<uint8_t[]> 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<uint8_t[]> 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<tinyxml2::XMLDocument> document = std::make_unique<tinyxml2::XMLDocument>();
|
||||
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(
|
||||
"</LXFML>",
|
||||
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<sql::PreparedStatement> insertionStatement(Database::CreatePreppedStmt("UPDATE ugc SET lxfml = ? WHERE id = ?;"));
|
||||
while (modelsToUpdate->next()) {
|
||||
int64_t modelId = modelsToUpdate->getInt64(1);
|
||||
std::unique_ptr<sql::Blob> oldLxfml(modelsToUpdate->getBlob(2));
|
||||
// Check if the stored blob starts with zlib magic (0x78 0xDA - best compression of zlib)
|
||||
// If it does, convert it to sd0.
|
||||
if (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<uint32_t>(oldLxfml->tellg());
|
||||
oldLxfml->seekg(0);
|
||||
|
||||
// Allocate 9 extra bytes. 5 for sd0 magic, 4 for the only zlib compressed size.
|
||||
uint32_t oldLxfmlSizeWithHeader = oldLxfmlSize + 9;
|
||||
std::unique_ptr<char[]> 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<std::istream*>(&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<sql::ResultSet> GetModelsFromDatabase() {
|
||||
std::unique_ptr<sql::PreparedStatement> modelsRawDataQuery(Database::CreatePreppedStmt("SELECT id, lxfml FROM ugc;"));
|
||||
return std::unique_ptr<sql::ResultSet>(modelsRawDataQuery->executeQuery());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Writes sd0 magic at the front of a char*
|
||||
*
|
||||
* @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<uint32_t*>(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;
|
||||
}
|
26
dCommon/BrickByBrickFix.h
Normal file
26
dCommon/BrickByBrickFix.h
Normal file
@ -0,0 +1,26 @@
|
||||
#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();
|
||||
|
||||
/**
|
||||
* @brief Max size of an inflated sd0 zlib chunk
|
||||
*
|
||||
*/
|
||||
constexpr uint32_t MAX_SD0_CHUNK_SIZE = 1024 * 256;
|
||||
};
|
@ -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)
|
||||
|
@ -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]);
|
||||
}
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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; }
|
||||
|
@ -1,65 +1,19 @@
|
||||
#include "MigrationRunner.h"
|
||||
|
||||
#include "BrickByBrickFix.h"
|
||||
#include "CDClientDatabase.h"
|
||||
#include "Database.h"
|
||||
#include "Game.h"
|
||||
#include "GeneralUtils.h"
|
||||
#include "dLogger.h"
|
||||
|
||||
#include <fstream>
|
||||
#include <algorithm>
|
||||
#include <thread>
|
||||
#include <istream>
|
||||
|
||||
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();
|
||||
delete stmt;
|
||||
|
||||
sql::SQLString finalSQL = "";
|
||||
Migration checkMigration{};
|
||||
|
||||
for (const auto& entry : GeneralUtils::GetFileNamesFromFolder("./migrations/")) {
|
||||
auto migration = LoadMigration(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();
|
||||
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');
|
||||
|
||||
stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
|
||||
stmt->setString(1, entry);
|
||||
stmt->execute();
|
||||
delete stmt;
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Migration MigrationRunner::LoadMigration(std::string path) {
|
||||
Migration LoadMigration(std::string path) {
|
||||
Migration migration{};
|
||||
std::ifstream file("./migrations/" + path);
|
||||
|
||||
if (file.is_open()) {
|
||||
std::hash<std::string> hash;
|
||||
|
||||
std::string line;
|
||||
std::string total = "";
|
||||
|
||||
@ -75,3 +29,102 @@ Migration MigrationRunner::LoadMigration(std::string path) {
|
||||
|
||||
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::GetFileNamesFromFolder("./migrations/dlu/")) {
|
||||
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);
|
||||
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 == "5_brick_model_sd0.sql") {
|
||||
runSd0Migrations = true;
|
||||
} else {
|
||||
finalSQL.append(migration.data);
|
||||
}
|
||||
|
||||
stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
|
||||
stmt->setString(1, migration.name);
|
||||
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);
|
||||
} 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* 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::GetFileNamesFromFolder("./migrations/cdserver/")) {
|
||||
auto migration = LoadMigration("cdserver/" + entry);
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
stmt = Database::CreatePreppedStmt("INSERT INTO migration_history (name) VALUES (?);");
|
||||
stmt->setString(1, migration.name);
|
||||
stmt->execute();
|
||||
delete stmt;
|
||||
}
|
||||
Game::logger->Log("MigrationRunner", "CDServer database is up to date.");
|
||||
}
|
||||
|
@ -1,19 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Database.h"
|
||||
|
||||
#include "dCommonVars.h"
|
||||
#include "Game.h"
|
||||
#include "dCommonVars.h"
|
||||
#include "dLogger.h"
|
||||
#include <string>
|
||||
|
||||
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();
|
||||
};
|
||||
|
@ -2413,7 +2413,6 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent
|
||||
/__\/// _ \ \ /\ / / _` | '__/ _ \/ /
|
||||
/ \/ \ __/\ V V / (_| | | | __/\_/
|
||||
\_____/\___| \_/\_/ \__,_|_| \___\/
|
||||
|
||||
<>=======()
|
||||
(/\___ /|\\ ()==========<>_
|
||||
\_/ | \\ //|\ ______/ \)
|
||||
@ -2430,34 +2429,25 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent
|
||||
______/ /
|
||||
'------'
|
||||
*/
|
||||
|
||||
//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<char[]> sd0Data(new char[sd0Size]);
|
||||
|
||||
uint32_t lxfmlSize;
|
||||
inStream->Read(lxfmlSize);
|
||||
uint8_t* inData = static_cast<uint8_t*>(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 +2459,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 +2505,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 +2523,7 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream* inStream, Entity* ent
|
||||
propertyId = propertyEntry->getUInt64(1);
|
||||
}
|
||||
|
||||
delete propertyEntry;
|
||||
delete propertyLookup;
|
||||
|
||||
//Insert into ugc:
|
||||
@ -2543,7 +2534,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 +2549,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 +2582,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<unsigned int>(0);
|
||||
bitStream.Write<unsigned int>(1);
|
||||
bitStream.Write(blueprintID);
|
||||
|
||||
bitStream.Write(lxfmlSize + 9);
|
||||
bitStream.Write<uint32_t>(sd0Size);
|
||||
|
||||
//Write a fake sd0 header:
|
||||
bitStream.Write<unsigned char>(0x73); //s
|
||||
bitStream.Write<unsigned char>(0x64); //d
|
||||
bitStream.Write<unsigned char>(0x30); //0
|
||||
bitStream.Write<unsigned char>(0x01); //1
|
||||
bitStream.Write<unsigned char>(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 +2628,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);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -85,71 +85,78 @@ 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;
|
||||
}
|
||||
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");
|
||||
MigrationRunner::RunMigrations();
|
||||
|
||||
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;
|
||||
}
|
||||
//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. Looking for cdclient.fdb to convert to sqlite.", cdclient_path.c_str());
|
||||
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());
|
||||
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();
|
||||
|
||||
//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());
|
||||
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;
|
||||
}
|
||||
|
||||
//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());
|
||||
if (std::rename("./cdclient.sqlite", "./res/CDServer.sqlite") != 0) {
|
||||
Game::logger->Log("MasterServer", "failed to move cdclient file.");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
// Run migrations should any need to be run.
|
||||
MigrationRunner::RunSQLiteMigrations();
|
||||
|
||||
//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 +832,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;
|
||||
|
@ -1069,15 +1069,6 @@ void HandlePacket(Packet* packet) {
|
||||
bitStream.Write<unsigned int>(1);
|
||||
bitStream.Write(blueprintID);
|
||||
|
||||
bitStream.Write<uint32_t>(lxfmlSize + 9);
|
||||
|
||||
//Write a fake sd0 header:
|
||||
bitStream.Write<unsigned char>(0x73); //s
|
||||
bitStream.Write<unsigned char>(0x64); //d
|
||||
bitStream.Write<unsigned char>(0x30); //0
|
||||
bitStream.Write<unsigned char>(0x01); //1
|
||||
bitStream.Write<unsigned char>(0xFF); //end magic
|
||||
|
||||
bitStream.Write<uint32_t>(lxfmlSize);
|
||||
|
||||
for (size_t i = 0; i < lxfmlSize; ++i)
|
||||
|
@ -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") {
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
1
migrations/dlu/5_brick_model_sd0.sql
Normal file
1
migrations/dlu/5_brick_model_sd0.sql
Normal file
@ -0,0 +1 @@
|
||||
# This file is here as a mock. The real migration is located in BrickByBrickFix.cpp
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
23
resources/sharedconfig.ini
Normal file
23
resources/sharedconfig.ini
Normal file
@ -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=
|
@ -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
|
||||
max_number_of_best_friends=5
|
||||
|
@ -146,8 +146,8 @@ int ReadAMFArrayFromBitStream() {
|
||||
ASSERT_EQ(res->GetValueType(), AMFValueType::AMFArray);
|
||||
ASSERT_EQ(static_cast<AMFArrayValue*>(res.get())->GetAssociativeMap().size(), 1);
|
||||
ASSERT_EQ(static_cast<AMFArrayValue*>(res.get())->GetDenseArray().size(), 1);
|
||||
ASSERT_EQ(static_cast<AMFStringValue*>(static_cast<AMFArrayValue*>(res.get())->FindValue("BehaviorID"))->GetStringValue(), "10447");
|
||||
ASSERT_EQ(static_cast<AMFStringValue*>(static_cast<AMFArrayValue*>(res.get())->GetDenseArray()[0])->GetStringValue(), "10447");
|
||||
ASSERT_EQ(static_cast<AMFArrayValue*>(res.get())->FindValue<AMFStringValue>("BehaviorID")->GetStringValue(), "10447");
|
||||
ASSERT_EQ(static_cast<AMFArrayValue*>(res.get())->GetValueAt<AMFStringValue>(0)->GetStringValue(), "10447");
|
||||
}
|
||||
// Test a dense array
|
||||
return 0;
|
||||
@ -219,102 +219,103 @@ int TestLiveCapture() {
|
||||
auto result = static_cast<AMFArrayValue*>(resultFromFn.get());
|
||||
// Test the outermost array
|
||||
|
||||
ASSERT_EQ(dynamic_cast<AMFStringValue*>(result->FindValue("BehaviorID"))->GetStringValue(), "10447");
|
||||
ASSERT_EQ(dynamic_cast<AMFStringValue*>(result->FindValue("objectID"))->GetStringValue(), "288300744895913279")
|
||||
ASSERT_EQ(result->FindValue<AMFStringValue>("BehaviorID")->GetStringValue(), "10447");
|
||||
ASSERT_EQ(result->FindValue<AMFStringValue>("objectID")->GetStringValue(), "288300744895913279");
|
||||
|
||||
// Test the execution state array
|
||||
auto executionState = result->FindValue<AMFArrayValue>("executionState");
|
||||
|
||||
// Test the execution state array
|
||||
auto executionState = dynamic_cast<AMFArrayValue*>(result->FindValue("executionState"));
|
||||
ASSERT_NE(executionState, nullptr);
|
||||
|
||||
auto strips = dynamic_cast<AMFArrayValue*>(executionState->FindValue("strips"))->GetDenseArray();
|
||||
auto strips = executionState->FindValue<AMFArrayValue>("strips")->GetDenseArray();
|
||||
|
||||
ASSERT_EQ(strips.size(), 1);
|
||||
|
||||
auto stripsPosition0 = dynamic_cast<AMFArrayValue*>(strips[0]);
|
||||
|
||||
auto actionIndex = dynamic_cast<AMFDoubleValue*>(stripsPosition0->FindValue("actionIndex"));
|
||||
auto actionIndex = stripsPosition0->FindValue<AMFDoubleValue>("actionIndex");
|
||||
|
||||
ASSERT_EQ(actionIndex->GetDoubleValue(), 0.0f);
|
||||
|
||||
auto stripIDExecution = dynamic_cast<AMFDoubleValue*>(stripsPosition0->FindValue("id"));
|
||||
auto stripIDExecution = stripsPosition0->FindValue<AMFDoubleValue>("id");
|
||||
|
||||
ASSERT_EQ(stripIDExecution->GetDoubleValue(), 0.0f);
|
||||
|
||||
auto stateIDExecution = dynamic_cast<AMFDoubleValue*>(executionState->FindValue("stateID"));
|
||||
auto stateIDExecution = executionState->FindValue<AMFDoubleValue>("stateID");
|
||||
|
||||
ASSERT_EQ(stateIDExecution->GetDoubleValue(), 0.0f);
|
||||
|
||||
auto states = dynamic_cast<AMFArrayValue*>(result->FindValue("states"))->GetDenseArray();
|
||||
auto states = result->FindValue<AMFArrayValue>("states")->GetDenseArray();
|
||||
|
||||
ASSERT_EQ(states.size(), 1);
|
||||
|
||||
auto firstState = dynamic_cast<AMFArrayValue*>(states[0]);
|
||||
|
||||
auto stateID = dynamic_cast<AMFDoubleValue*>(firstState->FindValue("id"));
|
||||
auto stateID = firstState->FindValue<AMFDoubleValue>("id");
|
||||
|
||||
ASSERT_EQ(stateID->GetDoubleValue(), 0.0f);
|
||||
|
||||
auto stripsInState = dynamic_cast<AMFArrayValue*>(firstState->FindValue("strips"))->GetDenseArray();
|
||||
auto stripsInState = firstState->FindValue<AMFArrayValue>("strips")->GetDenseArray();
|
||||
|
||||
ASSERT_EQ(stripsInState.size(), 1);
|
||||
|
||||
auto firstStrip = dynamic_cast<AMFArrayValue*>(stripsInState[0]);
|
||||
|
||||
auto actionsInFirstStrip = dynamic_cast<AMFArrayValue*>(firstStrip->FindValue("actions"))->GetDenseArray();
|
||||
auto actionsInFirstStrip = firstStrip->FindValue<AMFArrayValue>("actions")->GetDenseArray();
|
||||
|
||||
ASSERT_EQ(actionsInFirstStrip.size(), 3);
|
||||
|
||||
auto actionID = dynamic_cast<AMFDoubleValue*>(firstStrip->FindValue("id"));
|
||||
auto actionID = firstStrip->FindValue<AMFDoubleValue>("id");
|
||||
|
||||
ASSERT_EQ(actionID->GetDoubleValue(), 0.0f)
|
||||
|
||||
auto uiArray = dynamic_cast<AMFArrayValue*>(firstStrip->FindValue("ui"));
|
||||
auto uiArray = firstStrip->FindValue<AMFArrayValue>("ui");
|
||||
|
||||
auto xPos = dynamic_cast<AMFDoubleValue*>(uiArray->FindValue("x"));
|
||||
auto yPos = dynamic_cast<AMFDoubleValue*>(uiArray->FindValue("y"));
|
||||
auto xPos = uiArray->FindValue<AMFDoubleValue>("x");
|
||||
auto yPos = uiArray->FindValue<AMFDoubleValue>("y");
|
||||
|
||||
ASSERT_EQ(xPos->GetDoubleValue(), 103.0f);
|
||||
ASSERT_EQ(yPos->GetDoubleValue(), 82.0f);
|
||||
|
||||
auto stripID = dynamic_cast<AMFDoubleValue*>(firstStrip->FindValue("id"));
|
||||
auto stripID = firstStrip->FindValue<AMFDoubleValue>("id");
|
||||
|
||||
ASSERT_EQ(stripID->GetDoubleValue(), 0.0f)
|
||||
|
||||
auto firstAction = dynamic_cast<AMFArrayValue*>(actionsInFirstStrip[0]);
|
||||
|
||||
auto firstType = dynamic_cast<AMFStringValue*>(firstAction->FindValue("Type"));
|
||||
auto firstType = firstAction->FindValue<AMFStringValue>("Type");
|
||||
|
||||
ASSERT_EQ(firstType->GetStringValue(), "OnInteract");
|
||||
|
||||
auto firstCallback = dynamic_cast<AMFStringValue*>(firstAction->FindValue("__callbackID__"));
|
||||
auto firstCallback = firstAction->FindValue<AMFStringValue>("__callbackID__");
|
||||
|
||||
ASSERT_EQ(firstCallback->GetStringValue(), "");
|
||||
|
||||
auto secondAction = dynamic_cast<AMFArrayValue*>(actionsInFirstStrip[1]);
|
||||
|
||||
auto secondType = dynamic_cast<AMFStringValue*>(secondAction->FindValue("Type"));
|
||||
auto secondType = secondAction->FindValue<AMFStringValue>("Type");
|
||||
|
||||
ASSERT_EQ(secondType->GetStringValue(), "FlyUp");
|
||||
|
||||
auto secondCallback = dynamic_cast<AMFStringValue*>(secondAction->FindValue("__callbackID__"));
|
||||
auto secondCallback = secondAction->FindValue<AMFStringValue>("__callbackID__");
|
||||
|
||||
ASSERT_EQ(secondCallback->GetStringValue(), "");
|
||||
|
||||
auto secondDistance = dynamic_cast<AMFDoubleValue*>(secondAction->FindValue("Distance"));
|
||||
auto secondDistance = secondAction->FindValue<AMFDoubleValue>("Distance");
|
||||
|
||||
ASSERT_EQ(secondDistance->GetDoubleValue(), 25.0f);
|
||||
|
||||
auto thirdAction = dynamic_cast<AMFArrayValue*>(actionsInFirstStrip[2]);
|
||||
|
||||
auto thirdType = dynamic_cast<AMFStringValue*>(thirdAction->FindValue("Type"));
|
||||
auto thirdType = thirdAction->FindValue<AMFStringValue>("Type");
|
||||
|
||||
ASSERT_EQ(thirdType->GetStringValue(), "FlyDown");
|
||||
|
||||
auto thirdCallback = dynamic_cast<AMFStringValue*>(thirdAction->FindValue("__callbackID__"));
|
||||
auto thirdCallback = thirdAction->FindValue<AMFStringValue>("__callbackID__");
|
||||
|
||||
ASSERT_EQ(thirdCallback->GetStringValue(), "");
|
||||
|
||||
auto thirdDistance = dynamic_cast<AMFDoubleValue*>(thirdAction->FindValue("Distance"));
|
||||
auto thirdDistance = thirdAction->FindValue<AMFDoubleValue>("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<char>(byte);
|
||||
}
|
||||
|
||||
testFileStream.close();
|
||||
|
||||
auto resultFromFn = ReadFromBitStream(&testBitStream);
|
||||
auto result = static_cast<AMFArrayValue*>(resultFromFn.get());
|
||||
|
||||
// Actually a string value.
|
||||
ASSERT_EQ(result->FindValue<AMFDoubleValue>("BehaviorID"), nullptr);
|
||||
|
||||
// Does not exist in the associative portion
|
||||
ASSERT_EQ(result->FindValue<AMFNullValue>("DOES_NOT_EXIST"), nullptr);
|
||||
|
||||
result->PushBackValue(new AMFTrueValue());
|
||||
|
||||
// Exists and is correct type
|
||||
ASSERT_NE(result->GetValueAt<AMFTrueValue>(0), nullptr);
|
||||
|
||||
// Value exists but is wrong typing
|
||||
ASSERT_EQ(result->GetValueAt<AMFFalseValue>(0), nullptr);
|
||||
|
||||
// Value is out of bounds
|
||||
ASSERT_EQ(result->GetValueAt<AMFTrueValue>(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;
|
||||
}
|
||||
|
4
thirdparty/CMakeMariaDBLists.txt
vendored
4
thirdparty/CMakeMariaDBLists.txt
vendored
@ -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}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user