diff --git a/dChatServer/ChatIgnoreList.cpp b/dChatServer/ChatIgnoreList.cpp index 6433da70..661e74f9 100644 --- a/dChatServer/ChatIgnoreList.cpp +++ b/dChatServer/ChatIgnoreList.cpp @@ -3,6 +3,7 @@ #include "MessageType/Chat.h" #include "BitStreamUtils.h" #include "Game.h" +#include "dConfig.h" #include "Logger.h" #include "eObjectBits.h" @@ -72,8 +73,8 @@ void ChatIgnoreList::AddIgnore(Packet* packet) { return; } - constexpr int32_t MAX_IGNORES = 32; - if (receiver.ignoredPlayers.size() > MAX_IGNORES) { + const int32_t MAX_IGNORES = Game::config->GetValue("max_ignores", 32); + if (receiver.ignoredPlayers.size() >= MAX_IGNORES) { LOG_DEBUG("Player %llu has too many ignores", playerId); return; } diff --git a/dChatServer/ChatPacketHandler.cpp b/dChatServer/ChatPacketHandler.cpp index 844631b4..10904f4b 100644 --- a/dChatServer/ChatPacketHandler.cpp +++ b/dChatServer/ChatPacketHandler.cpp @@ -435,6 +435,11 @@ void ChatPacketHandler::HandleChatMessage(Packet* packet) { inStream.IgnoreBytes(4); inStream.Read(channel); inStream.Read(size); + if (size > MAX_MESSAGE_LENGTH) { + LOG("Received a probably spoofed chat message, ignoring msg"); + return; + } + inStream.IgnoreBytes(77); LUWString message(size); @@ -479,6 +484,11 @@ void ChatPacketHandler::HandlePrivateChatMessage(Packet* packet) { if (channel != eChatChannel::PRIVATE_CHAT) LOG("WARNING: Received Private chat with the wrong channel!"); inStream.Read(size); + if (size > MAX_MESSAGE_LENGTH) { + LOG("Received a probably spoofed chat message, ignoring msg"); + return; + } + inStream.IgnoreBytes(77); inStream.Read(LUReceiverName); diff --git a/dChatServer/PlayerContainer.cpp b/dChatServer/PlayerContainer.cpp index 6c75fd1d..148cab2a 100644 --- a/dChatServer/PlayerContainer.cpp +++ b/dChatServer/PlayerContainer.cpp @@ -176,6 +176,7 @@ LWOOBJID PlayerContainer::GetId(const std::u16string& playerName) { return toReturn; } +// TODO Make this a pointer again or do something to make it so you cant edit the LWOOBJID_EMPTY entry? it should be ignored in all cases anyways though... PlayerData& PlayerContainer::GetPlayerDataMutable(const LWOOBJID& playerID) { return m_Players.contains(playerID) ? m_Players[playerID] : m_Players[LWOOBJID_EMPTY]; } diff --git a/dCommon/AMFDeserialize.cpp b/dCommon/AMFDeserialize.cpp index 884a0784..9d46b175 100644 --- a/dCommon/AMFDeserialize.cpp +++ b/dCommon/AMFDeserialize.cpp @@ -3,6 +3,7 @@ #include #include "Amf3.h" +#include "StringifiedEnum.h" /** * AMF3 Reference document https://rtmp.veriskope.com/pdf/amf3-file-format-spec.pdf @@ -53,7 +54,7 @@ std::unique_ptr AMFDeserialize::Read(RakNet::BitStream& inStream) case eAmf::VectorObject: [[fallthrough]]; case eAmf::Dictionary: - throw marker; + throw std::invalid_argument(StringifiedEnum::ToString(marker).data()); default: throw std::invalid_argument("Invalid AMF3 marker" + std::to_string(static_cast(marker))); } @@ -88,6 +89,11 @@ const std::string AMFDeserialize::ReadString(RakNet::BitStream& inStream) { // Right shift by 1 bit to get index if reference or size of next string if value length = length >> 1; if (isReference) { + constexpr int32_t maxStringSize = 1024 * 1024; + if (length > maxStringSize) { + LOG("1MB string attempted to be allocated in AMF deserialize, possible spoof, aborting deserialize."); + throw std::invalid_argument("1MB string attempted to be allocated in AMF deserialize, possible spoof, aborting deserialize."); + } std::string value(length, 0); inStream.Read(&value[0], length); // Empty strings are never sent by reference @@ -117,6 +123,12 @@ std::unique_ptr AMFDeserialize::ReadAmfArray(RakNet::BitStream& i if (key.size() == 0) break; arrayValue->Insert(key, Read(inStream)); } + + constexpr int32_t maxArraySize = 10'000; + if (sizeOfDenseArray > maxArraySize) { + LOG("Someone sent 10,000 dense array entries, probably a bad packet."); + throw std::invalid_argument("Someone sent 10,000 dense array entries, probably a bad packet."); + } // Finally read dense portion for (uint32_t i = 0; i < sizeOfDenseArray; i++) { arrayValue->Insert(i, Read(inStream)); diff --git a/dCommon/BrickByBrickFix.cpp b/dCommon/BrickByBrickFix.cpp index b5a2df9b..d299341f 100644 --- a/dCommon/BrickByBrickFix.cpp +++ b/dCommon/BrickByBrickFix.cpp @@ -52,8 +52,7 @@ uint32_t BrickByBrickFix::TruncateBrokenBrickByBrickXml() { if (actualUncompressedSize != -1) { uint32_t previousSize = completeUncompressedModel.size(); - completeUncompressedModel.append(reinterpret_cast(uncompressedChunk.get())); - completeUncompressedModel.resize(previousSize + actualUncompressedSize); + completeUncompressedModel.append(reinterpret_cast(uncompressedChunk.get()), actualUncompressedSize); } else { LOG("Failed to inflate chunk %i for model %llu. Error: %i", chunkCount, model.id, err); break; diff --git a/dCommon/GeneralUtils.cpp b/dCommon/GeneralUtils.cpp index 7cc9278b..96bf9e01 100644 --- a/dCommon/GeneralUtils.cpp +++ b/dCommon/GeneralUtils.cpp @@ -308,8 +308,9 @@ std::vector GeneralUtils::GetSqlFileNamesFromFolder(const std::stri for (const auto& t : std::filesystem::directory_iterator(folder)) { if (t.is_directory() || t.is_symlink()) continue; auto filename = t.path().filename().string(); - const auto index = std::stoi(GeneralUtils::SplitString(filename, '_').at(0)); - filenames.emplace(index, std::move(filename)); + // Ensure the file has a name in the format of xxxxxxxx_anything_goes_here.sql + const auto migrationNumber = TryParse(GeneralUtils::SplitString(filename, '_').at(0)); + if (migrationNumber.has_value()) filenames.emplace(migrationNumber.value(), std::move(filename)); } // Now sort the map by the oldest migration. diff --git a/dCommon/GeneralUtils.h b/dCommon/GeneralUtils.h index 828fcee3..69534b58 100644 --- a/dCommon/GeneralUtils.h +++ b/dCommon/GeneralUtils.h @@ -205,6 +205,12 @@ namespace GeneralUtils { return isParsed ? static_cast(result) : std::optional{}; } + // A version of TryParse that will return `errorVal` if `str` failed to parse. + template + [[nodiscard]] T TryParse(std::string_view str, const T errorVal) { + return TryParse(str).value_or(errorVal); + } + template requires(!Numeric) [[nodiscard]] std::optional TryParse(std::string_view str); @@ -258,6 +264,11 @@ namespace GeneralUtils { return z ? std::make_optional(x.value(), y.value(), z.value()) : std::nullopt; } + // Alternative overload of TryParse with a default value + [[nodiscard]] inline NiPoint3 TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ, const NiPoint3 errorVal) { + return TryParse(strX, strY, strZ).value_or(errorVal); + } + /** * The TryParse overload for handling NiPoint3 by passing a span of three strings * @param str The string vector representing the X, Y, and Z coordinates @@ -268,6 +279,11 @@ namespace GeneralUtils { return (str.size() == 3) ? TryParse(str[0], str[1], str[2]) : std::nullopt; } + // Alternative overload of TryParse with a default value + [[nodiscard]] inline NiPoint3 TryParse(const std::span str, const NiPoint3 errorVal) { + return TryParse(str).value_or(errorVal); + } + template std::u16string to_u16string(const T value) { return GeneralUtils::ASCIIToUTF16(std::to_string(value)); diff --git a/dCommon/LxfmlBugged.cpp b/dCommon/LxfmlBugged.cpp index f5eeebc8..fcf62bb1 100644 --- a/dCommon/LxfmlBugged.cpp +++ b/dCommon/LxfmlBugged.cpp @@ -53,16 +53,21 @@ Lxfml::Result Lxfml::NormalizePositionOnlyFirstPart(const std::string_view data) continue; } - auto x = GeneralUtils::TryParse(split[9]).value(); - auto y = GeneralUtils::TryParse(split[10]).value(); - auto z = GeneralUtils::TryParse(split[11]).value(); - if (x < lowest.x) lowest.x = x; - if (y < lowest.y) lowest.y = y; - if (z < lowest.z) lowest.z = z; + try { + auto x = GeneralUtils::TryParse(split[9]).value(); + auto y = GeneralUtils::TryParse(split[10]).value(); + auto z = GeneralUtils::TryParse(split[11]).value(); + if (x < lowest.x) lowest.x = x; + if (y < lowest.y) lowest.y = y; + if (z < lowest.z) lowest.z = z; - if (highest.x < x) highest.x = x; - if (highest.y < y) highest.y = y; - if (highest.z < z) highest.z = z; + if (highest.x < x) highest.x = x; + if (highest.y < y) highest.y = y; + if (highest.z < z) highest.z = z; + } catch (std::exception& e) { + LOG("Failed to parse a split value of either (%s), (%s), or (%s).", split[9], split[10], split[11]); + return toReturn; // Early return since we failed to parse this lxfml. + } } auto delta = (highest - lowest) / 2.0f; diff --git a/dCommon/Sd0.cpp b/dCommon/Sd0.cpp index 5b97cb75..5766175d 100644 --- a/dCommon/Sd0.cpp +++ b/dCommon/Sd0.cpp @@ -71,6 +71,7 @@ Sd0::Sd0(std::istream& buffer) { WriteSize(chunk, chunkSize); + // Possible overflow from a massive chunk or allocation of a massive chunk. TODO: fix this chunk.resize(chunkSize + dataOffset); auto* dataStart = reinterpret_cast(chunk.data() + dataOffset); if (!buffer.read(dataStart, chunkSize)) { @@ -95,6 +96,11 @@ void Sd0::FromData(const uint8_t* data, size_t bufferSize) { startOffset, numToCopy, compressedChunk.data(), compressedChunk.size()); + if (compressedSize == -1) { + LOG("Failed to compress chunk, aborting"); + break; + } + auto& chunk = m_Chunks.emplace_back(); bool firstBuffer = m_Chunks.size() == 1; auto dataOffset = GetDataOffset(firstBuffer); @@ -119,6 +125,12 @@ std::string Sd0::GetAsStringUncompressed() const { auto dataOffset = GetDataOffset(first); first = false; const auto chunkSize = chunk.size(); + if (chunkSize <= static_cast(dataOffset)) { + LOG("Bad chunkSize for data, aborting"); + toReturn = ""; + totalSize = 0; + break; + } auto oldSize = toReturn.size(); toReturn.resize(oldSize + MAX_UNCOMPRESSED_CHUNK_SIZE); @@ -128,6 +140,13 @@ std::string Sd0::GetAsStringUncompressed() const { reinterpret_cast(toReturn.data()) + oldSize, MAX_UNCOMPRESSED_CHUNK_SIZE, error); + if (uncompressedSize == -1) { + LOG("Failed to decompress chunk, aborting"); + toReturn = ""; + totalSize = 0; + break; + } + totalSize += uncompressedSize; } diff --git a/dCommon/ZCompression.cpp b/dCommon/ZCompression.cpp index e5b3c8fb..11ec4e83 100644 --- a/dCommon/ZCompression.cpp +++ b/dCommon/ZCompression.cpp @@ -3,12 +3,12 @@ #include "zlib.h" namespace ZCompression { - int32_t GetMaxCompressedLength(int32_t nLenSrc) { - int32_t n16kBlocks = (nLenSrc + 16383) / 16384; // round up any fraction of a block + uint32_t GetMaxCompressedLength(uint32_t nLenSrc) { + uint32_t n16kBlocks = (nLenSrc + 16383) / 16384; // round up any fraction of a block return (nLenSrc + 6 + (n16kBlocks * 5)); } - int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst) { + int32_t Compress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst) { z_stream zInfo = { 0 }; zInfo.total_in = zInfo.avail_in = nLenSrc; zInfo.total_out = zInfo.avail_out = nLenDst; @@ -27,7 +27,7 @@ namespace ZCompression { return(nRet); } - int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr) { + int32_t Decompress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst, int32_t& nErr) { // Get the size of the decompressed data z_stream zInfo = { 0 }; zInfo.total_in = zInfo.avail_in = nLenSrc; diff --git a/dCommon/ZCompression.h b/dCommon/ZCompression.h index 22a5ff86..d58a8778 100644 --- a/dCommon/ZCompression.h +++ b/dCommon/ZCompression.h @@ -3,10 +3,10 @@ #include namespace ZCompression { - int32_t GetMaxCompressedLength(int32_t nLenSrc); + uint32_t GetMaxCompressedLength(uint32_t nLenSrc); - int32_t Compress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst); + int32_t Compress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst); - int32_t Decompress(const uint8_t* abSrc, int32_t nLenSrc, uint8_t* abDst, int32_t nLenDst, int32_t& nErr); + int32_t Decompress(const uint8_t* abSrc, uint32_t nLenSrc, uint8_t* abDst, uint32_t nLenDst, int32_t& nErr); } diff --git a/dCommon/dClient/AssetManager.cpp b/dCommon/dClient/AssetManager.cpp index bc4fbf35..92a458d3 100644 --- a/dCommon/dClient/AssetManager.cpp +++ b/dCommon/dClient/AssetManager.cpp @@ -7,7 +7,7 @@ #include "zlib.h" constexpr uint32_t CRC32_INIT = 0xFFFFFFFF; -constexpr auto NULL_TERMINATOR = std::string_view{"\0\0\0", 4}; +constexpr auto NULL_TERMINATOR = std::string_view{ "\0\0\0", 4 }; AssetManager::AssetManager(const std::filesystem::path& path) { if (!std::filesystem::is_directory(path)) { @@ -25,7 +25,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) { if (!std::filesystem::exists(m_Path / ".." / "versions")) { throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded."); } - + m_AssetBundleType = eAssetBundleType::Packed; m_RootPath = (m_Path / ".."); @@ -34,7 +34,7 @@ AssetManager::AssetManager(const std::filesystem::path& path) { if (!std::filesystem::exists(m_Path / ".." / ".." / "versions")) { throw std::runtime_error("No \"versions\" directory found in the parent directories of \"res\" - packed asset bundle cannot be loaded."); } - + m_AssetBundleType = eAssetBundleType::Packed; m_RootPath = (m_Path / ".." / ".."); @@ -54,15 +54,15 @@ AssetManager::AssetManager(const std::filesystem::path& path) { } switch (m_AssetBundleType) { - case eAssetBundleType::Packed: { - this->LoadPackIndex(); - break; - } - case eAssetBundleType::None: - [[fallthrough]]; - case eAssetBundleType::Unpacked: { - break; - } + case eAssetBundleType::Packed: { + this->LoadPackIndex(); + break; + } + case eAssetBundleType::None: + [[fallthrough]]; + case eAssetBundleType::Unpacked: { + break; + } } } @@ -79,7 +79,7 @@ bool AssetManager::HasFile(std::string fixedName) const { std::replace(fixedName.begin(), fixedName.end(), '\\', '/'); if (std::filesystem::exists(m_ResPath / fixedName)) return true; - if (this->m_AssetBundleType == eAssetBundleType::Unpacked) return false; + if (this->m_AssetBundleType == eAssetBundleType::Unpacked || !m_PackIndex) return false; std::replace(fixedName.begin(), fixedName.end(), '/', '\\'); if (fixedName.rfind("client\\res\\", 0) != 0) fixedName = "client\\res\\" + fixedName; @@ -145,8 +145,12 @@ bool AssetManager::GetFile(std::string fixedName, char** data, uint32_t* len) co } const auto& pack = this->m_PackIndex->GetPacks().at(packIndex); - const bool success = pack.ReadFileFromPack(crc, data, len); - + bool success = false; + try { + success = pack.ReadFileFromPack(crc, data, len); + } catch (std::exception& e) { + LOG("Failed to read file %s from pack file", fixedName.c_str()); + } return success; } diff --git a/dCommon/dClient/Pack.cpp b/dCommon/dClient/Pack.cpp index 800cfa19..0f558a54 100644 --- a/dCommon/dClient/Pack.cpp +++ b/dCommon/dClient/Pack.cpp @@ -46,6 +46,7 @@ bool Pack::HasFile(const uint32_t crc) const { } bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) const { + const auto pathStr = m_FilePath.string(); // Time for some wacky C file reading for speed reasons PackRecord pkRecord{}; @@ -65,16 +66,21 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons bool isCompressed = (pkRecord.m_IsCompressed & 0xff) > 0; auto inPackSize = isCompressed ? pkRecord.m_CompressedSize : pkRecord.m_UncompressedSize; - FILE* file; + FILE* file = nullptr; #ifdef _WIN32 - fopen_s(&file, m_FilePath.string().c_str(), "rb"); + fopen_s(&file, pathStr.c_str(), "rb"); #elif __APPLE__ // macOS has 64bit file IO by default - file = fopen(m_FilePath.string().c_str(), "rb"); + file = fopen(pathStr.c_str(), "rb"); #else - file = fopen64(m_FilePath.string().c_str(), "rb"); + file = fopen64(pathStr.c_str(), "rb"); #endif + if (!file) { + LOG("No file found for path %s", pathStr.c_str()); + throw std::runtime_error("Could not find file " + pathStr); + } + fseek(file, pos, SEEK_SET); if (!isCompressed) { @@ -102,14 +108,18 @@ bool Pack::ReadFileFromPack(const uint32_t crc, char** data, uint32_t* len) cons int32_t readInData = fread(&size, sizeof(uint32_t), 1, file); pos += 4; // Move pointer position 4 to the right - char* chunk = static_cast(malloc(size)); - int32_t readInData2 = fread(chunk, sizeof(int8_t), size, file); + std::unique_ptr chunk(new char[size]); + int32_t readInData2 = fread(chunk.get(), sizeof(int8_t), size, file); pos += size; // Move pointer position the amount of bytes read to the right int32_t err; - currentReadPos += ZCompression::Decompress(reinterpret_cast(chunk), size, reinterpret_cast(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err); + const auto countToRead = ZCompression::Decompress(reinterpret_cast(chunk.get()), size, reinterpret_cast(decompressedData + currentReadPos), Sd0::MAX_UNCOMPRESSED_CHUNK_SIZE, err); + if (countToRead == -1) { + LOG("Error decompressing zlib data from file %s", pathStr.c_str()); + throw std::runtime_error("Error decompressing zlib data from file " + pathStr); + } + currentReadPos += countToRead; - free(chunk); } *data = decompressedData; diff --git a/dCommon/dConfig.cpp b/dCommon/dConfig.cpp index 7ebfad25..21a0746d 100644 --- a/dCommon/dConfig.cpp +++ b/dCommon/dConfig.cpp @@ -84,3 +84,7 @@ void dConfig::ProcessLine(const std::string& line) { this->m_ConfigValues.insert(std::make_pair(key, value)); } + +std::string dConfig::GetValue(const std::string& key, const char* emptyValue) { + return GetValue(key, std::string(emptyValue)); +}; diff --git a/dCommon/dConfig.h b/dCommon/dConfig.h index 36c94148..8e645caa 100644 --- a/dCommon/dConfig.h +++ b/dCommon/dConfig.h @@ -5,6 +5,8 @@ #include #include +#include "GeneralUtils.h" + class dConfig { public: dConfig(const std::string& filepath); @@ -22,6 +24,14 @@ public: */ const std::string& GetValue(std::string key); + // Gets a value from the config and returns the parsed value, or the default value should parsing have failed. + template + T GetValue(const std::string& key, const T emptyValue = T()) { + return GeneralUtils::TryParse(GetValue(key)).value_or(emptyValue); + } + + std::string GetValue(const std::string& key, const char* emptyValue); + /** * Loads the config from a file */ @@ -43,3 +53,9 @@ private: std::vector> m_ConfigHandlers; std::string m_ConfigFilePath; }; + +template<> +inline std::string dConfig::GetValue(const std::string& key, const std::string emptyValue) { + const auto& value = GetValue(key); + return value.empty() ? emptyValue : value; +}; diff --git a/dCommon/dEnums/dCommonVars.h b/dCommon/dEnums/dCommonVars.h index c00c4be5..fc1ccded 100644 --- a/dCommon/dEnums/dCommonVars.h +++ b/dCommon/dEnums/dCommonVars.h @@ -16,8 +16,8 @@ // These are the same define, but they mean two different things in different contexts // so a different define to distinguish what calculation is happening will help clarity. -#define FRAMES_TO_MS(x) 1000 / x -#define MS_TO_FRAMES(x) 1000 / x +#define FRAMES_TO_MS(x) (1000 / (x)) +#define MS_TO_FRAMES(x) (1000 / (x)) //=========== FRAME TIMINGS =========== constexpr uint32_t highFramerate = 60; diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDItemComponentTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDItemComponentTable.cpp index 0d8b1ad9..8b69e262 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDItemComponentTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDItemComponentTable.cpp @@ -154,8 +154,8 @@ std::map CDItemComponentTable::ParseCraftingCurrencies(const CDIt // Checking for 2 here, not sure what to do when there's more stuff than expected if (amountSplit.size() == 2) { currencies.insert({ - std::stoull(amountSplit[0]), - std::stoi(amountSplit[1]) + GeneralUtils::TryParse(amountSplit[0], LOT_NULL), + GeneralUtils::TryParse(amountSplit[1], 0) }); } } diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp index c98254ea..6466d64c 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.cpp @@ -93,13 +93,14 @@ std::vector CDMissionsTable::Query(std::function p } const CDMissions* CDMissionsTable::GetPtrByMissionID(uint32_t missionID) const { + const CDMissions* toReturn = &Default; for (const auto& entry : GetEntries()) { if (entry.id == missionID) { - return const_cast(&entry); + toReturn = &entry; } } - return &Default; + return toReturn; } const CDMissions& CDMissionsTable::GetByMissionID(uint32_t missionID, bool& found) const { diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h index c5ae0e88..fea37bd6 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h +++ b/dDatabase/CDClientDatabase/CDClientTables/CDMissionsTable.h @@ -66,6 +66,7 @@ public: // Queries the table with a custom "where" clause std::vector Query(std::function predicate); + // Cannot be null. const CDMissions* GetPtrByMissionID(uint32_t missionID) const; const CDMissions& GetByMissionID(uint32_t missionID, bool& found) const; diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDObjectsTable.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDObjectsTable.cpp index 738a13ac..ede9dad7 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDObjectsTable.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDObjectsTable.cpp @@ -69,7 +69,7 @@ const CDObjects& CDObjectsTable::GetByID(const uint32_t lot) { entry.name = tableData.getStringField("name", ""); UNUSED(entry.placeable = tableData.getIntField("placeable", -1)); entry.type = tableData.getStringField("type", ""); - UNUSED(ntry.description = tableData.getStringField(4, "")); + UNUSED(entry.description = tableData.getStringField(4, "")); UNUSED(entry.localize = tableData.getIntField("localize", -1)); UNUSED(entry.npcTemplateID = tableData.getIntField("npcTemplateID", -1)); UNUSED(entry.displayName = tableData.getStringField("displayName", "")); diff --git a/dDatabase/CDClientDatabase/CDClientTables/CDRailActivatorComponent.cpp b/dDatabase/CDClientDatabase/CDClientTables/CDRailActivatorComponent.cpp index 72a26beb..277a5cee 100644 --- a/dDatabase/CDClientDatabase/CDClientTables/CDRailActivatorComponent.cpp +++ b/dDatabase/CDClientDatabase/CDClientTables/CDRailActivatorComponent.cpp @@ -56,7 +56,7 @@ CDRailActivatorComponent CDRailActivatorComponentTable::GetEntryByID(int32_t id) std::pair CDRailActivatorComponentTable::EffectPairFromString(std::string& str) { const auto split = GeneralUtils::SplitString(str, ':'); if (split.size() == 2) { - return { std::stoi(split.at(0)), GeneralUtils::ASCIIToUTF16(split.at(1)) }; + return { GeneralUtils::TryParse(split.at(0), 0), GeneralUtils::ASCIIToUTF16(split.at(1)) }; } return {}; diff --git a/dDatabase/GameDatabase/ITables/IProperty.h b/dDatabase/GameDatabase/ITables/IProperty.h index c4957e5b..24171576 100644 --- a/dDatabase/GameDatabase/ITables/IProperty.h +++ b/dDatabase/GameDatabase/ITables/IProperty.h @@ -34,6 +34,7 @@ public: }; struct PropertyEntranceResult { + // This is the number of entries that are in the query IF it were ran without a limit. int32_t totalEntriesMatchingQuery{}; // The entries that match the query. This should only contain up to 12 entries. std::vector entries; @@ -48,7 +49,7 @@ public: // Get the properties for the given property lookup params. // This is expected to return a result set of up to 12 properties // so as not to transfer too much data at once. - virtual std::optional GetProperties(const PropertyLookup& params) = 0; + virtual IProperty::PropertyEntranceResult GetProperties(const PropertyLookup& params) = 0; // Update the property moderation info for the given property id. virtual void UpdatePropertyModerationInfo(const IProperty::Info& info) = 0; diff --git a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h index b481877c..accfb27e 100644 --- a/dDatabase/GameDatabase/MySQL/MySQLDatabase.h +++ b/dDatabase/GameDatabase/MySQL/MySQLDatabase.h @@ -127,7 +127,7 @@ public: std::string GetBehavior(const LWOOBJID behaviorId) override; void RemoveBehavior(const LWOOBJID characterId) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; - std::optional GetProperties(const IProperty::PropertyLookup& params) override; + IProperty::PropertyEntranceResult GetProperties(const IProperty::PropertyLookup& params) override; std::vector GetDescendingLeaderboard(const uint32_t activityId) override; std::vector GetAscendingLeaderboard(const uint32_t activityId) override; std::vector GetNsLeaderboard(const uint32_t activityId) override; diff --git a/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp b/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp index 27d76899..c6900492 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Mail.cpp @@ -48,7 +48,7 @@ std::vector MySQLDatabase::GetMailForPlayer(const LWOOBJID characterId } std::optional MySQLDatabase::GetMail(const uint64_t mailId) { - auto res = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId); + auto res = ExecuteSelect("SELECT attachment_lot, attachment_count, receiver_id FROM mail WHERE id=? LIMIT 1;", mailId); if (!res->next()) { return std::nullopt; @@ -57,6 +57,7 @@ std::optional MySQLDatabase::GetMail(const uint64_t mailId) { MailInfo toReturn; toReturn.itemLOT = res->getInt("attachment_lot"); toReturn.itemCount = res->getInt("attachment_count"); + toReturn.receiverId = res->getUInt64("receiver_id"); return toReturn; } diff --git a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp index d554a21d..ee958c80 100644 --- a/dDatabase/GameDatabase/MySQL/Tables/Property.cpp +++ b/dDatabase/GameDatabase/MySQL/Tables/Property.cpp @@ -18,8 +18,8 @@ IProperty::Info ReadPropertyInfo(PreparedStmtResultSet& result) { return info; } -std::optional MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) { - std::optional result; +IProperty::PropertyEntranceResult MySQLDatabase::GetProperties(const IProperty::PropertyLookup& params) { + IProperty::PropertyEntranceResult result; std::string query; PreparedStmtResultSet properties; @@ -73,8 +73,7 @@ std::optional MySQLDatabase::GetProperties(co params.playerId ); if (count->next()) { - if (!result) result = IProperty::PropertyEntranceResult(); - result->totalEntriesMatchingQuery = count->getUInt("count"); + result.totalEntriesMatchingQuery = count->getUInt("count"); } } else { if (params.sortChoice == SORT_TYPE_REPUTATION) { @@ -127,14 +126,12 @@ std::optional MySQLDatabase::GetProperties(co params.playerSort ); if (count->next()) { - if (!result) result = IProperty::PropertyEntranceResult(); - result->totalEntriesMatchingQuery = count->getUInt("count"); + result.totalEntriesMatchingQuery = count->getUInt("count"); } } while (properties->next()) { - if (!result) result = IProperty::PropertyEntranceResult(); - result->entries.push_back(ReadPropertyInfo(properties)); + result.entries.push_back(ReadPropertyInfo(properties)); } return result; diff --git a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h index 3b6dc643..afcd0d26 100644 --- a/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h +++ b/dDatabase/GameDatabase/SQLite/SQLiteDatabase.h @@ -111,7 +111,7 @@ public: std::string GetBehavior(const LWOOBJID behaviorId) override; void RemoveBehavior(const LWOOBJID characterId) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; - std::optional GetProperties(const IProperty::PropertyLookup& params) override; + IProperty::PropertyEntranceResult GetProperties(const IProperty::PropertyLookup& params) override; std::vector GetDescendingLeaderboard(const uint32_t activityId) override; std::vector GetAscendingLeaderboard(const uint32_t activityId) override; std::vector GetNsLeaderboard(const uint32_t activityId) override; @@ -170,91 +170,91 @@ private: template<> inline void SetParam(PreppedStmtRef stmt, const int index, const std::string_view param) { - LOG("%s", param.data()); + LOG_DEBUG("%s", param.data()); stmt.bind(index, param.data()); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const char* param) { - LOG("%s", param); + LOG_DEBUG("%s", param); stmt.bind(index, param); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const std::string param) { - LOG("%s", param.c_str()); + LOG_DEBUG("%s", param.c_str()); stmt.bind(index, param.c_str()); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const int8_t param) { - LOG("%u", param); + LOG_DEBUG("%u", param); stmt.bind(index, param); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const uint8_t param) { - LOG("%d", param); + LOG_DEBUG("%d", param); stmt.bind(index, param); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const int16_t param) { - LOG("%u", param); + LOG_DEBUG("%u", param); stmt.bind(index, param); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const uint16_t param) { - LOG("%d", param); + LOG_DEBUG("%d", param); stmt.bind(index, param); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const uint32_t param) { - LOG("%u", param); + LOG_DEBUG("%u", param); stmt.bind(index, static_cast(param)); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const int32_t param) { - LOG("%d", param); + LOG_DEBUG("%d", param); stmt.bind(index, param); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const int64_t param) { - LOG("%llu", param); + LOG_DEBUG("%llu", param); stmt.bind(index, static_cast(param)); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const uint64_t param) { - LOG("%llu", param); + LOG_DEBUG("%llu", param); stmt.bind(index, static_cast(param)); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const float param) { - LOG("%f", param); + LOG_DEBUG("%f", param); stmt.bind(index, param); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const double param) { - LOG("%f", param); + LOG_DEBUG("%f", param); stmt.bind(index, param); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const bool param) { - LOG("%d", param); + LOG_DEBUG("%d", param); stmt.bind(index, param); } template<> inline void SetParam(PreppedStmtRef stmt, const int index, const std::istream* param) { - LOG("Blob"); + LOG_DEBUG("Blob"); // This is the one time you will ever see me use const_cast. std::stringstream stream; stream << param->rdbuf(); @@ -264,10 +264,10 @@ inline void SetParam(PreppedStmtRef stmt, const int index, const std::istream* p template<> inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional param) { if (param) { - LOG("%d", param.value()); + LOG_DEBUG("%d", param.value()); stmt.bind(index, static_cast(param.value())); } else { - LOG("Null"); + LOG_DEBUG("Null"); stmt.bindNull(index); } } @@ -275,10 +275,10 @@ inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional inline void SetParam(PreppedStmtRef stmt, const int index, const std::optional param) { if (param) { - LOG("%d", param.value()); + LOG_DEBUG("%d", param.value()); stmt.bind(index, static_cast(param.value())); } else { - LOG("Null"); + LOG_DEBUG("Null"); stmt.bindNull(index); } } diff --git a/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp b/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp index 7b0a4860..a101beaa 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Mail.cpp @@ -47,7 +47,7 @@ std::vector SQLiteDatabase::GetMailForPlayer(const LWOOBJID characterI } std::optional SQLiteDatabase::GetMail(const uint64_t mailId) { - auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count FROM mail WHERE id=? LIMIT 1;", mailId); + auto [_, res] = ExecuteSelect("SELECT attachment_lot, attachment_count, receiver_id FROM mail WHERE id=? LIMIT 1;", mailId); if (res.eof()) { return std::nullopt; @@ -56,6 +56,7 @@ std::optional SQLiteDatabase::GetMail(const uint64_t mailId) { MailInfo toReturn; toReturn.itemLOT = res.getIntField("attachment_lot"); toReturn.itemCount = res.getIntField("attachment_count"); + toReturn.receiverId = res.getInt64Field("receiver_id"); return toReturn; } diff --git a/dDatabase/GameDatabase/SQLite/Tables/Property.cpp b/dDatabase/GameDatabase/SQLite/Tables/Property.cpp index 67fc57b3..caff8441 100644 --- a/dDatabase/GameDatabase/SQLite/Tables/Property.cpp +++ b/dDatabase/GameDatabase/SQLite/Tables/Property.cpp @@ -18,8 +18,8 @@ IProperty::Info ReadPropertyInfo(CppSQLite3Query& propertyEntry) { return toReturn; } -std::optional SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) { - std::optional result; +IProperty::PropertyEntranceResult SQLiteDatabase::GetProperties(const IProperty::PropertyLookup& params) { + IProperty::PropertyEntranceResult result; std::string query; std::pair propertiesRes; @@ -73,8 +73,7 @@ std::optional SQLiteDatabase::GetProperties(c params.playerId ); if (!count.eof()) { - result = IProperty::PropertyEntranceResult(); - result->totalEntriesMatchingQuery = count.getIntField("count"); + result.totalEntriesMatchingQuery = count.getIntField("count"); } } else { if (params.sortChoice == SORT_TYPE_REPUTATION) { @@ -127,15 +126,13 @@ std::optional SQLiteDatabase::GetProperties(c params.playerSort ); if (!count.eof()) { - result = IProperty::PropertyEntranceResult(); - result->totalEntriesMatchingQuery = count.getIntField("count"); + result.totalEntriesMatchingQuery = count.getIntField("count"); } } auto& [_, properties] = propertiesRes; - if (!properties.eof() && !result.has_value()) result = IProperty::PropertyEntranceResult(); while (!properties.eof()) { - result->entries.push_back(ReadPropertyInfo(properties)); + result.entries.push_back(ReadPropertyInfo(properties)); properties.nextRow(); } diff --git a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h index 2c7890dd..b8706c49 100644 --- a/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h +++ b/dDatabase/GameDatabase/TestSQL/TestSQLDatabase.h @@ -90,7 +90,7 @@ class TestSQLDatabase : public GameDatabase { std::string GetBehavior(const LWOOBJID behaviorId) override; void RemoveBehavior(const LWOOBJID behaviorId) override; void UpdateAccountGmLevel(const uint32_t accountId, const eGameMasterLevel gmLevel) override; - std::optional GetProperties(const IProperty::PropertyLookup& params) override { return {}; }; + IProperty::PropertyEntranceResult GetProperties(const IProperty::PropertyLookup& params) override { return {}; }; std::vector GetDescendingLeaderboard(const uint32_t activityId) override { return {}; }; std::vector GetAscendingLeaderboard(const uint32_t activityId) override { return {}; }; std::vector GetNsLeaderboard(const uint32_t activityId) override { return {}; }; diff --git a/dGame/Entity.h b/dGame/Entity.h index 565c906c..c07bd3f4 100644 --- a/dGame/Entity.h +++ b/dGame/Entity.h @@ -112,6 +112,7 @@ public: uint16_t GetNetworkId() const; + // Cannot return nullptr. Entity* GetOwner() const; const NiPoint3& GetDefaultPosition() const; diff --git a/dGame/dBehaviors/AirMovementBehavior.cpp b/dGame/dBehaviors/AirMovementBehavior.cpp index 46d18680..ad8fe541 100644 --- a/dGame/dBehaviors/AirMovementBehavior.cpp +++ b/dGame/dBehaviors/AirMovementBehavior.cpp @@ -30,6 +30,21 @@ void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream& bitS return; } + // So a player can't send an arbitrary behaviorID in a modified client and cast any behavior on any air behavior + Behavior* toSync = nullptr; + if (m_GroundAction->GetBehaviorID() == behaviorId) { + toSync = m_GroundAction; + } else if (m_HitAction->GetBehaviorID() == behaviorId) { + toSync = m_HitAction; + } else if (m_HitActionEnemy->GetBehaviorID() == behaviorId) { + toSync = m_HitActionEnemy; + } else if (m_TimeoutAction->GetBehaviorID() == behaviorId) { + toSync = m_TimeoutAction; + } else { + LOG("Invalid Air Movement Behavior sync for behaviorID %i on behavior %i", behaviorId, m_behaviorId); + return; + } + LWOOBJID target{}; if (!bitStream.Read(target)) { @@ -37,15 +52,17 @@ void AirMovementBehavior::Sync(BehaviorContext* context, RakNet::BitStream& bitS return; } - auto* behavior = CreateBehavior(behaviorId); - if (Game::entityManager->GetEntity(target) != nullptr) { branch.target = target; } - behavior->Handle(context, bitStream, branch); + toSync->Handle(context, bitStream, branch); } void AirMovementBehavior::Load() { - this->m_Timeout = (GetFloat("timeout_ms") / 1000.0f); + m_Timeout = (GetFloat("timeout_ms") / 1000.0f); + m_GroundAction = GetAction("ground_action"); + m_HitAction = GetAction("hit_action"); + m_HitActionEnemy = GetAction("hit_action_enemy"); + m_TimeoutAction = GetAction("timeout_action"); } diff --git a/dGame/dBehaviors/AirMovementBehavior.h b/dGame/dBehaviors/AirMovementBehavior.h index 0edb1a76..cfa2dbf2 100644 --- a/dGame/dBehaviors/AirMovementBehavior.h +++ b/dGame/dBehaviors/AirMovementBehavior.h @@ -15,4 +15,9 @@ public: void Load() override; private: float m_Timeout; + + Behavior* m_GroundAction{}; + Behavior* m_HitAction{}; + Behavior* m_HitActionEnemy{}; + Behavior* m_TimeoutAction{}; }; diff --git a/dGame/dBehaviors/AreaOfEffectBehavior.cpp b/dGame/dBehaviors/AreaOfEffectBehavior.cpp index ce41785f..6f1b76fd 100644 --- a/dGame/dBehaviors/AreaOfEffectBehavior.cpp +++ b/dGame/dBehaviors/AreaOfEffectBehavior.cpp @@ -42,6 +42,7 @@ void AreaOfEffectBehavior::Handle(BehaviorContext* context, RakNet::BitStream& b LWOOBJID target{}; if (!bitStream.Read(target)) { LOG("failed to read in target %i from bitStream, aborting target Handle!", i); + continue; }; targets.push_back(target); } diff --git a/dGame/dBehaviors/BasicAttackBehavior.cpp b/dGame/dBehaviors/BasicAttackBehavior.cpp index 41b02e7c..e265a034 100644 --- a/dGame/dBehaviors/BasicAttackBehavior.cpp +++ b/dGame/dBehaviors/BasicAttackBehavior.cpp @@ -68,7 +68,7 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit } if (isBlocked) { - destroyableComponent->SetAttacksToBlock(std::min(destroyableComponent->GetAttacksToBlock() - 1, 0U)); + destroyableComponent->SetAttacksToBlock(std::max(static_cast(destroyableComponent->GetAttacksToBlock() - 1), 0)); Game::entityManager->SerializeEntity(targetEntity); this->m_OnFailBlocked->Handle(context, bitStream, branch); return; @@ -103,9 +103,10 @@ void BasicAttackBehavior::DoHandleBehavior(BehaviorContext* context, RakNet::Bit return; } - uint32_t totalDamageDealt = armorDamageDealt + healthDamageDealt; + uint64_t totalDamageDealt = armorDamageDealt + healthDamageDealt; // A value that's too large may be a cheating attempt, so we set it to MIN + // Can't overflow here either because should we somehow get to a 64 bit number it'll be clamped to a sane value. if (totalDamageDealt > this->m_MaxDamage) { totalDamageDealt = this->m_MinDamage; } diff --git a/dGame/dBehaviors/BlockBehavior.cpp b/dGame/dBehaviors/BlockBehavior.cpp index 0d3e164b..8a7c8a64 100644 --- a/dGame/dBehaviors/BlockBehavior.cpp +++ b/dGame/dBehaviors/BlockBehavior.cpp @@ -48,15 +48,13 @@ void BlockBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branc return; } - auto* destroyableComponent = entity->GetComponent(); + auto* const destroyableComponent = entity->GetComponent(); - destroyableComponent->SetAttacksToBlock(this->m_numAttacksCanBlock); - - if (destroyableComponent == nullptr) { - return; + if (destroyableComponent) { + // ??? what is going on here? + destroyableComponent->SetAttacksToBlock(this->m_numAttacksCanBlock); + destroyableComponent->SetAttacksToBlock(0); } - - destroyableComponent->SetAttacksToBlock(0); } void BlockBehavior::Timer(BehaviorContext* context, BehaviorBranchContext branch, LWOOBJID second) { diff --git a/dGame/dBehaviors/ChainBehavior.cpp b/dGame/dBehaviors/ChainBehavior.cpp index feb27988..237c5280 100644 --- a/dGame/dBehaviors/ChainBehavior.cpp +++ b/dGame/dBehaviors/ChainBehavior.cpp @@ -11,6 +11,11 @@ void ChainBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bitStrea return; } + if (chainIndex == 0) { + LOG("Received invalid chain index of 0 for behavior %i.", m_behaviorId); + return; + } + chainIndex--; if (chainIndex < this->m_behaviors.size()) { diff --git a/dGame/dBehaviors/JetPackBehavior.cpp b/dGame/dBehaviors/JetPackBehavior.cpp index 8cfb87da..78f25fcd 100644 --- a/dGame/dBehaviors/JetPackBehavior.cpp +++ b/dGame/dBehaviors/JetPackBehavior.cpp @@ -7,6 +7,7 @@ void JetPackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_stream, const BehaviorBranchContext branch) { auto* entity = Game::entityManager->GetEntity(branch.target); + if (!entity) return; GameMessages::SendSetJetPackMode(entity, true, this->m_BypassChecks, this->m_EnableHover, this->m_effectId, this->m_Airspeed, this->m_MaxAirspeed, this->m_VerticalVelocity, this->m_WarningEffectID); @@ -21,6 +22,7 @@ void JetPackBehavior::Handle(BehaviorContext* context, RakNet::BitStream& bit_st void JetPackBehavior::UnCast(BehaviorContext* context, BehaviorBranchContext branch) { auto* entity = Game::entityManager->GetEntity(branch.target); + if (!entity) return; GameMessages::SendSetJetPackMode(entity, false); diff --git a/dGame/dBehaviors/SwitchMultipleBehavior.cpp b/dGame/dBehaviors/SwitchMultipleBehavior.cpp index 00d639d5..ef7309b5 100644 --- a/dGame/dBehaviors/SwitchMultipleBehavior.cpp +++ b/dGame/dBehaviors/SwitchMultipleBehavior.cpp @@ -17,6 +17,11 @@ void SwitchMultipleBehavior::Handle(BehaviorContext* context, RakNet::BitStream& return; }; + if (m_behaviors.empty()) { + LOG("No behaviors were loaded for %i, aborting call.", m_behaviorId); + return; + } + uint32_t trigger = 0; for (unsigned int i = 0; i < this->m_behaviors.size(); i++) { diff --git a/dGame/dComponents/BaseCombatAIComponent.cpp b/dGame/dComponents/BaseCombatAIComponent.cpp index 2bad800b..5f6399da 100644 --- a/dGame/dComponents/BaseCombatAIComponent.cpp +++ b/dGame/dComponents/BaseCombatAIComponent.cpp @@ -478,6 +478,7 @@ std::vector BaseCombatAIComponent::GetTargetWithinAggroRange() const { for (auto id : m_Parent->GetTargetsInPhantom()) { auto* other = Game::entityManager->GetEntity(id); + if (!other) continue; const auto distance = Vector3::DistanceSquared(m_Parent->GetPosition(), other->GetPosition()); diff --git a/dGame/dComponents/BuffComponent.cpp b/dGame/dComponents/BuffComponent.cpp index bf09d2ea..8d699b54 100644 --- a/dGame/dComponents/BuffComponent.cpp +++ b/dGame/dComponents/BuffComponent.cpp @@ -450,19 +450,10 @@ const std::vector& BuffComponent::GetBuffParameters(int32_t buffI param.value = result.getFloatField("NumberValue"); param.effectId = result.getIntField("EffectID"); - if (!result.fieldIsNull("StringValue")) { - std::istringstream stream(result.getStringField("StringValue")); - std::string token; - - while (std::getline(stream, token, ',')) { - try { - const auto value = std::stof(token); - - param.values.push_back(value); - } catch (std::invalid_argument& exception) { - LOG("Failed to parse value (%s): (%s)!", token.c_str(), exception.what()); - } - } + for (const auto& str : GeneralUtils::SplitString(result.getStringField("StringValue"), ',')) { + if (str.empty()) continue; + const auto value = GeneralUtils::TryParse(str); + if (value) param.values.push_back(value.value()); } parameters.push_back(param); diff --git a/dGame/dComponents/CharacterComponent.cpp b/dGame/dComponents/CharacterComponent.cpp index dda9d559..a703d884 100644 --- a/dGame/dComponents/CharacterComponent.cpp +++ b/dGame/dComponents/CharacterComponent.cpp @@ -797,8 +797,14 @@ std::string CharacterComponent::StatisticsToString() const { return result.str(); } -uint64_t CharacterComponent::GetStatisticFromSplit(std::vector split, uint32_t index) { - return split.size() > index ? std::stoull(split.at(index)) : 0; +uint64_t CharacterComponent::GetStatisticFromSplit(const std::vector& split, const uint32_t index) { + uint64_t toReturn = 0; + if (index < split.size()) { + const auto parsed = GeneralUtils::TryParse(split[index]); + if (parsed) toReturn = *parsed; + } + + return toReturn; } ZoneStatistics& CharacterComponent::GetZoneStatisticsForMap(LWOMAPID mapID) { diff --git a/dGame/dComponents/CharacterComponent.h b/dGame/dComponents/CharacterComponent.h index 6f5eeaab..a37dda43 100644 --- a/dGame/dComponents/CharacterComponent.h +++ b/dGame/dComponents/CharacterComponent.h @@ -450,7 +450,7 @@ private: * @param index the statistics ID in the string * @return the integer value of this statistic, parsed from the string */ - static uint64_t GetStatisticFromSplit(std::vector split, uint32_t index); + static uint64_t GetStatisticFromSplit(const std::vector& split, const uint32_t index); /** * Gets all the statistics for a certain map, if it doesn't exist, it creates empty stats @@ -526,6 +526,7 @@ private: /** * Total amount of meters traveled by this character + * Should be a double and then truncated so decimals can be tracked */ uint64_t m_MetersTraveled; diff --git a/dGame/dComponents/DestroyableComponent.cpp b/dGame/dComponents/DestroyableComponent.cpp index b4250966..ebccc662 100644 --- a/dGame/dComponents/DestroyableComponent.cpp +++ b/dGame/dComponents/DestroyableComponent.cpp @@ -793,7 +793,7 @@ void DestroyableComponent::Smash(const LWOOBJID source, const eKillType killType std::vector scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPTED_ACTIVITY); for (Entity* scriptEntity : scriptedActs) { - if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds + if (!zoneControl || scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds scriptEntity->GetScript()->OnPlayerDied(scriptEntity, m_Parent); } } @@ -964,6 +964,8 @@ void DestroyableComponent::DoHardcoreModeDrops(const LWOOBJID source) { if (m_Parent->IsPlayer()) { //remove hardcore_lose_uscore_on_death_percent from the player's uscore: auto* character = m_Parent->GetComponent(); + if (!character) return; + auto uscore = character->GetUScore(); auto uscoreToLose = static_cast(uscore * (Game::entityManager->GetHardcoreLoseUscoreOnDeathPercent() / 100.0f)); diff --git a/dGame/dComponents/InventoryComponent.cpp b/dGame/dComponents/InventoryComponent.cpp index 2ff827d8..a7e8d5ed 100644 --- a/dGame/dComponents/InventoryComponent.cpp +++ b/dGame/dComponents/InventoryComponent.cpp @@ -966,8 +966,9 @@ void InventoryComponent::EquipScripts(Item* equippedItem) { auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name); if (!itemScript) { LOG("null script?"); + } else { + itemScript->OnFactionTriggerItemEquipped(m_Parent, equippedItem->GetId()); } - itemScript->OnFactionTriggerItemEquipped(m_Parent, equippedItem->GetId()); } } @@ -981,8 +982,9 @@ void InventoryComponent::UnequipScripts(Item* unequippedItem) { auto* itemScript = CppScripts::GetScript(m_Parent, scriptCompData.script_name); if (!itemScript) { LOG("null script?"); + } else { + itemScript->OnFactionTriggerItemUnequipped(m_Parent, unequippedItem->GetId()); } - itemScript->OnFactionTriggerItemUnequipped(m_Parent, unequippedItem->GetId()); } } @@ -1633,7 +1635,7 @@ void InventoryComponent::LoadPetXml(const tinyxml2::XMLDocument& document) { DatabasePet databasePet; databasePet.lot = lot; databasePet.moderationState = moderationStatus; - databasePet.name = std::string(name); + databasePet.name = name ? name : ""; SetDatabasePet(id, databasePet); diff --git a/dGame/dComponents/LUPExhibitComponent.h b/dGame/dComponents/LUPExhibitComponent.h index 4e93dbf9..f3450304 100644 --- a/dGame/dComponents/LUPExhibitComponent.h +++ b/dGame/dComponents/LUPExhibitComponent.h @@ -22,7 +22,7 @@ public: void NextLUPExhibit(); private: float m_UpdateTimer = 0.0f; - std::array m_LUPExhibits = { 11121, 11295, 11423, 11979 }; + const std::array m_LUPExhibits = { 11121, 11295, 11423, 11979 }; uint8_t m_LUPExhibitIndex = 0; bool m_DirtyLUPExhibit = true; }; diff --git a/dGame/dComponents/MissionComponent.cpp b/dGame/dComponents/MissionComponent.cpp index e7c03379..08c4ce8d 100644 --- a/dGame/dComponents/MissionComponent.cpp +++ b/dGame/dComponents/MissionComponent.cpp @@ -449,45 +449,48 @@ void MissionComponent::LoadFromXml(const tinyxml2::XMLDocument& doc) { if (mis == nullptr) return; - auto* cur = mis->FirstChildElement("cur"); auto* done = mis->FirstChildElement("done"); - - auto* doneM = done->FirstChildElement(); - - while (doneM) { - int missionId; - - doneM->QueryAttribute("id", &missionId); - - auto* mission = new Mission(this, missionId); - - mission->LoadFromXmlDone(*doneM); - - doneM = doneM->NextSiblingElement(); - - m_Missions.insert_or_assign(missionId, mission); - } - - auto* currentM = cur->FirstChildElement(); - - uint32_t missionOrder{}; - while (currentM) { - int missionId; - - currentM->QueryAttribute("id", &missionId); - - auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId); - - mission->LoadFromXmlCur(*currentM); - - if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) { - mission->SetUniqueMissionOrderID(missionOrder); - if (missionOrder > m_LastUsedMissionOrderUID) m_LastUsedMissionOrderUID = missionOrder; + if (done) { + auto* doneM = done->FirstChildElement(); + + while (doneM) { + int missionId; + + doneM->QueryAttribute("id", &missionId); + + auto* mission = new Mission(this, missionId); + + mission->LoadFromXmlDone(*doneM); + + doneM = doneM->NextSiblingElement(); + + m_Missions.insert_or_assign(missionId, mission); } + } + + auto* cur = mis->FirstChildElement("cur"); + if (cur) { + auto* currentM = cur->FirstChildElement(); - currentM = currentM->NextSiblingElement(); + uint32_t missionOrder{}; + while (currentM) { + int missionId; - m_Missions.insert_or_assign(missionId, mission); + currentM->QueryAttribute("id", &missionId); + + auto* mission = m_Missions.contains(missionId) ? m_Missions[missionId] : new Mission(this, missionId); + + mission->LoadFromXmlCur(*currentM); + + if (currentM->QueryAttribute("o", &missionOrder) == tinyxml2::XML_SUCCESS && mission->IsMission()) { + mission->SetUniqueMissionOrderID(missionOrder); + if (missionOrder > m_LastUsedMissionOrderUID) m_LastUsedMissionOrderUID = missionOrder; + } + + currentM = currentM->NextSiblingElement(); + + m_Missions.insert_or_assign(missionId, mission); + } } } diff --git a/dGame/dComponents/MultiZoneEntranceComponent.cpp b/dGame/dComponents/MultiZoneEntranceComponent.cpp index f8dabf18..674af099 100644 --- a/dGame/dComponents/MultiZoneEntranceComponent.cpp +++ b/dGame/dComponents/MultiZoneEntranceComponent.cpp @@ -17,7 +17,10 @@ MultiZoneEntranceComponent::MultiZoneEntranceComponent(Entity* parent, const int MultiZoneEntranceComponent::~MultiZoneEntranceComponent() {} void MultiZoneEntranceComponent::OnUse(Entity* originator) { - auto* rocket = originator->GetComponent()->RocketEquip(originator); + auto* const characterComponent = originator->GetComponent(); + if (!characterComponent) return; + + auto* rocket = characterComponent->RocketEquip(originator); if (!rocket) return; // the LUP world menu is just the property menu, the client knows how to handle it @@ -26,7 +29,7 @@ void MultiZoneEntranceComponent::OnUse(Entity* originator) { void MultiZoneEntranceComponent::OnSelectWorld(Entity* originator, uint32_t index) { auto* rocketLaunchpadControlComponent = m_Parent->GetComponent(); - if (!rocketLaunchpadControlComponent) return; + if (!rocketLaunchpadControlComponent || index >= m_LUPWorlds.size()) return; rocketLaunchpadControlComponent->Launch(originator, m_LUPWorlds[index], 0); } diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index 6fb5eedf..cd2b50b9 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -922,7 +922,9 @@ void PetComponent::Deactivate() { } void PetComponent::Release() { - auto* inventoryComponent = GetOwner()->GetComponent(); + auto* const owner = GetOwner(); + if (!owner) return; + auto* const inventoryComponent = owner->GetComponent(); if (inventoryComponent == nullptr) { return; @@ -932,9 +934,9 @@ void PetComponent::Release() { inventoryComponent->RemoveDatabasePet(m_DatabaseId); - auto* item = inventoryComponent->FindItemBySubKey(m_DatabaseId); + auto* const item = inventoryComponent->FindItemBySubKey(m_DatabaseId); - item->SetCount(0, false, false); + if (item) item->SetCount(0, false, false); } void PetComponent::Command(const NiPoint3& position, const LWOOBJID source, const int32_t commandType, const int32_t typeId, const bool overrideObey) { diff --git a/dGame/dComponents/PhantomPhysicsComponent.cpp b/dGame/dComponents/PhantomPhysicsComponent.cpp index 01ef7dfd..bae79d8d 100644 --- a/dGame/dComponents/PhantomPhysicsComponent.cpp +++ b/dGame/dComponents/PhantomPhysicsComponent.cpp @@ -58,31 +58,15 @@ PhantomPhysicsComponent::PhantomPhysicsComponent(Entity* parent, const int32_t c } if (m_IsRespawnVolume) { - { - auto respawnString = std::stringstream(m_Parent->GetVarAsString(u"rspPos")); + const auto respawnPos = GeneralUtils::SplitString(m_Parent->GetVarAsString(u"rspPos"), '\x1f'); + m_RespawnPos = GeneralUtils::TryParse(respawnPos, NiPoint3Constant::ZERO); - std::string segment; - std::vector seglist; - - while (std::getline(respawnString, segment, '\x1f')) { - seglist.push_back(segment); - } - - m_RespawnPos = NiPoint3(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2])); - } - - { - auto respawnString = std::stringstream(m_Parent->GetVarAsString(u"rspRot")); - - std::string segment; - std::vector seglist; - - while (std::getline(respawnString, segment, '\x1f')) { - seglist.push_back(segment); - } - - m_RespawnRot = NiQuaternion(std::stof(seglist[0]), std::stof(seglist[1]), std::stof(seglist[2]), std::stof(seglist[3])); - } + const auto respawnRot = GeneralUtils::SplitString(m_Parent->GetVarAsString(u"rspRot"), '\x1f'); + m_RespawnRot = NiQuaternion( + GeneralUtils::TryParse(respawnRot[0], 1.0f), + GeneralUtils::TryParse(respawnRot[1], 0.0f), + GeneralUtils::TryParse(respawnRot[2], 0.0f), + GeneralUtils::TryParse(respawnRot[3], 0.0f)); } // HF - RespawnPoints. Legacy respawn entity. diff --git a/dGame/dComponents/PropertyEntranceComponent.cpp b/dGame/dComponents/PropertyEntranceComponent.cpp index 1f3b28a2..f282a73d 100644 --- a/dGame/dComponents/PropertyEntranceComponent.cpp +++ b/dGame/dComponents/PropertyEntranceComponent.cpp @@ -131,7 +131,7 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl const auto lookupResult = Database::Get()->GetProperties(propertyLookup); - for (const auto& propertyEntry : lookupResult->entries) { + for (const auto& propertyEntry : lookupResult.entries) { const auto owner = propertyEntry.ownerId; const auto otherCharacter = Database::Get()->GetCharacterInfo(owner); if (!otherCharacter.has_value()) { @@ -174,5 +174,5 @@ void PropertyEntranceComponent::OnPropertyEntranceSync(Entity* entity, bool incl } // Query here is to figure out whether or not to display the button to go to the next page or not. - GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, lookupResult->totalEntriesMatchingQuery - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr); + GameMessages::SendPropertySelectQuery(m_Parent->GetObjectID(), startIndex, lookupResult.totalEntriesMatchingQuery - (startIndex + numResults) > 0, character->GetPropertyCloneID(), false, true, entries, sysAddr); } diff --git a/dGame/dComponents/PropertyManagementComponent.cpp b/dGame/dComponents/PropertyManagementComponent.cpp index 59b918e6..3c62dc52 100644 --- a/dGame/dComponents/PropertyManagementComponent.cpp +++ b/dGame/dComponents/PropertyManagementComponent.cpp @@ -107,20 +107,12 @@ std::vector PropertyManagementComponent::GetPaths() const { std::vector points; - std::istringstream stream(result.getStringField("path")); - std::string token; - - while (std::getline(stream, token, ' ')) { - try { - auto value = std::stof(token); - - points.push_back(value); - } catch (std::invalid_argument& exception) { - LOG("Failed to parse value (%s): (%s)!", token.c_str(), exception.what()); - } + for (const auto& str : GeneralUtils::SplitString(result.getStringField("path"), ' ')) { + const auto value = GeneralUtils::TryParse(str); + if (value) points.push_back(value.value()); } - for (auto i = 0u; i < points.size(); i += 3) { + for (auto i = 0u; i + 2 < points.size(); i += 3) { paths.emplace_back(points[i], points[i + 1], points[i + 2]); } @@ -780,15 +772,17 @@ void PropertyManagementComponent::OnQueryPropertyData(Entity* originator, const privacy = static_cast(this->privacyOption); if (moderatorRequested) { auto moderationInfo = Database::Get()->GetPropertyInfo(zoneId, cloneId); - if (moderationInfo->rejectionReason != "") { - moderatorRequested = false; - rejectionReason = moderationInfo->rejectionReason; - } else if (moderationInfo->rejectionReason == "" && moderationInfo->modApproved == 1) { - moderatorRequested = false; - rejectionReason = ""; - } else { - moderatorRequested = true; - rejectionReason = ""; + if (moderationInfo) { + if (moderationInfo->rejectionReason != "") { + moderatorRequested = false; + rejectionReason = moderationInfo->rejectionReason; + } else if (moderationInfo->rejectionReason == "" && moderationInfo->modApproved == 1) { + moderatorRequested = false; + rejectionReason = ""; + } else { + moderatorRequested = true; + rejectionReason = ""; + } } } } diff --git a/dGame/dComponents/QuickBuildComponent.cpp b/dGame/dComponents/QuickBuildComponent.cpp index 4f167c17..0940d19d 100644 --- a/dGame/dComponents/QuickBuildComponent.cpp +++ b/dGame/dComponents/QuickBuildComponent.cpp @@ -380,7 +380,7 @@ void QuickBuildComponent::StartQuickBuild(Entity* const user) { m_Builder = user->GetObjectID(); auto* character = user->GetComponent(); - character->SetCurrentActivity(eGameActivity::QUICKBUILDING); + if (character) character->SetCurrentActivity(eGameActivity::QUICKBUILDING); Game::entityManager->SerializeEntity(user); diff --git a/dGame/dComponents/RacingControlComponent.cpp b/dGame/dComponents/RacingControlComponent.cpp index 6d0f675b..06cd02be 100644 --- a/dGame/dComponents/RacingControlComponent.cpp +++ b/dGame/dComponents/RacingControlComponent.cpp @@ -306,7 +306,7 @@ void RacingControlComponent::OnRequestDie(Entity* player, const std::u16string& eKillType::VIOLENT, deathType, 0, 0, 90.0f, false, true, 0); auto* destroyableComponent = vehicle->GetComponent(); - uint32_t respawnImagination = 0; + int32_t respawnImagination = 0; // Reset imagination to half its current value, rounded up to the nearest value divisible by 10, as it was done in live. // Do not actually change the value yet. Do that on respawn. if (destroyableComponent) { diff --git a/dGame/dComponents/TriggerComponent.cpp b/dGame/dComponents/TriggerComponent.cpp index e0bd1c4c..92fa5f49 100644 --- a/dGame/dComponents/TriggerComponent.cpp +++ b/dGame/dComponents/TriggerComponent.cpp @@ -385,26 +385,30 @@ void TriggerComponent::HandleSetPhysicsVolumeEffect(Entity* targetEntity, std::v } phantomPhysicsComponent->SetPhysicsEffectActive(true); ePhysicsEffectType effectType = ePhysicsEffectType::PUSH; - std::transform(argArray.at(0).begin(), argArray.at(0).end(), argArray.at(0).begin(), ::tolower); //Transform to lowercase - if (argArray.at(0) == "push") effectType = ePhysicsEffectType::PUSH; - else if (argArray.at(0) == "attract") effectType = ePhysicsEffectType::ATTRACT; - else if (argArray.at(0) == "repulse") effectType = ePhysicsEffectType::REPULSE; - else if (argArray.at(0) == "gravity") effectType = ePhysicsEffectType::GRAVITY_SCALE; - else if (argArray.at(0) == "friction") effectType = ePhysicsEffectType::FRICTION; + if (!argArray.empty()) { + std::transform(argArray.at(0).begin(), argArray.at(0).end(), argArray.at(0).begin(), ::tolower); //Transform to lowercase + if (argArray.at(0) == "push") effectType = ePhysicsEffectType::PUSH; + else if (argArray.at(0) == "attract") effectType = ePhysicsEffectType::ATTRACT; + else if (argArray.at(0) == "repulse") effectType = ePhysicsEffectType::REPULSE; + else if (argArray.at(0) == "gravity") effectType = ePhysicsEffectType::GRAVITY_SCALE; + else if (argArray.at(0) == "friction") effectType = ePhysicsEffectType::FRICTION; + } phantomPhysicsComponent->SetEffectType(effectType); - phantomPhysicsComponent->SetDirectionalMultiplier(std::stof(argArray.at(1))); + if (argArray.size() > 1) { + phantomPhysicsComponent->SetDirectionalMultiplier(GeneralUtils::TryParse(argArray.at(1), 0.0f)); + } if (argArray.size() > 4) { const NiPoint3 direction = - GeneralUtils::TryParse(argArray.at(2), argArray.at(3), argArray.at(4)).value_or(NiPoint3Constant::ZERO); + GeneralUtils::TryParse(argArray.at(2), argArray.at(3), argArray.at(4), NiPoint3Constant::ZERO); phantomPhysicsComponent->SetDirection(direction); } if (argArray.size() > 5) { - const uint32_t min = GeneralUtils::TryParse(argArray.at(6)).value_or(0); + const uint32_t min = GeneralUtils::TryParse(argArray.at(6), 0); phantomPhysicsComponent->SetMin(min); - const uint32_t max = GeneralUtils::TryParse(argArray.at(7)).value_or(0); + const uint32_t max = GeneralUtils::TryParse(argArray.at(7), 0); phantomPhysicsComponent->SetMax(max); } diff --git a/dGame/dGameMessages/GameMessageHandler.cpp b/dGame/dGameMessages/GameMessageHandler.cpp index 1e89e2a9..40c3e913 100644 --- a/dGame/dGameMessages/GameMessageHandler.cpp +++ b/dGame/dGameMessages/GameMessageHandler.cpp @@ -61,6 +61,11 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System User* usr = UserManager::Instance()->GetUser(sysAddr); + if (!usr) { + LOG("Failed to find a logged in user for (%llu), aborting GM: %4i, %s!", sysAddr.ToString(), messageID, StringifiedEnum::ToString(messageID).data()); + return; + } + if (!entity) { LOG("Failed to find associated entity (%llu), aborting GM: %4i, %s!", objectID, messageID, StringifiedEnum::ToString(messageID).data()); return; @@ -76,7 +81,8 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System if (msg->requiredGmLevel > eGameMasterLevel::CIVILIAN) { auto* usingEntity = Game::entityManager->GetEntity(usr->GetLoggedInChar()); if (!usingEntity || usingEntity->GetGMLevel() < msg->requiredGmLevel) { - LOG("User %s (%llu) does not have the required GM level to execute this command.", usingEntity->GetCharacter()->GetName().c_str(), usingEntity->GetObjectID()); + if (usingEntity) LOG("User %s (%llu) does not have the required GM level to execute this command.", usingEntity->GetCharacter()->GetName().c_str(), usingEntity->GetObjectID()); + else LOG("ObjectID %llu tried to use a gm required message.", usr->GetLoggedInChar()); return; } } @@ -167,8 +173,8 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System GameMessages::SendRestoreToPostLoadStats(entity, sysAddr); - auto* destroyable = entity->GetComponent(); - destroyable->SetImagination(destroyable->GetImagination()); + auto* const destroyable = entity->GetComponent(); + if (destroyable) destroyable->SetImagination(destroyable->GetImagination()); Game::entityManager->SerializeEntity(entity); std::vector racingControllers = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::RACING_CONTROL); @@ -186,7 +192,7 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System std::vector scriptedActs = Game::entityManager->GetEntitiesByComponent(eReplicaComponentType::SCRIPT); for (Entity* scriptEntity : scriptedActs) { - if (scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds + if (!zoneControl || scriptEntity->GetObjectID() != zoneControl->GetObjectID()) { // Don't want to trigger twice on instance worlds scriptEntity->GetScript()->OnPlayerLoaded(scriptEntity, entity); } } @@ -332,9 +338,9 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System if (behaviorId > 0) { auto bs = RakNet::BitStream(reinterpret_cast(&startSkill.sBitStream[0]), startSkill.sBitStream.size(), false); - auto* skillComponent = entity->GetComponent(); + auto* const skillComponent = entity->GetComponent(); - success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID, startSkill.skillID); + if (skillComponent) success = skillComponent->CastPlayerSkill(behaviorId, startSkill.uiSkillHandle, bs, startSkill.optionalTargetID, startSkill.skillID); if (success && entity->GetCharacter()) { DestroyableComponent* destComp = entity->GetComponent(); @@ -387,9 +393,9 @@ void GameMessageHandler::HandleMessage(RakNet::BitStream& inStream, const System if (usr != nullptr) { auto bs = RakNet::BitStream(reinterpret_cast(&sync.sBitStream[0]), sync.sBitStream.size(), false); - auto* skillComponent = entity->GetComponent(); + auto* const skillComponent = entity->GetComponent(); - skillComponent->SyncPlayerSkill(sync.uiSkillHandle, sync.uiBehaviorHandle, bs); + if (skillComponent) skillComponent->SyncPlayerSkill(sync.uiSkillHandle, sync.uiBehaviorHandle, bs); } EchoSyncSkill echo = EchoSyncSkill(); diff --git a/dGame/dGameMessages/GameMessages.cpp b/dGame/dGameMessages/GameMessages.cpp index 8f4fdeb4..3af6cb48 100644 --- a/dGame/dGameMessages/GameMessages.cpp +++ b/dGame/dGameMessages/GameMessages.cpp @@ -2137,6 +2137,7 @@ void GameMessages::HandleUpdatePropertyOrModelForFilterCheck(RakNet::BitStream& inStream.Read(worldId); inStream.Read(descriptionLength); + if (descriptionLength > MAX_MESSAGE_LENGTH) return; for (uint32_t i = 0; i < descriptionLength; ++i) { uint16_t character; inStream.Read(character); @@ -2144,6 +2145,7 @@ void GameMessages::HandleUpdatePropertyOrModelForFilterCheck(RakNet::BitStream& } inStream.Read(nameLength); + if (nameLength > MAX_MESSAGE_LENGTH) return; for (uint32_t i = 0; i < nameLength; ++i) { uint16_t character; inStream.Read(character); @@ -2474,9 +2476,15 @@ void GameMessages::HandleBBBSaveRequest(RakNet::BitStream& inStream, Entity* ent uint32_t sd0Size; inStream.Read(sd0Size); - std::unique_ptr sd0Data(new char[sd0Size]); - if (sd0Data == nullptr) return; + // For the sake of letting players make models as big as they want, only reject if we cant allocate the required memory. + std::unique_ptr sd0Data; + try { + sd0Data.reset(new char[sd0Size]); + } catch (std::exception& e) { + LOG("Failed to allocate sd0 of size %u", sd0Size); + return; + } inStream.ReadAlignedBytes(reinterpret_cast(sd0Data.get()), sd0Size); @@ -2652,6 +2660,7 @@ void GameMessages::HandlePropertyEntranceSync(RakNet::BitStream& inStream, Entit inStream.Read(startIndex); inStream.Read(filterTextLength); + if (filterTextLength > MAX_MESSAGE_LENGTH) return; for (auto i = 0u; i < filterTextLength; i++) { char c; inStream.Read(c); @@ -3040,6 +3049,7 @@ void GameMessages::HandleVerifyAck(RakNet::BitStream& inStream, Entity* entity, uint32_t sBitStreamLength = 0; inStream.Read(sBitStreamLength); + if (sBitStreamLength > MAX_MESSAGE_LENGTH) return; for (uint64_t k = 0; k < sBitStreamLength; k++) { uint8_t character; inStream.Read(character); @@ -3261,6 +3271,7 @@ void GameMessages::HandleClientTradeUpdate(RakNet::BitStream& inStream, Entity* inStream.Read(currency); inStream.Read(itemCount); + if (itemCount > MAX_MESSAGE_LENGTH) return; LOG("Trade update from (%llu) -> (%llu), (%i)", entity->GetObjectID(), currency, itemCount); @@ -3673,6 +3684,7 @@ void GameMessages::HandleRequestSetPetName(RakNet::BitStream& inStream, Entity* inStream.Read(nameLength); + if (nameLength > MAX_MESSAGE_LENGTH) return; for (size_t i = 0; i < nameLength; i++) { char16_t character; inStream.Read(character); @@ -3742,6 +3754,7 @@ void GameMessages::HandleMessageBoxResponse(RakNet::BitStream& inStream, Entity* inStream.Read(iButton); inStream.Read(identifierLength); + if (identifierLength > MAX_MESSAGE_LENGTH) return; for (size_t i = 0; i < identifierLength; i++) { char16_t character; inStream.Read(character); @@ -3749,6 +3762,7 @@ void GameMessages::HandleMessageBoxResponse(RakNet::BitStream& inStream, Entity* } inStream.Read(userDataLength); + if (userDataLength > MAX_MESSAGE_LENGTH) return; for (size_t i = 0; i < userDataLength; i++) { char16_t character; inStream.Read(character); @@ -3796,6 +3810,7 @@ void GameMessages::HandleChoiceBoxRespond(RakNet::BitStream& inStream, Entity* e std::u16string identifier; inStream.Read(buttonIdentifierLength); + if (buttonIdentifierLength > MAX_MESSAGE_LENGTH) return; for (size_t i = 0; i < buttonIdentifierLength; i++) { char16_t character; inStream.Read(character); @@ -3805,6 +3820,7 @@ void GameMessages::HandleChoiceBoxRespond(RakNet::BitStream& inStream, Entity* e inStream.Read(iButton); inStream.Read(identifierLength); + if (identifierLength > MAX_MESSAGE_LENGTH) return; for (size_t i = 0; i < identifierLength; i++) { char16_t character; inStream.Read(character); @@ -4158,7 +4174,13 @@ void GameMessages::HandleUpdatePropertyPerformanceCost(RakNet::BitStream& inStre return; } - Database::Get()->UpdatePerformanceCost(zone->GetZoneID(), performanceCost); + const auto* const propertyManagementComponent = entity->GetComponent(); + const auto* const ownerEntity = propertyManagementComponent ? propertyManagementComponent->GetOwner() : nullptr; + const auto* const character = ownerEntity ? ownerEntity->GetCharacter() : nullptr; + const auto& zoneID = zone->GetZoneID(); + if (character && character->GetPropertyCloneID() == zoneID.GetCloneID()) { + Database::Get()->UpdatePerformanceCost(zoneID, performanceCost); + } } void GameMessages::HandleVehicleNotifyHitImaginationServer(RakNet::BitStream& inStream, Entity* entity, const SystemAddress& sysAddr) { @@ -4783,13 +4805,19 @@ void GameMessages::HandleParseChatMessage(RakNet::BitStream& inStream, Entity* e uint32_t wsStringLength; inStream.Read(wsStringLength); + + if (wsStringLength > MAX_MESSAGE_LENGTH) { + LOG("Max message length reached, capping message."); + wsStringLength = MAX_MESSAGE_LENGTH; + } + for (uint32_t i = 0; i < wsStringLength; ++i) { uint16_t character; inStream.Read(character); wsString.push_back(character); } - if (wsString[0] == L'/') { + if (!wsString.empty() && wsString[0] == L'/') { SlashCommandHandler::HandleChatCommand(wsString, entity, sysAddr); } } @@ -4806,6 +4834,7 @@ void GameMessages::HandleFireEventServerSide(RakNet::BitStream& inStream, Entity LWOOBJID senderID{}; inStream.Read(argsLength); + if (argsLength > MAX_MESSAGE_LENGTH) return; for (uint32_t i = 0; i < argsLength; ++i) { uint16_t character; inStream.Read(character); @@ -5436,7 +5465,7 @@ void GameMessages::HandleModularBuildFinish(RakNet::BitStream& inStream, Entity* std::vector modList; auto& oldPartList = character->GetVar(u"currentModifiedBuild"); bool everyPieceSwapped = !oldPartList.empty(); // If the player didn't put a build in initially, then they should not get this achievement. - if (count >= 3) { + if (count >= 3 && count < 8) { std::u16string modules; for (uint32_t k = 0; k < count; k++) { @@ -5733,6 +5762,7 @@ void GameMessages::HandleMatchRequest(RakNet::BitStream& inStream, Entity* entit inStream.Read(activator); inStream.Read(playerChoicesLen); + if (playerChoicesLen > MAX_MESSAGE_LENGTH) return; for (uint32_t i = 0; i < playerChoicesLen; ++i) { uint16_t character; inStream.Read(character); @@ -5890,7 +5920,7 @@ void GameMessages::HandlePlayerRailArrivedNotification(RakNet::BitStream& inStre const SystemAddress& sysAddr) { uint32_t pathNameLength; inStream.Read(pathNameLength); - + if (pathNameLength > MAX_MESSAGE_LENGTH) return; std::u16string pathName; for (auto k = 0; k < pathNameLength; k++) { uint16_t c; diff --git a/dGame/dInventory/Inventory.cpp b/dGame/dInventory/Inventory.cpp index db83ac58..f5742165 100644 --- a/dGame/dInventory/Inventory.cpp +++ b/dGame/dInventory/Inventory.cpp @@ -75,7 +75,8 @@ uint32_t Inventory::GetLotCount(const LOT lot) const { } void Inventory::SetSize(const uint32_t value) { - free += static_cast(value) - static_cast(size); + const auto delta = static_cast(value) - static_cast(size); + free = static_cast(std::max(0, static_cast(free) + delta)); size = value; diff --git a/dGame/dInventory/Item.cpp b/dGame/dInventory/Item.cpp index 7ee27f01..19b536a4 100644 --- a/dGame/dInventory/Item.cpp +++ b/dGame/dInventory/Item.cpp @@ -401,7 +401,8 @@ void Item::Disassemble(const eInventoryType inventoryType) { const auto deliminator = '+'; while (std::getline(ssData, token, deliminator)) { - const auto modLot = std::stoi(token.substr(2, token.size() - 1)); + if (token.size() <= 2) continue; // invalid token, must have size of at least 3. + const auto modLot = GeneralUtils::TryParse(token.substr(2, token.size() - 1), LOT_NULL); modArray.push_back(modLot); } @@ -440,7 +441,10 @@ void Item::DisassembleModel(uint32_t numToDismantle) { std::vector renderAssetSplit = GeneralUtils::SplitString(renderAsset, '/'); if (renderAssetSplit.empty()) return; - std::string lxfmlPath = "BrickModels" + lxfmlFolderName + "/" + GeneralUtils::SplitString(renderAssetSplit.back(), '.').at(0) + ".lxfml"; + const auto renderAssetSplitSplit = GeneralUtils::SplitString(renderAssetSplit.back(), '.'); + if (renderAssetSplitSplit.empty()) return; + + std::string lxfmlPath = "BrickModels" + lxfmlFolderName + "/" + renderAssetSplitSplit[0] + ".lxfml"; auto file = Game::assetManager->GetFile(lxfmlPath.c_str()); if (!file) { diff --git a/dGame/dInventory/ItemSet.cpp b/dGame/dInventory/ItemSet.cpp index d8d320ed..1544e611 100644 --- a/dGame/dInventory/ItemSet.cpp +++ b/dGame/dInventory/ItemSet.cpp @@ -128,8 +128,8 @@ void ItemSet::OnEquip(const LOT lot) { return; } - auto* skillComponent = m_InventoryComponent->GetParent()->GetComponent(); - auto* missionComponent = m_InventoryComponent->GetParent()->GetComponent(); + auto [skillComponent, missionComponent] = m_InventoryComponent->GetParent()->GetComponentsMut(); + if (!skillComponent || !missionComponent) return; // Nothing to do here if these are null for (const auto skill : skillSet) { auto* skillTable = CDClientManager::GetTable(); diff --git a/dGame/dMission/Mission.cpp b/dGame/dMission/Mission.cpp index e13cc411..4afb2207 100644 --- a/dGame/dMission/Mission.cpp +++ b/dGame/dMission/Mission.cpp @@ -74,23 +74,19 @@ Mission::Mission(MissionComponent* missionComponent, const uint32_t missionId) { void Mission::LoadFromXmlDone(const tinyxml2::XMLElement& element) { // Start custom XML - if (element.Attribute("state") != nullptr) { - m_State = static_cast(std::stoul(element.Attribute("state"))); - } + m_State = static_cast(element.UnsignedAttribute("state")); // End custom XML - if (element.Attribute("cct") != nullptr) { - m_Completions = std::stoul(element.Attribute("cct")); + m_Completions = element.UnsignedAttribute("cct"); - m_Timestamp = std::stoul(element.Attribute("cts")); - } + m_Timestamp = element.UnsignedAttribute("cts"); } void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) { const auto* const character = GetCharacter(); // Start custom XML if (element.Attribute("state") != nullptr) { - m_State = static_cast(std::stoul(element.Attribute("state"))); + m_State = static_cast(element.IntAttribute("state", -1)); } // End custom XML @@ -106,7 +102,7 @@ void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) { const auto type = curTask->GetType(); - auto value = std::stoul(task->Attribute("v")); + auto value = task->UnsignedAttribute("v"); curTask->SetProgress(value, false); task = task->NextSiblingElement(); @@ -114,7 +110,7 @@ void Mission::LoadFromXmlCur(const tinyxml2::XMLElement& element) { if (type == eMissionTaskType::COLLECTION || type == eMissionTaskType::VISIT_PROPERTY) { std::vector uniques; while (task != nullptr && value > 0) { - const auto unique = std::stoul(task->Attribute("v")); + const auto unique = task->UnsignedAttribute("v"); uniques.push_back(unique); diff --git a/dGame/dPropertyBehaviors/PropertyBehavior.cpp b/dGame/dPropertyBehaviors/PropertyBehavior.cpp index 139b6d04..c19c4d2f 100644 --- a/dGame/dPropertyBehaviors/PropertyBehavior.cpp +++ b/dGame/dPropertyBehaviors/PropertyBehavior.cpp @@ -17,50 +17,63 @@ PropertyBehavior::PropertyBehavior(bool _isTemplated) { isTemplated = _isTemplated; } +bool CheckStateRange(const BehaviorState state) { + return state >= BehaviorState::HOME_STATE && state <= BehaviorState::STAR_STATE; +} + template<> void PropertyBehavior::HandleMsg(AddStripMessage& msg) { + if (!CheckStateRange(msg.GetActionContext().GetStateId())) return; m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_LastEditedState = msg.GetActionContext().GetStateId(); }; template<> void PropertyBehavior::HandleMsg(AddActionMessage& msg) { + if (!CheckStateRange(msg.GetActionContext().GetStateId())) return; m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_LastEditedState = msg.GetActionContext().GetStateId(); }; template<> void PropertyBehavior::HandleMsg(RearrangeStripMessage& msg) { + if (!CheckStateRange(msg.GetActionContext().GetStateId())) return; m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_LastEditedState = msg.GetActionContext().GetStateId(); }; template<> void PropertyBehavior::HandleMsg(UpdateActionMessage& msg) { + if (!CheckStateRange(msg.GetActionContext().GetStateId())) return; m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_LastEditedState = msg.GetActionContext().GetStateId(); }; template<> void PropertyBehavior::HandleMsg(UpdateStripUiMessage& msg) { + if (!CheckStateRange(msg.GetActionContext().GetStateId())) return; m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_LastEditedState = msg.GetActionContext().GetStateId(); }; template<> void PropertyBehavior::HandleMsg(RemoveStripMessage& msg) { + if (!CheckStateRange(msg.GetActionContext().GetStateId())) return; m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_LastEditedState = msg.GetActionContext().GetStateId(); }; template<> void PropertyBehavior::HandleMsg(RemoveActionsMessage& msg) { + if (!CheckStateRange(msg.GetActionContext().GetStateId())) return; m_States[msg.GetActionContext().GetStateId()].HandleMsg(msg); m_LastEditedState = msg.GetActionContext().GetStateId(); }; template<> void PropertyBehavior::HandleMsg(SplitStripMessage& msg) { + if (!CheckStateRange(msg.GetSourceActionContext().GetStateId())) return; + if (!CheckStateRange(msg.GetDestinationActionContext().GetStateId())) return; m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg); m_LastEditedState = msg.GetDestinationActionContext().GetStateId(); @@ -68,6 +81,8 @@ void PropertyBehavior::HandleMsg(SplitStripMessage& msg) { template<> void PropertyBehavior::HandleMsg(MigrateActionsMessage& msg) { + if (!CheckStateRange(msg.GetSourceActionContext().GetStateId())) return; + if (!CheckStateRange(msg.GetDestinationActionContext().GetStateId())) return; m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg); m_LastEditedState = msg.GetDestinationActionContext().GetStateId(); @@ -75,6 +90,8 @@ void PropertyBehavior::HandleMsg(MigrateActionsMessage& msg) { template<> void PropertyBehavior::HandleMsg(MergeStripsMessage& msg) { + if (!CheckStateRange(msg.GetSourceActionContext().GetStateId())) return; + if (!CheckStateRange(msg.GetDestinationActionContext().GetStateId())) return; m_States[msg.GetSourceActionContext().GetStateId()].HandleMsg(msg); m_States[msg.GetDestinationActionContext().GetStateId()].HandleMsg(msg); m_LastEditedState = msg.GetDestinationActionContext().GetStateId(); diff --git a/dGame/dUtilities/BrickDatabase.cpp b/dGame/dUtilities/BrickDatabase.cpp index 61a7341d..2cc034e9 100644 --- a/dGame/dUtilities/BrickDatabase.cpp +++ b/dGame/dUtilities/BrickDatabase.cpp @@ -66,13 +66,9 @@ const BrickList& BrickDatabase::GetBricks(const LxfmlPath& lxfmlPath) { std::string materialString(materialList); const auto materials = GeneralUtils::SplitString(materialString, ','); - if (!materials.empty()) { - brick.materialID = std::stoi(materials[0]); - } else { - brick.materialID = 0; - } + brick.materialID = GeneralUtils::TryParse(materials[0], 0); } else if (materialID != nullptr) { - brick.materialID = std::stoi(materialID); + brick.materialID = GeneralUtils::TryParse(materialID, 0); } else { brick.materialID = 0; // This is bad, makes it so the minigame can't be played } diff --git a/dGame/dUtilities/CheatDetection.cpp b/dGame/dUtilities/CheatDetection.cpp index a0f8c53a..acc415a6 100644 --- a/dGame/dUtilities/CheatDetection.cpp +++ b/dGame/dUtilities/CheatDetection.cpp @@ -54,17 +54,23 @@ void LogAndSaveFailedAntiCheatCheck(const LWOOBJID& id, const SystemAddress& sys // If player exists and entity exists in world, use both for logging info. if (entity && player) { + const auto* const playerChar = player->GetCharacter(); + const auto& playerName = playerChar ? playerChar->GetName() : "(null player character)"; + const auto* const entityChar = entity->GetCharacter(); + const auto& entityName = entityChar ? entityChar->GetName() : "(null entity character)"; LOG("Player (%s) (%llu) at system address (%s) with sending player (%s) (%llu) does not match their own.", - player->GetCharacter()->GetName().c_str(), player->GetObjectID(), + playerName.c_str(), player->GetObjectID(), sysAddr.ToString(), - entity->GetCharacter()->GetName().c_str(), entity->GetObjectID()); - if (player->GetCharacter()) toReport = player->GetCharacter()->GetParentUser(); + entityName.c_str(), entity->GetObjectID()); + if (playerChar) toReport = playerChar->GetParentUser(); // In the case that the target entity id did not exist, just log the player info. } else if (player) { + const auto* const playerChar = player->GetCharacter(); + const auto& playerName = playerChar ? playerChar->GetName() : "(null player character)"; LOG("Player (%s) (%llu) at system address (%s) with sending player (%llu) does not match their own.", - player->GetCharacter()->GetName().c_str(), player->GetObjectID(), + playerName.c_str(), player->GetObjectID(), sysAddr.ToString(), id); - if (player->GetCharacter()) toReport = player->GetCharacter()->GetParentUser(); + if (playerChar) toReport = playerChar->GetParentUser(); // In the rare case that the player does not exist, just log the system address and who the target id was. } else { LOG("Player at system address (%s) with sending player (%llu) does not match their own.", @@ -76,8 +82,11 @@ void LogAndSaveFailedAntiCheatCheck(const LWOOBJID& id, const SystemAddress& sys auto* user = UserManager::Instance()->GetUser(sysAddr); if (user) { + const auto* const lastChar = user->GetLastUsedChar(); + const auto& lastName = lastChar ? lastChar->GetName() : "(null last char)"; + const auto lastObjID = lastChar ? lastChar->GetObjectID() : LWOOBJID_EMPTY; LOG("User at system address (%s) (%s) (%llu) sent a packet as (%llu) which is not an id they own.", - sysAddr.ToString(), user->GetLastUsedChar()->GetName().c_str(), user->GetLastUsedChar()->GetObjectID(), id); + sysAddr.ToString(), lastName.c_str(), lastObjID, id); // Can't know sending player. Just log system address for IP banning. } else { LOG("No user found for system address (%s).", sysAddr.ToString()); diff --git a/dGame/dUtilities/Loot.cpp b/dGame/dUtilities/Loot.cpp index 2fe7b796..2edec1c4 100644 --- a/dGame/dUtilities/Loot.cpp +++ b/dGame/dUtilities/Loot.cpp @@ -326,7 +326,7 @@ void DropRegularLoot(Team& team, GameMessages::DropClientLoot& lootMsg, const bo void DropLoot(Entity* player, const LWOOBJID source, const std::map& rolledItems, uint32_t minCoins, uint32_t maxCoins, const bool noTeamLootOnDeath) { player = player->GetOwner(); // if the owner is overwritten, we collect that here const auto playerID = player->GetObjectID(); - if (!player || !player->IsPlayer()) { + if (!player->IsPlayer()) { LOG("Trying to drop loot for non-player %llu:%i", playerID, player->GetLOT()); return; } diff --git a/dGame/dUtilities/Mail.cpp b/dGame/dUtilities/Mail.cpp index 5f1377e7..b0054513 100644 --- a/dGame/dUtilities/Mail.cpp +++ b/dGame/dUtilities/Mail.cpp @@ -74,69 +74,74 @@ namespace Mail { void SendRequest::Handle() { SendResponse response; auto* character = player->GetCharacter(); - const bool restrictMailOnMute = UserManager::Instance()->GetMuteRestrictMail() && character->GetParentUser()->GetIsMuted(); - const bool restrictedMailAccess = character->HasPermission(ePermissionMap::RestrictedMailAccess); - - if (character && !(restrictedMailAccess || restrictMailOnMute)) { - mailInfo.recipient = std::regex_replace(mailInfo.recipient, std::regex("[^0-9a-zA-Z]+"), ""); - auto receiverID = Database::Get()->GetCharacterInfo(mailInfo.recipient); - - if (!receiverID) { - response.status = eSendResponse::RecipientNotFound; - } else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) { - response.status = eSendResponse::CannotMailSelf; - } else { - uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee; - uint32_t stackSize = 0; - - auto inventoryComponent = player->GetComponent(); - Item* item = nullptr; - - bool hasAttachment = mailInfo.itemID != 0 && mailInfo.itemCount > 0; - - if (hasAttachment) { - item = inventoryComponent->FindItemById(mailInfo.itemID); - if (item) { - mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig().mailPercentAttachmentFee); - mailInfo.itemLOT = item->GetLot(); - } - } - - if (hasAttachment && !item) { - response.status = eSendResponse::AttachmentNotFound; - } else if (player->GetCharacter()->GetCoins() - mailCost < 0) { - response.status = eSendResponse::NotEnoughCoins; - } else { - bool removeSuccess = true; - // Remove coins and items from the sender - player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL); - if (inventoryComponent && hasAttachment && item) { - removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, ALL, true); - auto* missionComponent = player->GetComponent(); - if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount); - } - - // we passed all the checks, now we can actully send the mail - if (removeSuccess) { - mailInfo.senderId = character->GetID(); - mailInfo.senderUsername = character->GetName(); - mailInfo.receiverId = receiverID->id; - mailInfo.itemSubkey = LWOOBJID_EMPTY; - - //clear out the attachementID - mailInfo.itemID = 0; - - Database::Get()->InsertNewMail(mailInfo); - response.status = eSendResponse::Success; - character->SaveXMLToDatabase(); - } else { - response.status = eSendResponse::AttachmentNotFound; - } - } - } + if (!character) { + response.status = eSendResponse::UnknownError; } else { - response.status = eSendResponse::SenderAccountIsMuted; + const bool restrictMailOnMute = UserManager::Instance()->GetMuteRestrictMail() && character->GetParentUser()->GetIsMuted(); + const bool restrictedMailAccess = character->HasPermission(ePermissionMap::RestrictedMailAccess); + + if (character && !(restrictedMailAccess || restrictMailOnMute)) { + mailInfo.recipient = std::regex_replace(mailInfo.recipient, std::regex("[^0-9a-zA-Z]+"), ""); + auto receiverID = Database::Get()->GetCharacterInfo(mailInfo.recipient); + + if (!receiverID) { + response.status = eSendResponse::RecipientNotFound; + } else if (GeneralUtils::CaseInsensitiveStringCompare(mailInfo.recipient, character->GetName()) || receiverID->id == character->GetID()) { + response.status = eSendResponse::CannotMailSelf; + } else { + uint32_t mailCost = Game::zoneManager->GetWorldConfig().mailBaseFee; + uint32_t stackSize = 0; + + auto inventoryComponent = player->GetComponent(); + Item* item = nullptr; + + bool hasAttachment = mailInfo.itemID != 0 && mailInfo.itemCount > 0; + + if (hasAttachment) { + item = inventoryComponent->FindItemById(mailInfo.itemID); + if (item) { + mailCost += (item->GetInfo().baseValue * Game::zoneManager->GetWorldConfig().mailPercentAttachmentFee); + mailInfo.itemLOT = item->GetLot(); + } + } + + if (hasAttachment && !item) { + response.status = eSendResponse::AttachmentNotFound; + } else if (player->GetCharacter()->GetCoins() - mailCost < 0) { + response.status = eSendResponse::NotEnoughCoins; + } else { + bool removeSuccess = true; + // Remove coins and items from the sender + player->GetCharacter()->SetCoins(player->GetCharacter()->GetCoins() - mailCost, eLootSourceType::MAIL); + if (inventoryComponent && hasAttachment && item) { + removeSuccess = inventoryComponent->RemoveItem(mailInfo.itemLOT, mailInfo.itemCount, ALL, true); + auto* missionComponent = player->GetComponent(); + if (missionComponent && removeSuccess) missionComponent->Progress(eMissionTaskType::GATHER, mailInfo.itemLOT, LWOOBJID_EMPTY, "", -mailInfo.itemCount); + } + + // we passed all the checks, now we can actully send the mail + if (removeSuccess) { + mailInfo.senderId = character->GetID(); + mailInfo.senderUsername = character->GetName(); + mailInfo.receiverId = receiverID->id; + mailInfo.itemSubkey = LWOOBJID_EMPTY; + + //clear out the attachementID + mailInfo.itemID = 0; + + Database::Get()->InsertNewMail(mailInfo); + response.status = eSendResponse::Success; + character->SaveXMLToDatabase(); + } else { + response.status = eSendResponse::AttachmentNotFound; + } + } + } + } else { + response.status = eSendResponse::SenderAccountIsMuted; + } } + LOG("Finished send with status %s", StringifiedEnum::ToString(response.status).data()); response.Send(sysAddr); } @@ -193,7 +198,7 @@ namespace Mail { if (mailID > 0 && playerID == player->GetObjectID() && inv) { auto playerMail = Database::Get()->GetMail(mailID); - if (!playerMail) { + if (!playerMail || playerMail->receiverId != player->GetObjectID()) { response.status = eAttachmentCollectResponse::MailNotFound; } else if (!inv->HasSpaceForLoot({ {playerMail->itemLOT, playerMail->itemCount} })) { response.status = eAttachmentCollectResponse::NoSpaceInInventory; @@ -225,15 +230,21 @@ namespace Mail { DeleteResponse response; response.mailID = mailID; - auto mailData = Database::Get()->GetMail(mailID); - if (mailData && !(mailData->itemLOT > 0 && mailData->itemCount > 0)) { - Database::Get()->DeleteMail(mailID); - response.status = eDeleteResponse::Success; - } else if (mailData && mailData->itemLOT > 0 && mailData->itemCount > 0) { - response.status = eDeleteResponse::HasAttachments; - } else { - response.status = eDeleteResponse::NotFound; + const auto mailData = Database::Get()->GetMail(mailID); + response.status = eDeleteResponse::NotFound; + if (mailData) { + if (mailData->receiverId != playerID) { + LOG("Player %llu attempted to delete mail owned by %llu. Possible spoof?", playerID, mailData->receiverId); + } else { + if (!(mailData->itemLOT > 0 && mailData->itemCount > 0)) { + Database::Get()->DeleteMail(mailID); + response.status = eDeleteResponse::Success; + } else if (mailData->itemLOT > 0 && mailData->itemCount > 0) { + response.status = eDeleteResponse::HasAttachments; + } + } } + LOG("DeleteRequest status %s", StringifiedEnum::ToString(response.status).data()); response.Send(sysAddr); } @@ -253,11 +264,19 @@ namespace Mail { void ReadRequest::Handle() { ReadResponse response; + response.status = eReadResponse::UnknownError; response.mailID = mailID; - if (Database::Get()->GetMail(mailID)) { - response.status = eReadResponse::Success; - Database::Get()->MarkMailRead(mailID); + const auto mail = Database::Get()->GetMail(mailID); + if (mail) { + if (mail->receiverId == player->GetObjectID()) { + response.status = eReadResponse::Success; + Database::Get()->MarkMailRead(mailID); + } else { + LOG("Player %llu tried to mark mail read for player %llu", mail->receiverId, player->GetObjectID()); + } + } else { + LOG("No mail by ID %llu found to mark as read.", mailID); } LOG("ReadRequest %s", StringifiedEnum::ToString(response.status).data()); diff --git a/dGame/dUtilities/Preconditions.cpp b/dGame/dUtilities/Preconditions.cpp index 0c322987..f0cacad0 100644 --- a/dGame/dUtilities/Preconditions.cpp +++ b/dGame/dUtilities/Preconditions.cpp @@ -114,11 +114,13 @@ bool Precondition::Check(Entity* player, bool evaluateCosts) const { bool Precondition::CheckValue(Entity* player, const uint32_t value, bool evaluateCosts) const { - auto* missionComponent = player->GetComponent(); - auto* inventoryComponent = player->GetComponent(); - auto* destroyableComponent = player->GetComponent(); - auto* levelComponent = player->GetComponent(); auto* character = player->GetCharacter(); + auto [missionComponent, inventoryComponent, destroyableComponent, levelComponent] = + player->GetComponentsMut(); + + if (!missionComponent || !inventoryComponent || !destroyableComponent || !levelComponent || !character) { + return false; + } Mission* mission; diff --git a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp index ad4e0463..f1b92e26 100644 --- a/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp +++ b/dGame/dUtilities/SlashCommands/DEVGMCommands.cpp @@ -825,7 +825,7 @@ namespace DEVGMCommands { } const auto numberToSpawnOptional = GeneralUtils::TryParse(splitArgs[1]); - if (!numberToSpawnOptional && numberToSpawnOptional.value() > 0) { + if (!numberToSpawnOptional) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid number of enemies to spawn."); return; } @@ -833,7 +833,7 @@ namespace DEVGMCommands { // Must spawn within a radius of at least 0.0f const auto radiusToSpawnWithinOptional = GeneralUtils::TryParse(splitArgs[2]); - if (!radiusToSpawnWithinOptional && radiusToSpawnWithinOptional.value() < 0.0f) { + if (!radiusToSpawnWithinOptional || radiusToSpawnWithinOptional.value() < 0.0f) { ChatPackets::SendSystemMessage(sysAddr, u"Invalid radius to spawn within."); return; } @@ -1133,6 +1133,10 @@ namespace DEVGMCommands { } const auto& password = splitArgs[2]; + if (password.length() >= 50) { + ChatPackets::SendSystemMessage(sysAddr, u"Password is too long."); + return; + } ZoneInstanceManager::Instance()->CreatePrivateZone(Game::server, zone.value(), clone.value(), password); diff --git a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp index 9e0bb475..4ef1eb4e 100644 --- a/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp +++ b/dGame/dUtilities/SlashCommands/GMZeroCommands.cpp @@ -187,8 +187,13 @@ namespace GMZeroCommands { auto splitArgs = GeneralUtils::SplitString(args, ' '); if (splitArgs.empty()) return; - ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map..."); const auto& password = splitArgs[0]; + if (password.length() >= 50) { + ChatPackets::SendSystemMessage(sysAddr, u"Password is too long."); + return; + } + + ChatPackets::SendSystemMessage(sysAddr, u"Requesting private map..."); ZoneInstanceManager::Instance()->RequestPrivateZone(Game::server, false, password, [=](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string serverIP, uint16_t serverPort) { LOG("Transferring %s to Zone %i (Instance %i | Clone %i | Mythran Shift: %s) with IP %s and Port %i", sysAddr.ToString(), zoneID, zoneInstance, zoneClone, mythranShift == true ? "true" : "false", serverIP.c_str(), serverPort); diff --git a/dMasterServer/MasterServer.cpp b/dMasterServer/MasterServer.cpp index aba78800..0ae71f07 100644 --- a/dMasterServer/MasterServer.cpp +++ b/dMasterServer/MasterServer.cpp @@ -328,20 +328,14 @@ int main(int argc, char** argv) { } Game::randomEngine = std::mt19937(time(0)); - uint32_t maxClients = 999; - uint32_t ourPort = 2000; - std::string ourIP = "localhost"; - const auto maxClientsString = Game::config->GetValue("max_clients"); - if (!maxClientsString.empty()) maxClients = std::stoi(maxClientsString); - const auto masterServerPortString = Game::config->GetValue("master_server_port"); - if (!masterServerPortString.empty()) ourPort = std::atoi(masterServerPortString.c_str()); - const auto externalIPString = Game::config->GetValue("external_ip"); - if (!externalIPString.empty()) ourIP = externalIPString; + uint32_t maxClients = Game::config->GetValue("max_clients", 999); + uint32_t ourPort = Game::config->GetValue("master_server_port", 2000); + std::string ourIP = Game::config->GetValue("external_ip", "localhost"); char salt[BCRYPT_HASHSIZE]; char hash[BCRYPT_HASHSIZE]; - const auto& cfgPassword = Game::config->GetValue("master_password"); - int res = GenerateBCryptPassword(!cfgPassword.empty() ? cfgPassword : "3.25DARKFLAME1", 13, salt, hash); + const auto& cfgPassword = Game::config->GetValue("master_password", "3.25DARKFLAME1"); + int res = GenerateBCryptPassword(cfgPassword, 13, salt, hash); assert(res == 0); Game::server = new dServer(ourIP, ourPort, 0, maxClients, true, false, Game::logger, "", 0, ServiceType::MASTER, Game::config, &Game::lastSignal, hash); @@ -535,8 +529,7 @@ void HandlePacket(Packet* packet) { case MessageType::Master::REQUEST_ZONE_TRANSFER: { LOG("Received zone transfer req"); - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER; uint64_t requestID = 0; uint8_t mythranShift = false; uint32_t zoneID = 0; @@ -575,8 +568,7 @@ void HandlePacket(Packet* packet) { //This is here because otherwise we'd have to include IM in //non-master servers. This packet allows us to add World //servers back if master crashed - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER; uint32_t theirPort = 0; uint32_t theirZoneID = 0; @@ -602,13 +594,13 @@ void HandlePacket(Packet* packet) { instance->SetSysAddr(packet->systemAddress); } } - break; + break; case ServiceType::CHAT: chatServerMasterPeerSysAddr = packet->systemAddress; break; - case ServiceType::AUTH: - authServerMasterPeerSysAddr = packet->systemAddress; - break; + case ServiceType::AUTH: + authServerMasterPeerSysAddr = packet->systemAddress; + break; default: // We just ignore any other server type break; @@ -664,8 +656,7 @@ void HandlePacket(Packet* packet) { } case MessageType::Master::PLAYER_ADDED: { - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER; LWOMAPID theirZoneID = 0; LWOINSTANCEID theirInstanceID = 0; @@ -684,8 +675,7 @@ void HandlePacket(Packet* packet) { } case MessageType::Master::PLAYER_REMOVED: { - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER; LWOMAPID theirZoneID = 0; LWOINSTANCEID theirInstanceID = 0; @@ -702,8 +692,7 @@ void HandlePacket(Packet* packet) { } case MessageType::Master::CREATE_PRIVATE_ZONE: { - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER; uint32_t mapId; LWOCLONEID cloneId; @@ -714,6 +703,8 @@ void HandlePacket(Packet* packet) { uint32_t len; inStream.Read(len); + len = std::min(len, 50); // cap the master password at 50 characters + for (uint32_t i = 0; len > i; i++) { char character; inStream.Read(character); @@ -726,8 +717,7 @@ void HandlePacket(Packet* packet) { } case MessageType::Master::REQUEST_PRIVATE_ZONE: { - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER; uint64_t requestID = 0; uint8_t mythranShift = false; @@ -739,6 +729,7 @@ void HandlePacket(Packet* packet) { uint32_t len; inStream.Read(len); + len = std::min(len, 50); for (uint32_t i = 0; i < len; i++) { char character; inStream.Read(character); @@ -761,8 +752,7 @@ void HandlePacket(Packet* packet) { } case MessageType::Master::WORLD_READY: { - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER; LWOMAPID zoneID; LWOINSTANCEID instanceID; @@ -785,8 +775,7 @@ void HandlePacket(Packet* packet) { } case MessageType::Master::PREP_ZONE: { - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER; int32_t zoneID; inStream.Read(zoneID); @@ -801,8 +790,7 @@ void HandlePacket(Packet* packet) { } case MessageType::Master::AFFIRM_TRANSFER_RESPONSE: { - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER; uint64_t requestID; @@ -876,7 +864,7 @@ int ShutdownSequence(int32_t signal) { if (!instance->GetIsReady()) { Game::im->RemoveInstance(instance); } - + instance->SetIsShuttingDown(true); } diff --git a/dNavigation/dNavMesh.cpp b/dNavigation/dNavMesh.cpp index d9584b00..a65ed0a3 100644 --- a/dNavigation/dNavMesh.cpp +++ b/dNavigation/dNavMesh.cpp @@ -37,6 +37,10 @@ dNavMesh::~dNavMesh() { void dNavMesh::LoadNavmesh() { + struct FPClose { + FILE* fp{}; + ~FPClose() { if (fp) fclose(fp); } + } fp; std::string path = (BinaryPathFinder::GetBinaryDir() / "navmeshes/" / (std::to_string(m_ZoneId) + ".bin")).string(); @@ -44,55 +48,48 @@ void dNavMesh::LoadNavmesh() { return; } - FILE* fp; - #ifdef _WIN32 - fopen_s(&fp, path.c_str(), "rb"); + fopen_s(&fp.fp, path.c_str(), "rb"); #elif __APPLE__ // macOS has 64bit file IO by default - fp = fopen(path.c_str(), "rb"); + fp.fp = fopen(path.c_str(), "rb"); #else - fp = fopen64(path.c_str(), "rb"); + fp.fp = fopen64(path.c_str(), "rb"); #endif - if (!fp) { + if (!fp.fp) { return; } // Read header. NavMeshSetHeader header; - size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp); + size_t readLen = fread(&header, sizeof(NavMeshSetHeader), 1, fp.fp); if (readLen != 1) { - fclose(fp); return; } if (header.magic != NAVMESHSET_MAGIC) { - fclose(fp); return; } if (header.version != NAVMESHSET_VERSION) { - fclose(fp); return; } dtNavMesh* mesh = dtAllocNavMesh(); if (!mesh) { - fclose(fp); return; } dtStatus status = mesh->init(&header.params); if (dtStatusFailed(status)) { - fclose(fp); return; } // Read tiles. for (int i = 0; i < header.numTiles; ++i) { NavMeshTileHeader tileHeader; - readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp); + readLen = fread(&tileHeader, sizeof(tileHeader), 1, fp.fp); if (readLen != 1) return; if (!tileHeader.tileRef || !tileHeader.dataSize) @@ -101,14 +98,15 @@ void dNavMesh::LoadNavmesh() { unsigned char* data = static_cast(dtAlloc(tileHeader.dataSize, DT_ALLOC_PERM)); if (!data) break; memset(data, 0, tileHeader.dataSize); - readLen = fread(data, tileHeader.dataSize, 1, fp); - if (readLen != 1) return; + readLen = fread(data, tileHeader.dataSize, 1, fp.fp); + if (readLen != 1) { + dtFree(data); + return; + } mesh->addTile(data, tileHeader.dataSize, DT_TILE_FREE_DATA, tileHeader.tileRef, 0); } - fclose(fp); - m_NavMesh = mesh; } @@ -187,13 +185,18 @@ std::vector dNavMesh::GetPath(const NiPoint3& startPos, const NiPoint3 path.push_back(startPos); //insert the start pos // Linearly interpolate between these two points: - for (int i = 0; i < numPoints; i++) { - NiPoint3 newPoint{ startPos }; + const auto yDist = endPos.y - startPos.y; + // Ensure we cannot divide by zero + const auto xDist = endPos.x - startPos.x; + if (xDist != 0.0f) { + for (int i = 0; i < numPoints; i++) { + NiPoint3 newPoint{ startPos }; - newPoint.x += speed; - newPoint.y = newPoint.y + (((endPos.y - startPos.y) / (endPos.x - startPos.x)) * (newPoint.x - startPos.x)); + newPoint.x += speed; + newPoint.y = newPoint.y + ((yDist / xDist) * (newPoint.x - startPos.x)); - path.push_back(newPoint); + path.push_back(newPoint); + } } path.push_back(endPos); //finally insert our end pos @@ -212,16 +215,16 @@ std::vector dNavMesh::GetPath(const NiPoint3& startPos, const NiPoint3 ePos[2] = endPos.z; dtStatus pathFindStatus; - dtPolyRef startRef; - dtPolyRef endRef; + dtPolyRef startRef{}; + dtPolyRef endRef{}; float polyPickExt[3] = { 32.0f, 32.0f, 32.0f }; dtQueryFilter filter{}; //Find our start poly - m_NavQuery->findNearestPoly(sPos, polyPickExt, &filter, &startRef, 0); + const auto startResult = m_NavQuery->findNearestPoly(sPos, polyPickExt, &filter, &startRef, 0); //Find our end poly - m_NavQuery->findNearestPoly(ePos, polyPickExt, &filter, &endRef, 0); + const auto endResult = m_NavQuery->findNearestPoly(ePos, polyPickExt, &filter, &endRef, 0); pathFindStatus = DT_FAILURE; int m_nstraightPath = 0; @@ -232,7 +235,7 @@ std::vector dNavMesh::GetPath(const NiPoint3& startPos, const NiPoint3 dtPolyRef m_straightPathPolys[MAX_POLYS]; int m_straightPathOptions = 0; - if (startRef && endRef) { + if (dtStatusSucceed(startResult) && dtStatusSucceed(endResult)) { m_NavQuery->findPath(startRef, endRef, sPos, ePos, &filter, m_polys, &m_npolys, MAX_POLYS); if (m_npolys) { diff --git a/dNavigation/dTerrain/RawChunk.cpp b/dNavigation/dTerrain/RawChunk.cpp index fe7ac8a2..df4950d4 100644 --- a/dNavigation/dTerrain/RawChunk.cpp +++ b/dNavigation/dTerrain/RawChunk.cpp @@ -18,6 +18,7 @@ RawChunk::RawChunk(std::ifstream& stream) { // We can just skip the rest of the data so we can read the next chunks, we don't need anymore data + // Possible overflow here? TODO make reasonable upper bound or confirm big numbers arent necessary to have uint32_t colorMapSize; BinaryIO::BinaryRead(stream, colorMapSize); stream.seekg(static_cast(stream.tellg()) + (colorMapSize * colorMapSize * 4)); diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index 0473ddf2..e1f500b3 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -157,8 +157,7 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { } //If we aren't running in live mode, then only GMs are allowed to enter: - const auto& closedToNonDevs = Game::config->GetValue("closed_to_non_devs"); - if (closedToNonDevs.size() > 0 && bool(std::stoi(closedToNonDevs)) && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) { + if (Game::config->GetValue("closed_to_non_devs", false) && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) { stamps.emplace_back(eStamps::GM_REQUIRED, 1); AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "The server is currently only open to developers.", "", 2001, username, stamps); return; diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp index 41bf1a2d..43d36e0f 100644 --- a/dNet/ClientPackets.cpp +++ b/dNet/ClientPackets.cpp @@ -108,6 +108,7 @@ ChatModerationRequest ClientPackets::HandleChatModerationRequest(Packet* packet) uint16_t messageLength; inStream.Read(messageLength); + if (messageLength > MAX_MESSAGE_LENGTH) return request; for (uint32_t i = 0; i < messageLength; ++i) { uint16_t character; inStream.Read(character); diff --git a/dNet/MasterPackets.cpp b/dNet/MasterPackets.cpp index aac49929..eb473422 100644 --- a/dNet/MasterPackets.cpp +++ b/dNet/MasterPackets.cpp @@ -77,7 +77,8 @@ void MasterPackets::SendZoneTransferResponse(dServer* server, const SystemAddres void MasterPackets::HandleServerInfo(Packet* packet) { RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + uint64_t header{}; + inStream.Read(header); uint32_t theirPort = 0; uint32_t theirZoneID = 0; diff --git a/dNet/WorldPackets.cpp b/dNet/WorldPackets.cpp index 1c9898a9..91117056 100644 --- a/dNet/WorldPackets.cpp +++ b/dNet/WorldPackets.cpp @@ -103,12 +103,9 @@ void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, int64_t rep //Compress the data before sending: const uint32_t reservedSize = ZCompression::GetMaxCompressedLength(data.GetNumberOfBytesUsed()); - uint8_t* compressedData = new uint8_t[reservedSize]; + auto compressedData = std::make_unique(reservedSize); - // TODO There should be better handling here for not enough memory... - if (!compressedData) return; - - size_t size = ZCompression::Compress(data.GetData(), data.GetNumberOfBytesUsed(), compressedData, reservedSize); + size_t size = ZCompression::Compress(data.GetData(), data.GetNumberOfBytesUsed(), compressedData.get(), reservedSize); assert(size <= reservedSize); @@ -123,11 +120,10 @@ void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, int64_t rep * an assertion is done to prevent bad data from being saved or sent. */ #pragma warning(disable:6385) // C6385 Reading invalid data from 'compressedData'. - bitStream.WriteAlignedBytes(compressedData, size); + bitStream.WriteAlignedBytes(compressedData.get(), size); #pragma warning(default:6385) SEND_PACKET; - delete[] compressedData; LOG("Sent CreateCharacter for ID: %llu", player); } diff --git a/dNet/dServer.cpp b/dNet/dServer.cpp index 53009d90..92cfab5b 100644 --- a/dNet/dServer.cpp +++ b/dNet/dServer.cpp @@ -233,13 +233,11 @@ bool dServer::Startup() { } void dServer::UpdateMaximumMtuSize() { - auto maxMtuSize = mConfig->GetValue("maximum_mtu_size"); - mPeer->SetMTUSize(maxMtuSize.empty() ? 1228 : std::stoi(maxMtuSize)); + mPeer->SetMTUSize(mConfig->GetValue("maximum_mtu_size", 1228)); } void dServer::UpdateBandwidthLimit() { - auto newBandwidth = mConfig->GetValue("maximum_outgoing_bandwidth"); - mPeer->SetPerConnectionOutgoingBandwidthLimit(!newBandwidth.empty() ? std::stoi(newBandwidth) : 0); + mPeer->SetPerConnectionOutgoingBandwidthLimit(mConfig->GetValue("maximum_outgoing_bandwidth", 0)); } void dServer::Shutdown() { diff --git a/dPhysics/dpEntity.cpp b/dPhysics/dpEntity.cpp index 6fc40452..6368764f 100644 --- a/dPhysics/dpEntity.cpp +++ b/dPhysics/dpEntity.cpp @@ -109,6 +109,7 @@ void dpEntity::SetPosition(const NiPoint3& newPos) { } void dpEntity::SetRotation(const NiQuaternion& newRot) { + if (!m_CollisionShape) return; m_Rotation = newRot; if (m_CollisionShape->GetShapeType() == dpShapeType::Box) { @@ -118,6 +119,7 @@ void dpEntity::SetRotation(const NiQuaternion& newRot) { } void dpEntity::SetScale(float newScale) { + if (!m_CollisionShape) return; m_Scale = newScale; if (m_CollisionShape->GetShapeType() == dpShapeType::Box) { diff --git a/dPhysics/dpWorld.cpp b/dPhysics/dpWorld.cpp index c9bc742a..a3df390c 100644 --- a/dPhysics/dpWorld.cpp +++ b/dPhysics/dpWorld.cpp @@ -87,7 +87,7 @@ void dpWorld::Shutdown() { } bool dpWorld::IsLoaded() { - return m_NavMesh->IsNavmeshLoaded(); + return m_NavMesh && m_NavMesh->IsNavmeshLoaded(); } void dpWorld::StepWorld(float deltaTime) { diff --git a/dScripts/02_server/Map/AM/AmSkullkinDrill.cpp b/dScripts/02_server/Map/AM/AmSkullkinDrill.cpp index 576688e7..df5a300f 100644 --- a/dScripts/02_server/Map/AM/AmSkullkinDrill.cpp +++ b/dScripts/02_server/Map/AM/AmSkullkinDrill.cpp @@ -36,13 +36,14 @@ void AmSkullkinDrill::OnStartup(Entity* self) { Entity* AmSkullkinDrill::GetStandObj(Entity* self) { const auto& myGroup = self->GetGroups(); - if (myGroup.empty()) { + if (myGroup.empty() || myGroup[0].empty()) { return nullptr; } + const auto& group = myGroup[0]; std::string groupName = "Drill_Stand_"; - groupName.push_back(myGroup[0][myGroup[0].size() - 1]); + groupName.push_back(group.back()); const auto standObjs = Game::entityManager->GetEntitiesInGroup(groupName); diff --git a/dScripts/02_server/Map/General/Binoculars.cpp b/dScripts/02_server/Map/General/Binoculars.cpp index 854ccf71..786c0faf 100644 --- a/dScripts/02_server/Map/General/Binoculars.cpp +++ b/dScripts/02_server/Map/General/Binoculars.cpp @@ -7,9 +7,14 @@ void Binoculars::OnUse(Entity* self, Entity* user) { const auto number = self->GetVarAsString(u"number"); - int32_t flag = std::stoi(std::to_string(Game::server->GetZoneID()).substr(0, 2) + number); - if (user->GetCharacter()->GetPlayerFlag(flag) == false) { - user->GetCharacter()->SetPlayerFlag(flag, true); + const int32_t flag = GeneralUtils::TryParse(std::to_string(Game::server->GetZoneID()).substr(0, 2) + number, 0); + GameMessages::GetFlag flagMsg; + flagMsg.target = user->GetObjectID(); + flagMsg.flagID = flag; + flagMsg.Send(); + if (!flagMsg.flag) { + auto* const character = user->GetCharacter(); + if (character) character->SetPlayerFlag(flag, true); GameMessages::SendFireEventClientSide(self->GetObjectID(), user->GetSystemAddress(), u"achieve", LWOOBJID_EMPTY, 0, -1, LWOOBJID_EMPTY); } } diff --git a/dScripts/02_server/Map/General/StoryBoxInteractServer.cpp b/dScripts/02_server/Map/General/StoryBoxInteractServer.cpp index 4ad78d6a..a19a87d4 100644 --- a/dScripts/02_server/Map/General/StoryBoxInteractServer.cpp +++ b/dScripts/02_server/Map/General/StoryBoxInteractServer.cpp @@ -33,14 +33,18 @@ void StoryBoxInteractServer::OnUse(Entity* self, Entity* user) { const auto storyText = self->GetVarAsString(u"storyText"); if (storyText.length() > 2) { auto storyValue = GeneralUtils::TryParse(storyText.substr(storyText.length() - 2)); - if(!storyValue) return; + if (!storyValue) return; int32_t boxFlag = self->GetVar(u"altFlagID"); if (boxFlag <= 0) { boxFlag = (10000 + Game::server->GetZoneID() + storyValue.value()); } - - if (user->GetCharacter()->GetPlayerFlag(boxFlag) == false) { - user->GetCharacter()->SetPlayerFlag(boxFlag, true); + GameMessages::GetFlag flagMsg; + flagMsg.target = user->GetObjectID(); + flagMsg.flagID = boxFlag; + flagMsg.Send(); + if (!flagMsg.flag) { + auto* const character = user->GetCharacter(); + if (character) user->GetCharacter()->SetPlayerFlag(boxFlag, true); GameMessages::SendFireEventClientSide(self->GetObjectID(), user->GetSystemAddress(), u"achieve", LWOOBJID_EMPTY, 0, -1, LWOOBJID_EMPTY); } } diff --git a/dScripts/02_server/Map/NS/NsConcertChoiceBuildManager.cpp b/dScripts/02_server/Map/NS/NsConcertChoiceBuildManager.cpp index 554eabac..5306a1dd 100644 --- a/dScripts/02_server/Map/NS/NsConcertChoiceBuildManager.cpp +++ b/dScripts/02_server/Map/NS/NsConcertChoiceBuildManager.cpp @@ -26,7 +26,8 @@ void NsConcertChoiceBuildManager::SpawnCrate(Entity* self) { const auto splitGroup = GeneralUtils::SplitString(group, '_'); if (splitGroup.size() < 2) return; - const auto groupNumber = std::stoi(splitGroup.at(1)); + const auto groupNumber = GeneralUtils::TryParse(splitGroup.at(1), -1); + if (groupNumber == -1) return; EntityInfo info{}; info.lot = crate.lot; diff --git a/dScripts/02_server/Map/NT/NtParadoxPanelServer.cpp b/dScripts/02_server/Map/NT/NtParadoxPanelServer.cpp index e64ad2dd..f182c8a5 100644 --- a/dScripts/02_server/Map/NT/NtParadoxPanelServer.cpp +++ b/dScripts/02_server/Map/NT/NtParadoxPanelServer.cpp @@ -33,7 +33,8 @@ void NtParadoxPanelServer::OnUse(Entity* self, Entity* user) { const auto flag = self->GetVar(u"flag"); - player->GetCharacter()->SetPlayerFlag(flag, true); + auto* const character = player->GetCharacter(); + if (character) character->SetPlayerFlag(flag, true); RenderComponent::PlayAnimation(player, u"rebuild-celebrate"); diff --git a/dScripts/02_server/Map/Property/AG_Small/ZoneAgProperty.cpp b/dScripts/02_server/Map/Property/AG_Small/ZoneAgProperty.cpp index 6f2f6d36..39fae3aa 100644 --- a/dScripts/02_server/Map/Property/AG_Small/ZoneAgProperty.cpp +++ b/dScripts/02_server/Map/Property/AG_Small/ZoneAgProperty.cpp @@ -413,7 +413,8 @@ void ZoneAgProperty::BaseOnFireEventServerSide(Entity* self, Entity* sender, std if (player == nullptr) return; - player->GetCharacter()->SetPlayerFlag(self->GetVar(defeatedProperyFlag), true); + auto* const character = player->GetCharacter(); + if (character) character->SetPlayerFlag(self->GetVar(defeatedProperyFlag), true); GameMessages::SendNotifyClientObject(self->GetObjectID(), u"PlayCinematic", 0, 0, LWOOBJID_EMPTY, destroyedCinematic, UNASSIGNED_SYSTEM_ADDRESS); diff --git a/dScripts/02_server/Map/VE/VeMissionConsole.cpp b/dScripts/02_server/Map/VE/VeMissionConsole.cpp index 011d680d..aa7035eb 100644 --- a/dScripts/02_server/Map/VE/VeMissionConsole.cpp +++ b/dScripts/02_server/Map/VE/VeMissionConsole.cpp @@ -15,7 +15,7 @@ void VeMissionConsole::OnUse(Entity* self, Entity* user) { // The flag to set is 101 const auto flagNumber = self->GetVar(m_NumberVariable); - const int32_t flag = std::stoi("101" + GeneralUtils::UTF16ToWTF8(flagNumber)); + const int32_t flag = GeneralUtils::TryParse("101" + GeneralUtils::UTF16ToWTF8(flagNumber), 0); auto* character = user->GetCharacter(); if (character != nullptr) { diff --git a/dScripts/02_server/Map/njhub/CavePrisonCage.cpp b/dScripts/02_server/Map/njhub/CavePrisonCage.cpp index 8496ecfb..591c29d5 100644 --- a/dScripts/02_server/Map/njhub/CavePrisonCage.cpp +++ b/dScripts/02_server/Map/njhub/CavePrisonCage.cpp @@ -13,7 +13,10 @@ void CavePrisonCage::OnStartup(Entity* self) { return; } - auto* spawner = Game::zoneManager->GetSpawnersByName("PrisonCounterweight_0" + GeneralUtils::UTF16ToWTF8(myNum))[0]; + const auto spawners = Game::zoneManager->GetSpawnersByName("PrisonCounterweight_0" + GeneralUtils::UTF16ToWTF8(myNum)); + if (spawners.empty()) return; + + auto* spawner = spawners[0]; self->SetVar(u"CWSpawner", spawner); @@ -21,6 +24,7 @@ void CavePrisonCage::OnStartup(Entity* self) { } void CavePrisonCage::Setup(Entity* self, Spawner* spawner) { + if (!spawner) return; SpawnCounterweight(self, spawner); NiPoint3 mypos = self->GetPosition(); @@ -62,6 +66,8 @@ void CavePrisonCage::OnQuickBuildNotifyState(Entity* self, eQuickBuildState stat } void CavePrisonCage::SpawnCounterweight(Entity* self, Spawner* spawner) { + if (!spawner) return; + spawner->Reset(); auto* counterweight = spawner->Spawn(); @@ -164,7 +170,8 @@ void CavePrisonCage::OnTimerDone(Entity* self, std::string timerName) { const auto flagNum = 2020 + self->GetVarAs(u"myNumber"); // Set the flag on the builder character - builder->GetCharacter()->SetPlayerFlag(flagNum, true); + auto* const character = builder->GetCharacter(); + if (character) character->SetPlayerFlag(flagNum, true); // Setup a timer named 'VillagerEscape' to be triggered in 5 seconds self->AddTimer("VillagerEscape", 5.0f); diff --git a/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp b/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp index 919a2b4f..d7be9d32 100644 --- a/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp +++ b/dScripts/02_server/Map/njhub/boss_instance/NjMonastryBossInstance.cpp @@ -408,7 +408,7 @@ void NjMonastryBossInstance::SummonWave(Entity* self, Entity* frakjaw) { // Stop the music for the first, fourth and fifth wave const auto wave = self->GetVar(WaveNumberVariable); - if (wave >= 1 || wave < (m_Waves.size() - 1)) { + if (wave >= 1 && wave < (m_Waves.size() - 1)) { GameMessages::SendNotifyClientObject(self->GetObjectID(), StopMusicNotification, 0, 0, LWOOBJID_EMPTY, AudioWaveAudio + std::to_string(wave - 1), UNASSIGNED_SYSTEM_ADDRESS); diff --git a/dScripts/BaseConsoleTeleportServer.cpp b/dScripts/BaseConsoleTeleportServer.cpp index d4a49299..d172696c 100644 --- a/dScripts/BaseConsoleTeleportServer.cpp +++ b/dScripts/BaseConsoleTeleportServer.cpp @@ -96,7 +96,7 @@ void BaseConsoleTeleportServer::TransferPlayer(Entity* self, Entity* player, int auto* characterComponent = player->GetComponent(); - if (characterComponent) characterComponent->SendToZone(std::stoi(GeneralUtils::UTF16ToWTF8(teleportZone))); + if (characterComponent) characterComponent->SendToZone(GeneralUtils::TryParse(GeneralUtils::UTF16ToWTF8(teleportZone), 0)); UpdatePlayerTable(self, player, false); } diff --git a/dScripts/BasePropertyServer.cpp b/dScripts/BasePropertyServer.cpp index 9048adeb..69b839f3 100644 --- a/dScripts/BasePropertyServer.cpp +++ b/dScripts/BasePropertyServer.cpp @@ -127,12 +127,15 @@ void BasePropertyServer::BasePlayerLoaded(Entity* self, Entity* player) { if (player->GetObjectID() != propertyOwner) return; } else { - const auto defeatedFlag = player->GetCharacter()->GetPlayerFlag(self->GetVar(defeatedProperyFlag)); + GameMessages::GetFlag flagMsg; + flagMsg.target = player->GetObjectID(); + flagMsg.flagID = self->GetVar(defeatedProperyFlag); + flagMsg.Send(); self->SetNetworkVar(UnclaimedVariable, true); self->SetVar(PlayerIDVariable, player->GetObjectID()); - if (!defeatedFlag) { + if (!flagMsg.flag) { StartMaelstrom(self, player); SpawnSpots(self); GameMessages::SendPlay2DAmbientSound(player, GUIDMaelstrom); diff --git a/dScripts/BaseRandomServer.cpp b/dScripts/BaseRandomServer.cpp index 17817d7a..4b67a5e3 100644 --- a/dScripts/BaseRandomServer.cpp +++ b/dScripts/BaseRandomServer.cpp @@ -124,21 +124,23 @@ void BaseRandomServer::NotifySpawnerOfDeath(Entity* self, Spawner* spawner) { return; } - const auto& sectionName = spawnerName.substr(0, spawnerName.size() - 7); + if (spawnerName.size() >= 7) { + const auto& sectionName = spawnerName.substr(0, spawnerName.size() - 7); - const auto variableName = u"mobsDead" + GeneralUtils::ASCIIToUTF16(sectionName); + const auto variableName = u"mobsDead" + GeneralUtils::ASCIIToUTF16(sectionName); - auto mobDeathCount = self->GetVar(variableName); + auto mobDeathCount = self->GetVar(variableName); - mobDeathCount++; + mobDeathCount++; - if (mobDeathCount >= mobDeathResetNumber) { - const auto& zoneInfo = GeneralUtils::SplitString(sectionName, '_'); + if (mobDeathCount >= mobDeathResetNumber) { + const auto& zoneInfo = GeneralUtils::SplitString(sectionName, '_'); - SpawnSection(self, sectionName, sectionMultipliers[zoneInfo[sectionIDConst - 1]]); + SpawnSection(self, sectionName, sectionMultipliers[zoneInfo[sectionIDConst - 1]]); + } + + self->SetVar(variableName, mobDeathCount); } - - self->SetVar(variableName, mobDeathCount); } void BaseRandomServer::NamedEnemyDeath(Entity* self, Spawner* spawner) { diff --git a/dScripts/BaseRandomServer.h b/dScripts/BaseRandomServer.h index bc5d6b21..daa8bd4c 100644 --- a/dScripts/BaseRandomServer.h +++ b/dScripts/BaseRandomServer.h @@ -37,7 +37,7 @@ public: void NamedTimerDone(Entity* self, const std::string& timerName); protected: - std::vector namedMobs = { + const std::vector namedMobs = { 11988, // Ronin 11984, // Spiderling 12654, // Horsemen diff --git a/dScripts/BaseSurvivalServer.cpp b/dScripts/BaseSurvivalServer.cpp index 07890e71..f71991ba 100644 --- a/dScripts/BaseSurvivalServer.cpp +++ b/dScripts/BaseSurvivalServer.cpp @@ -24,7 +24,8 @@ void BaseSurvivalServer::BasePlayerLoaded(Entity* self, Entity* player) { if (waitingIter != state.waitingPlayers.end() || playersIter != state.players.end()) { auto* characterComponent = player->GetComponent(); - if (characterComponent) characterComponent->SendToZone(player->GetCharacter()->GetLastNonInstanceZoneID()); + const auto* const character = player->GetCharacter(); + if (characterComponent && character) characterComponent->SendToZone(character->GetLastNonInstanceZoneID()); return; } diff --git a/dScripts/NtFactionSpyServer.cpp b/dScripts/NtFactionSpyServer.cpp index 532c128a..7414bb34 100644 --- a/dScripts/NtFactionSpyServer.cpp +++ b/dScripts/NtFactionSpyServer.cpp @@ -73,7 +73,7 @@ void NtFactionSpyServer::OnCinematicUpdate(Entity* self, Entity* sender, eCinema // Make sure we have a path of type _ if (pathSplit.size() >= 2) { auto pathRoot = pathSplit.at(0); - auto pathIndex = std::stoi(GeneralUtils::UTF16ToWTF8(pathSplit.at(1))) - 1; + auto pathIndex = GeneralUtils::TryParse(GeneralUtils::UTF16ToWTF8(pathSplit.at(1)), -1) - 1; const auto& dialogueTable = self->GetVar>(m_SpyDialogueTableVariable); // Make sure we're listening to the root we're interested in diff --git a/dScripts/SpawnPetBaseServer.cpp b/dScripts/SpawnPetBaseServer.cpp index 395a20c2..d2374162 100644 --- a/dScripts/SpawnPetBaseServer.cpp +++ b/dScripts/SpawnPetBaseServer.cpp @@ -57,7 +57,7 @@ bool SpawnPetBaseServer::CheckNumberOfPets(Entity* self, Entity* user) { if (petID.empty()) continue; - const auto* spawnedPet = Game::entityManager->GetEntity(std::stoull(petID)); + const auto* spawnedPet = Game::entityManager->GetEntity(GeneralUtils::TryParse(petID, LWOOBJID_EMPTY)); if (spawnedPet == nullptr) continue; diff --git a/dScripts/ai/AG/AgFans.cpp b/dScripts/ai/AG/AgFans.cpp index 15f2ed0f..060d29de 100644 --- a/dScripts/ai/AG/AgFans.cpp +++ b/dScripts/ai/AG/AgFans.cpp @@ -24,8 +24,11 @@ void AgFans::OnStartup(Entity* self) { } void AgFans::ToggleFX(Entity* self, bool hit) { - std::string fanGroup = self->GetGroups()[0]; - std::vector fanVolumes = Game::entityManager->GetEntitiesInGroup(fanGroup); + const auto groups = self->GetGroups(); + std::string fanGroup = groups.empty() ? "" : groups[0]; + const auto fanVolumes = Game::entityManager->GetEntitiesInGroup(fanGroup); + const auto fxObjs = Game::entityManager->GetEntitiesInGroup(fanGroup + "fx"); + auto* const fxObj = fxObjs.empty() ? nullptr : fxObjs[0]; auto* renderComponent = static_cast(self->GetComponent(eReplicaComponentType::RENDER)); @@ -47,8 +50,7 @@ void AgFans::ToggleFX(Entity* self, bool hit) { volumePhys->SetPhysicsEffectActive(false); Game::entityManager->SerializeEntity(volume); if (!hit) { - Entity* fxObj = Game::entityManager->GetEntitiesInGroup(fanGroup + "fx")[0]; - RenderComponent::PlayAnimation(fxObj, u"trigger"); + if (fxObj) RenderComponent::PlayAnimation(fxObj, u"trigger"); } } } else if (!self->GetVar(u"on") && self->GetVar(u"alive")) { @@ -63,8 +65,7 @@ void AgFans::ToggleFX(Entity* self, bool hit) { volumePhys->SetPhysicsEffectActive(true); Game::entityManager->SerializeEntity(volume); if (!hit) { - Entity* fxObj = Game::entityManager->GetEntitiesInGroup(fanGroup + "fx")[0]; - RenderComponent::PlayAnimation(fxObj, u"idle"); + if (fxObj) RenderComponent::PlayAnimation(fxObj, u"idle"); } } } diff --git a/dScripts/ai/FV/FvBrickPuzzleServer.cpp b/dScripts/ai/FV/FvBrickPuzzleServer.cpp index f8601e3f..77fa0d32 100644 --- a/dScripts/ai/FV/FvBrickPuzzleServer.cpp +++ b/dScripts/ai/FV/FvBrickPuzzleServer.cpp @@ -6,6 +6,7 @@ void FvBrickPuzzleServer::OnStartup(Entity* self) { const auto myGroup = GeneralUtils::UTF16ToWTF8(self->GetVar(u"spawner_name")); + if (myGroup.size() <= 10) return; const auto pipeNum = GeneralUtils::TryParse(myGroup.substr(10, 1)); if (!pipeNum) return; @@ -17,6 +18,7 @@ void FvBrickPuzzleServer::OnStartup(Entity* self) { void FvBrickPuzzleServer::OnDie(Entity* self, Entity* killer) { const auto myGroup = GeneralUtils::UTF16ToWTF8(self->GetVar(u"spawner_name")); + if (myGroup.size() <= 10) return; const auto pipeNum = GeneralUtils::TryParse(myGroup.substr(10, 1)); if (!pipeNum) return; diff --git a/dScripts/ai/FV/FvFacilityBrick.cpp b/dScripts/ai/FV/FvFacilityBrick.cpp index 26c07647..1402991c 100644 --- a/dScripts/ai/FV/FvFacilityBrick.cpp +++ b/dScripts/ai/FV/FvFacilityBrick.cpp @@ -9,9 +9,12 @@ void FvFacilityBrick::OnStartup(Entity* self) { } void FvFacilityBrick::OnNotifyObject(Entity* self, Entity* sender, const std::string& name, int32_t param1, int32_t param2) { - auto* brickSpawner = Game::zoneManager->GetSpawnersByName("ImaginationBrick")[0]; - auto* bugSpawner = Game::zoneManager->GetSpawnersByName("MaelstromBug")[0]; - auto* canisterSpawner = Game::zoneManager->GetSpawnersByName("BrickCanister")[0]; + const auto brickObjs = Game::zoneManager->GetSpawnersByName("ImaginationBrick"); + auto* const brickSpawner = brickObjs.empty() ? nullptr : brickObjs[0]; + const auto bugObjs = Game::zoneManager->GetSpawnersByName("MaelstromBug"); + auto* const bugSpawner = bugObjs.empty() ? nullptr : bugObjs[0]; + const auto canisterObjs = Game::zoneManager->GetSpawnersByName("BrickCanister"); + auto* const canisterSpawner = canisterObjs.empty() ? nullptr : canisterObjs[0]; if (name == "ConsoleLeftUp") { GameMessages::SendStopFXEffect(self, true, "LeftPipeOff"); @@ -62,7 +65,7 @@ void FvFacilityBrick::OnNotifyObject(Entity* self, Entity* sender, const std::st canisterSpawner->Reset(); canisterSpawner->Deactivate(); } else if (self->GetVar(u"ConsoleLEFTActive") || self->GetVar(u"ConsoleRIGHTActive")) { - brickSpawner->Activate(); + if (brickSpawner) brickSpawner->Activate(); auto* object = Game::entityManager->GetEntitiesInGroup("Brick")[0]; @@ -70,17 +73,25 @@ void FvFacilityBrick::OnNotifyObject(Entity* self, Entity* sender, const std::st GameMessages::SendStopFXEffect(object, true, "bluebrick"); } - bugSpawner->Reset(); - bugSpawner->Deactivate(); + if (bugSpawner) { + bugSpawner->Reset(); + bugSpawner->Deactivate(); + } - canisterSpawner->Reset(); - canisterSpawner->Activate(); + if (canisterSpawner) { + canisterSpawner->Reset(); + canisterSpawner->Activate(); + } } else { - brickSpawner->Reset(); - brickSpawner->Deactivate(); + if (brickSpawner) { + brickSpawner->Reset(); + brickSpawner->Deactivate(); + } - bugSpawner->Reset(); - bugSpawner->Activate(); + if (bugSpawner) { + bugSpawner->Reset(); + bugSpawner->Activate(); + } } } diff --git a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp index e97316e5..3dd2773f 100644 --- a/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp +++ b/dScripts/ai/MINIGAME/SG_GF/SERVER/SGCannon.cpp @@ -241,7 +241,7 @@ void SGCannon::GameOverTimerFunc(Entity* self) { void SGCannon::DoSpawnTimerFunc(Entity* self, const std::string& name) { if (self->GetVar(GameStartedVariable)) { - const auto spawnNumber = static_cast(std::stoi(name.substr(7))); + const auto spawnNumber = GeneralUtils::TryParse(name.substr(7), 0); const auto& activeSpawns = self->GetVar>(ActiveSpawnsVariable); if (activeSpawns.size() <= spawnNumber) { LOG_DEBUG("Trying to spawn %i when spawns size is only %i", spawnNumber, activeSpawns.size()); diff --git a/dScripts/ai/NS/NsConcertQuickBuild.cpp b/dScripts/ai/NS/NsConcertQuickBuild.cpp index 7fd3f125..2278eb5c 100644 --- a/dScripts/ai/NS/NsConcertQuickBuild.cpp +++ b/dScripts/ai/NS/NsConcertQuickBuild.cpp @@ -41,7 +41,9 @@ void NsConcertQuickBuild::OnStartup(Entity* self) { return; // Get the manager of the crate of this quick build - const auto groupNumber = std::stoi(splitGroup.at(3)); + const auto groupNumber = GeneralUtils::TryParse(splitGroup.at(3), -1); + if (groupNumber == -1) return; + const auto managerObjects = Game::entityManager->GetEntitiesInGroup("CB_" + std::to_string(groupNumber)); if (managerObjects.empty()) return; diff --git a/dScripts/ai/NS/NsGetFactionMissionServer.cpp b/dScripts/ai/NS/NsGetFactionMissionServer.cpp index 185bd344..d395cfd8 100644 --- a/dScripts/ai/NS/NsGetFactionMissionServer.cpp +++ b/dScripts/ai/NS/NsGetFactionMissionServer.cpp @@ -42,8 +42,11 @@ void NsGetFactionMissionServer::OnRespondToMission(Entity* self, int missionID, } if (flagID != -1) { - player->GetCharacter()->SetPlayerFlag(ePlayerFlag::JOINED_A_FACTION, true); - player->GetCharacter()->SetPlayerFlag(flagID, true); + auto* const character = player->GetCharacter(); + if (character) { + character->SetPlayerFlag(ePlayerFlag::JOINED_A_FACTION, true); + character->SetPlayerFlag(flagID, true); + } } MissionComponent* mis = static_cast(player->GetComponent(eReplicaComponentType::MISSION)); diff --git a/dScripts/ai/PROPERTY/AG/AgPropGuard.cpp b/dScripts/ai/PROPERTY/AG/AgPropGuard.cpp index 4e77cb55..0670408d 100644 --- a/dScripts/ai/PROPERTY/AG/AgPropGuard.cpp +++ b/dScripts/ai/PROPERTY/AG/AgPropGuard.cpp @@ -33,7 +33,8 @@ void AgPropGuard::OnMissionDialogueOK(Entity* self, Entity* target, int missionI ) { //GameMessages::SendNotifyClientObject(Game::entityManager->GetZoneControlEntity()->GetObjectID(), u"GuardChat", target->GetObjectID(), 0, target->GetObjectID(), "", target->GetSystemAddress()); - target->GetCharacter()->SetPlayerFlag(113, true); + auto* const character = target->GetCharacter(); + if (character) character->SetPlayerFlag(113, true); Game::entityManager->GetZoneControlEntity()->AddTimer("GuardFlyAway", 1.0f); } diff --git a/dServer/CMakeLists.txt b/dServer/CMakeLists.txt index ca4e6198..988465df 100644 --- a/dServer/CMakeLists.txt +++ b/dServer/CMakeLists.txt @@ -8,3 +8,5 @@ target_include_directories(dServer PUBLIC ".") target_include_directories(dServer PRIVATE "${PROJECT_SOURCE_DIR}/dCommon/" # BinaryPathFinder.h ) + +target_link_libraries(dServer PRIVATE dCommon) diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index 34b485df..0ba980da 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -763,11 +763,12 @@ void HandleMasterPacket(Packet* packet) { case MessageType::Master::NEW_SESSION_ALERT: { CINSTREAM_SKIP_HEADER; - uint32_t sessionKey = inStream.Read(sessionKey); + uint32_t sessionKey{}; + inStream.Read(sessionKey); LUString username; inStream.Read(username); - LOG("Got new session alert for user %s", username.string.c_str()); + LOG("Got new session alert for user %s %i", username.string.c_str(), sessionKey); //Find them: User* user = UserManager::Instance()->GetUser(username.string.c_str()); if (!user) { @@ -970,8 +971,7 @@ void HandlePacket(Packet* packet) { } case MessageType::World::LOGIN_REQUEST: { - RakNet::BitStream inStream(packet->data, packet->length, false); - uint64_t header = inStream.Read(header); + CINSTREAM_SKIP_HEADER; LWOOBJID playerID = 0; inStream.Read(playerID); @@ -1256,13 +1256,22 @@ void HandlePacket(Packet* packet) { return; } - Entity* entity = Game::entityManager->GetEntity(user->GetLastUsedChar()->GetObjectID()); - if (entity) entity->ProcessPositionUpdate(positionUpdate); + if (const auto* const lastChar = user->GetLastUsedChar()) { + if (auto* const entity = Game::entityManager->GetEntity(lastChar->GetObjectID())) { + entity->ProcessPositionUpdate(positionUpdate); + } + } break; } case MessageType::World::MAIL: { - Mail::HandleMail(inStream, packet->systemAddress, UserManager::Instance()->GetUser(packet->systemAddress)->GetLastUsedChar()->GetEntity()); + if (auto* const user = UserManager::Instance()->GetUser(packet->systemAddress)) { + if (auto* const lastChar = user->GetLastUsedChar()) { + if (auto* const entity = lastChar->GetEntity()) { + Mail::HandleMail(inStream, packet->systemAddress, entity); + } + } + } break; } @@ -1285,7 +1294,8 @@ void HandlePacket(Packet* packet) { LWOOBJID objectID = 0; auto user = UserManager::Instance()->GetUser(packet->systemAddress); if (user) { - objectID = user->GetLastUsedChar()->GetObjectID(); + const auto* const lastChar = user->GetLastUsedChar(); + if (lastChar) objectID = lastChar->GetObjectID(); } bitStream.Write(objectID); @@ -1385,13 +1395,19 @@ void HandlePacket(Packet* packet) { return; } - if (user->GetIsMuted()) { - user->GetLastUsedChar()->SendMuteNotice(); + const auto* const lastChar = user->GetLastUsedChar(); + if (!lastChar) { + LOG("No last used character for chat message %i", user->GetAccountID()); return; } - std::string playerName = user->GetLastUsedChar()->GetName(); - bool isMythran = user->GetLastUsedChar()->GetGMLevel() > eGameMasterLevel::CIVILIAN; - bool isOk = Game::chatFilter->IsSentenceOkay(GeneralUtils::UTF16ToWTF8(chatMessage.message), user->GetLastUsedChar()->GetGMLevel()).empty(); + + if (user->GetIsMuted()) { + lastChar->SendMuteNotice(); + return; + } + std::string playerName = lastChar->GetName(); + bool isMythran = lastChar->GetGMLevel() > eGameMasterLevel::CIVILIAN; + bool isOk = Game::chatFilter->IsSentenceOkay(GeneralUtils::UTF16ToWTF8(chatMessage.message), lastChar->GetGMLevel()).empty(); LOG_DEBUG("Msg: %s was approved previously? %i", GeneralUtils::UTF16ToWTF8(chatMessage.message).c_str(), user->GetLastChatMessageApproved()); if (!isOk) return; if (!isOk && !isMythran) return; diff --git a/dZoneManager/Level.cpp b/dZoneManager/Level.cpp index 13c85c1a..40ee6d30 100644 --- a/dZoneManager/Level.cpp +++ b/dZoneManager/Level.cpp @@ -43,11 +43,11 @@ void Level::MakeSpawner(SceneObject obj) { for (LDFBaseData* data : obj.settings) { if (!data) continue; if (data->GetKey() == u"spawntemplate") { - spawnInfo.templateID = std::stoi(data->GetValueAsString()); + spawnInfo.templateID = GeneralUtils::TryParse(data->GetValueAsString(), 0); } if (data->GetKey() == u"spawner_node_id") { - node->nodeID = std::stoi(data->GetValueAsString()); + node->nodeID = GeneralUtils::TryParse(data->GetValueAsString(), 0u); } if (data->GetKey() == u"spawner_name") { @@ -55,35 +55,34 @@ void Level::MakeSpawner(SceneObject obj) { } if (data->GetKey() == u"max_to_spawn") { - spawnInfo.maxToSpawn = std::stoi(data->GetValueAsString()); + spawnInfo.maxToSpawn = GeneralUtils::TryParse(data->GetValueAsString(), 0); } if (data->GetKey() == u"spawner_active_on_load") { - spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString()); + spawnInfo.activeOnLoad = GeneralUtils::TryParse(data->GetValueAsString(), false); } if (data->GetKey() == u"active_on_load") { - spawnInfo.activeOnLoad = std::stoi(data->GetValueAsString()); + spawnInfo.activeOnLoad = GeneralUtils::TryParse(data->GetValueAsString(), false); } if (data->GetKey() == u"respawn") { if (data->GetValueType() == eLDFType::LDF_TYPE_FLOAT) // Floats are in seconds { - spawnInfo.respawnTime = std::stof(data->GetValueAsString()); + spawnInfo.respawnTime = GeneralUtils::TryParse(data->GetValueAsString(), 0.0f); } else if (data->GetValueType() == eLDFType::LDF_TYPE_U32) // Ints are in ms? { - spawnInfo.respawnTime = std::stoul(data->GetValueAsString()) / 1000; + spawnInfo.respawnTime = GeneralUtils::TryParse(data->GetValueAsString(), 0) / 1000; } } if (data->GetKey() == u"spawnsGroupOnSmash") { - spawnInfo.spawnsOnSmash = std::stoi(data->GetValueAsString()); + spawnInfo.spawnsOnSmash = GeneralUtils::TryParse(data->GetValueAsString(), false); } if (data->GetKey() == u"spawnNetNameForSpawnGroupOnSmash") { spawnInfo.spawnOnSmashGroupName = data->GetValueAsString(); } if (data->GetKey() == u"groupID") { // Load object groups - std::string groupStr = data->GetValueAsString(); - spawnInfo.groups = GeneralUtils::SplitString(groupStr, ';'); + spawnInfo.groups = GeneralUtils::SplitString(data->GetValueAsString(), ';'); if (spawnInfo.groups.back().empty()) spawnInfo.groups.erase(spawnInfo.groups.end() - 1); } if (data->GetKey() == u"no_auto_spawn") { @@ -236,10 +235,11 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) { BinaryIO::BinaryRead(file, obj.lot); if (header.fileInfo.version >= 38) { - uint32_t tmp = 1; + int32_t tmp = 1; BinaryIO::BinaryRead(file, tmp); if (tmp > -1 && tmp < 11) obj.nodeType = tmp; } + if (header.fileInfo.version >= 32) { BinaryIO::BinaryRead(file, obj.glomId); } @@ -290,7 +290,7 @@ void Level::ReadSceneObjectDataChunk(std::istream& file, Header& header) { } // If this is a client only object, we can skip loading it if (data->GetKey() == u"loadOnClientOnly") { - skipLoadingObject |= static_cast(std::stoi(data->GetValueAsString())); + skipLoadingObject |= GeneralUtils::TryParse(data->GetValueAsString(), false); break; } } diff --git a/dZoneManager/Spawner.cpp b/dZoneManager/Spawner.cpp index 3baf193f..df966055 100644 --- a/dZoneManager/Spawner.cpp +++ b/dZoneManager/Spawner.cpp @@ -53,7 +53,7 @@ Spawner::Spawner(const SpawnerInfo info) { } for (Spawner* ssSpawner : spawnSmashSpawnersN) { m_SpawnSmashFoundGroup = true; - m_SpawnOnSmash = ssSpawner; + m_SpawnOnSmashID = ssSpawner ? ssSpawner->m_Info.spawnerID : LWOOBJID_EMPTY; ssSpawner->AddSpawnedEntityDieCallback([=, this]() { Spawn(); }); @@ -185,12 +185,14 @@ void Spawner::Update(const float deltaTime) { } return; } - for (size_t i = 0; i < m_WaitTimes.size(); ++i) { + for (size_t i = 0; i < m_WaitTimes.size(); ) { m_WaitTimes[i] += deltaTime; if (m_WaitTimes[i] >= m_Info.respawnTime) { m_WaitTimes.erase(m_WaitTimes.begin() + i); Spawn(); + } else { + i++; } } } @@ -222,15 +224,18 @@ void Spawner::NotifyOfEntityDeath(const LWOOBJID& objectID) { return; } - for (size_t i = 0; i < node->entities.size(); ++i) { + for (size_t i = 0; i < node->entities.size();) { if (node->entities[i] && node->entities[i] == objectID) node->entities.erase(node->entities.begin() + i); + else + i++; } m_Entities.erase(objectID); - if (m_SpawnOnSmash != nullptr) { - m_SpawnOnSmash->Reset(); + auto* const spawnOnSmash = Game::zoneManager->GetSpawner(m_SpawnOnSmashID); + if (spawnOnSmash) { + spawnOnSmash->Reset(); } } diff --git a/dZoneManager/Spawner.h b/dZoneManager/Spawner.h index f96e1892..701ceb29 100644 --- a/dZoneManager/Spawner.h +++ b/dZoneManager/Spawner.h @@ -82,7 +82,7 @@ private: EntityInfo m_EntityInfo; int32_t m_AmountSpawned = 0; bool m_Start = false; - Spawner* m_SpawnOnSmash = nullptr; + LWOOBJID m_SpawnOnSmashID = LWOOBJID_EMPTY; }; #endif // SPAWNER_H diff --git a/resources/chatconfig.ini b/resources/chatconfig.ini index 1391df44..7c6bc28c 100644 --- a/resources/chatconfig.ini +++ b/resources/chatconfig.ini @@ -12,3 +12,5 @@ web_server_enabled=0 # Unused for now # web_server_listen_ip=127.0.0.1 web_server_listen_port=2005 + +max_ignores=32