mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 15:57:13 +00:00
Several game messages read a 32 bit length and then read that many characters without checking it, so the loop ran for the whole length even after the message had ended. The length now has to fit in the data the message has left. This covers the activity state, cinematic path, death type, removed item info and inventory group messages and the shared wide string reader. Co-Authored-By: Claude Fable 5.2 <noreply@anthropic.com>
428 lines
15 KiB
C++
428 lines
15 KiB
C++
#pragma once
|
|
|
|
// C++
|
|
#include <charconv>
|
|
#include <cstdint>
|
|
#include <cmath>
|
|
#include <ctime>
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <random>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <type_traits>
|
|
|
|
#include "BitStream.h"
|
|
#include "NiPoint3.h"
|
|
#include "dPlatforms.h"
|
|
#include "Game.h"
|
|
#include "Logger.h"
|
|
#include "DluAssert.h"
|
|
|
|
#include <glm/ext/vector_float3.hpp>
|
|
|
|
enum eInventoryType : uint32_t;
|
|
enum class eObjectBits : size_t;
|
|
enum class eReplicaComponentType : uint32_t;
|
|
|
|
/*!
|
|
\file GeneralUtils.hpp
|
|
\brief A namespace containing general utility functions
|
|
*/
|
|
|
|
//! The general utils namespace
|
|
namespace GeneralUtils {
|
|
// Text for a SQL LIKE pattern with its wildcards (% and _) and the escape character escaped; use with ESCAPE '<escape>'.
|
|
// '!' works the same in SQLite and MySQL (a backslash in a MySQL string literal depends on the sql_mode).
|
|
inline std::string LikeEscape(std::string_view text, char escape = '\\') {
|
|
std::string escaped;
|
|
escaped.reserve(text.size());
|
|
for (const char c : text) {
|
|
if (c == '%' || c == '_' || c == escape) escaped += escape;
|
|
escaped += c;
|
|
}
|
|
return escaped;
|
|
}
|
|
|
|
//! Converts a plain ASCII string to a UTF-16 string
|
|
/*!
|
|
\param string The string to convert
|
|
\param size A size to trim the string to. Default is SIZE_MAX (No trimming)
|
|
\return An UTF-16 representation of the string
|
|
*/
|
|
std::u16string ASCIIToUTF16(const std::string_view string, const size_t size = SIZE_MAX);
|
|
|
|
//! Converts a UTF-8 String to a UTF-16 string
|
|
/*!
|
|
\param string The string to convert
|
|
\param size A size to trim the string to. Default is SIZE_MAX (No trimming)
|
|
\return An UTF-16 representation of the string
|
|
*/
|
|
std::u16string UTF8ToUTF16(const std::string_view string, const size_t size = SIZE_MAX);
|
|
|
|
namespace details {
|
|
//! Internal, do not use
|
|
bool _NextUTF8Char(std::string_view& slice, uint32_t& out);
|
|
}
|
|
|
|
//! Converts a Latin1 string to a UTF-8 string
|
|
/*!
|
|
\param string The string to convert
|
|
\param size A size to trim the string to. Default is SIZE_MAX (No trimming)
|
|
\return An UTF-8 representation of the string
|
|
*/
|
|
std::string Latin1ToUTF8(const std::u8string_view string, const size_t size = SIZE_MAX);
|
|
|
|
//! Converts a UTF-16 string to a UTF-8 string
|
|
/*!
|
|
\param string The string to convert
|
|
\param size A size to trim the string to. Default is SIZE_MAX (No trimming)
|
|
\return An UTF-8 representation of the string
|
|
*/
|
|
std::string UTF16ToWTF8(const std::u16string_view string, const size_t size = SIZE_MAX);
|
|
|
|
/**
|
|
* Compares two basic strings but does so ignoring case sensitivity
|
|
* \param a the first string to compare against the second string
|
|
* \param b the second string to compare against the first string
|
|
* @return if the two strings are equal
|
|
*/
|
|
bool CaseInsensitiveStringCompare(const std::string_view a, const std::string_view b);
|
|
|
|
// MARK: Bits
|
|
|
|
// MARK: Bits
|
|
|
|
//! Sets a bit on a numerical value
|
|
template <typename T>
|
|
inline void SetBit(T& value, const eObjectBits bits) {
|
|
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
|
|
const auto index = static_cast<size_t>(bits);
|
|
if (index > (sizeof(T) * 8) - 1) {
|
|
return;
|
|
}
|
|
|
|
value |= static_cast<T>(1) << index;
|
|
}
|
|
|
|
//! Clears a bit on a numerical value
|
|
template <typename T>
|
|
inline void ClearBit(T& value, const eObjectBits bits) {
|
|
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
|
|
const auto index = static_cast<size_t>(bits);
|
|
if (index > (sizeof(T) * 8 - 1)) {
|
|
return;
|
|
}
|
|
|
|
value &= ~(static_cast<T>(1) << index);
|
|
}
|
|
|
|
//! Sets a specific bit in a signed 64-bit integer
|
|
/*!
|
|
\param value The value to set the bit for
|
|
\param index The index of the bit
|
|
*/
|
|
int64_t SetBit(int64_t value, const uint32_t index);
|
|
|
|
//! Clears a specific bit in a signed 64-bit integer
|
|
/*!
|
|
\param value The value to clear the bit from
|
|
\param index The index of the bit
|
|
*/
|
|
int64_t ClearBit(int64_t value, const uint32_t index);
|
|
|
|
//! Checks a specific bit in a signed 64-bit integer
|
|
/*!
|
|
\parma value The value to check the bit in
|
|
\param index The index of the bit
|
|
\return Whether or not the bit is set
|
|
*/
|
|
bool CheckBit(int64_t value, const uint32_t index);
|
|
|
|
bool ReplaceInString(std::string& str, const std::string_view from, const std::string_view to);
|
|
|
|
/**
|
|
* Whether a stream still holds a number of values of a size. A length read from a stream is checked with this
|
|
* before that many values are read, as the stream can not hold more values than it has bytes left.
|
|
* @param inStream The stream the values are read from
|
|
* @param count The number of values
|
|
* @param valueSize The size of one value in bytes
|
|
*/
|
|
inline bool StreamHolds(const RakNet::BitStream& inStream, const uint64_t count, const uint64_t valueSize = 1) {
|
|
// Neither side can wrap: the count is at most 32 bits wide wherever it is read from a stream
|
|
return count <= UINT32_MAX && count * valueSize * 8 <= static_cast<uint64_t>(inStream.GetNumberOfUnreadBits());
|
|
}
|
|
|
|
std::u16string ReadWString(RakNet::BitStream& inStream);
|
|
|
|
std::vector<std::u16string> SplitString(const std::u16string_view str, const char16_t delimiter);
|
|
|
|
std::vector<std::string> SplitString(const std::string_view str, const char delimiter);
|
|
|
|
std::vector<std::string> GetSqlFileNamesFromFolder(const std::string_view folder);
|
|
|
|
/**
|
|
* Reads a bind address setting (bind_ip). Returns the IPv4 address to bind to, an empty string for all
|
|
* interfaces (empty, "*" or "0.0.0.0"), "127.0.0.1" for "localhost", or nullopt when the value isn't an IPv4 address.
|
|
*/
|
|
[[nodiscard]] std::optional<std::string> ParseBindAddress(const std::string_view value);
|
|
|
|
/**
|
|
* Transparent string hasher - used to allow string_view key lookups for maps storing std::string keys
|
|
* https://www.reddit.com/r/cpp_questions/comments/12xw3sn/find_stdstring_view_in_unordered_map_with/jhki225/
|
|
* https://godbolt.org/z/789xv8Eeq
|
|
*/
|
|
template <typename... Bases>
|
|
struct overload : Bases... {
|
|
using is_transparent = void;
|
|
using Bases::operator() ...;
|
|
};
|
|
|
|
struct char_pointer_hash {
|
|
auto operator()(const char* const ptr) const noexcept {
|
|
return std::hash<std::string_view>{}(ptr);
|
|
}
|
|
};
|
|
|
|
using transparent_string_hash = overload<
|
|
std::hash<std::string>,
|
|
std::hash<std::string_view>,
|
|
char_pointer_hash
|
|
>;
|
|
|
|
// Concept constraining to enum types
|
|
template <typename T>
|
|
concept Enum = std::is_enum_v<T>;
|
|
|
|
// Concept constraining to numeric types
|
|
template <typename T>
|
|
concept Numeric = std::integral<T> || Enum<T> || std::floating_point<T>;
|
|
|
|
// Concept trickery to enable parsing underlying numeric types
|
|
template <Numeric T>
|
|
struct numeric_parse { using type = T; };
|
|
|
|
// If an enum, present an alias to its underlying type for parsing
|
|
template <Numeric T> requires Enum<T>
|
|
struct numeric_parse<T> { using type = std::underlying_type_t<T>; };
|
|
|
|
// If a boolean, present an alias to an intermediate integral type for parsing
|
|
template <Numeric T> requires std::same_as<T, bool>
|
|
struct numeric_parse<T> { using type = uint8_t; };
|
|
|
|
// Shorthand type alias
|
|
template <Numeric T>
|
|
using numeric_parse_t = numeric_parse<T>::type;
|
|
|
|
/**
|
|
* For numeric values: Parses a string_view and returns an optional variable depending on the result.
|
|
* @param str The string_view to be evaluated
|
|
* @returns An std::optional containing the desired value if it is equivalent to the string
|
|
*/
|
|
template <Numeric T>
|
|
[[nodiscard]] std::optional<T> TryParse(std::string_view str) {
|
|
numeric_parse_t<T> result;
|
|
while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1);
|
|
|
|
const char* const strEnd = str.data() + str.size();
|
|
const auto [parseEnd, ec] = std::from_chars(str.data(), strEnd, result);
|
|
const bool isParsed = parseEnd == strEnd && ec == std::errc{};
|
|
|
|
return isParsed ? static_cast<T>(result) : std::optional<T>{};
|
|
}
|
|
|
|
// A version of TryParse that will return `errorVal` if `str` failed to parse.
|
|
template <Numeric T>
|
|
[[nodiscard]] T TryParse(std::string_view str, const T errorVal) {
|
|
return TryParse<T>(str).value_or(errorVal);
|
|
}
|
|
|
|
template<typename T>
|
|
requires(!Numeric<T>)
|
|
[[nodiscard]] std::optional<T> TryParse(std::string_view str);
|
|
|
|
#if !(__GNUC__ >= 11 || _MSC_VER >= 1924)
|
|
|
|
// MacOS floating-point parse helper function specializations
|
|
namespace details {
|
|
template <std::floating_point T>
|
|
[[nodiscard]] T _parse(const std::string_view str, size_t& parseNum);
|
|
}
|
|
|
|
/**
|
|
* For floating-point values: Parses a string_view and returns an optional variable depending on the result.
|
|
* Note that this function overload is only included for MacOS, as from_chars will fulfill its purpose otherwise.
|
|
* @param str The string_view to be evaluated
|
|
* @returns An std::optional containing the desired value if it is equivalent to the string
|
|
*/
|
|
template <std::floating_point T>
|
|
[[nodiscard]] std::optional<T> TryParse(std::string_view str) noexcept
|
|
try {
|
|
while (!str.empty() && std::isspace(str.front())) str.remove_prefix(1);
|
|
|
|
size_t parseNum;
|
|
const T result = details::_parse<T>(str, parseNum);
|
|
const bool isParsed = str.length() == parseNum;
|
|
|
|
return isParsed ? result : std::optional<T>{};
|
|
} catch (...) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
// A version of TryParse that will return `errorVal` if `str` failed to parse.
|
|
template <std::floating_point T>
|
|
[[nodiscard]] T TryParse(std::string_view str, const T errorVal) {
|
|
return TryParse<T>(str).value_or(errorVal);
|
|
}
|
|
|
|
#endif
|
|
|
|
/**
|
|
* The TryParse overload for handling NiPoint3 by passing 3 seperate string references
|
|
* @param strX The string representing the X coordinate
|
|
* @param strY The string representing the Y coordinate
|
|
* @param strZ The string representing the Z coordinate
|
|
* @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters
|
|
*/
|
|
template <typename T>
|
|
[[nodiscard]] std::optional<T> TryParse(const std::string_view strX, const std::string_view strY, const std::string_view strZ) {
|
|
const auto x = TryParse<float>(strX);
|
|
if (!x) return std::nullopt;
|
|
|
|
const auto y = TryParse<float>(strY);
|
|
if (!y) return std::nullopt;
|
|
|
|
const auto z = TryParse<float>(strZ);
|
|
return z ? std::make_optional<T>(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<NiPoint3>(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
|
|
* @returns An std::optional containing the desired NiPoint3 if it can be constructed from the string parameters
|
|
*/
|
|
template <typename T>
|
|
[[nodiscard]] std::optional<T> TryParse(const std::span<const std::string> str) {
|
|
return (str.size() == 3) ? TryParse<T>(str[0], str[1], str[2]) : std::nullopt;
|
|
}
|
|
|
|
// Alternative overload of TryParse with a default value
|
|
[[nodiscard]] inline NiPoint3 TryParse(const std::span<const std::string> str, const NiPoint3 errorVal) {
|
|
return TryParse<NiPoint3>(str).value_or(errorVal);
|
|
}
|
|
|
|
template <typename T>
|
|
std::u16string to_u16string(const T value) {
|
|
return GeneralUtils::ASCIIToUTF16(std::to_string(value));
|
|
}
|
|
|
|
// From boost::hash_combine
|
|
template <class T>
|
|
constexpr void hash_combine(std::size_t& s, const T& v) {
|
|
std::hash<T> h;
|
|
s ^= h(v) + 0x9e3779b9 + (s << 6) + (s >> 2);
|
|
}
|
|
|
|
// MARK: Random Number Generation
|
|
|
|
//! Generates a random number
|
|
/*!
|
|
\param min The minimum the generate from
|
|
\param max The maximum to generate to
|
|
*/
|
|
template <typename T>
|
|
inline T GenerateRandomNumber(const std::size_t min, const std::size_t max) {
|
|
// Make sure it is a numeric type
|
|
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
|
|
|
|
if constexpr (std::is_integral_v<T>) { // constexpr only necessary on first statement
|
|
std::uniform_int_distribution<T> distribution(min, max);
|
|
return distribution(Game::randomEngine);
|
|
} else if (std::is_floating_point_v<T>) {
|
|
std::uniform_real_distribution<T> distribution(min, max);
|
|
return distribution(Game::randomEngine);
|
|
}
|
|
|
|
return T();
|
|
}
|
|
|
|
template<typename Container>
|
|
inline Container::value_type GetRandomElement(const Container& container) {
|
|
DluAssert(!container.empty());
|
|
return container[GenerateRandomNumber<typename Container::size_type>(0, container.size() - 1)];
|
|
}
|
|
|
|
/**
|
|
* Casts the value of an enum entry to its underlying type
|
|
* @param entry Enum entry to cast
|
|
* @returns The enum entry's value in its underlying type
|
|
*/
|
|
template <Enum eType>
|
|
constexpr std::underlying_type_t<eType> ToUnderlying(const eType entry) noexcept {
|
|
return static_cast<std::underlying_type_t<eType>>(entry);
|
|
}
|
|
|
|
// on Windows we need to undef these or else they conflict with our numeric limits calls
|
|
// DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS DEVELOPERS
|
|
#ifdef _WIN32
|
|
#undef min
|
|
#undef max
|
|
#endif
|
|
|
|
template <typename T>
|
|
inline T GenerateRandomNumber() {
|
|
// Make sure it is a numeric type
|
|
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
|
|
|
|
return GenerateRandomNumber<T>(std::numeric_limits<T>::min(), std::numeric_limits<T>::max());
|
|
}
|
|
|
|
/**
|
|
* Generates a number from the operating system's entropy source instead of the shared game engine.
|
|
* Use this for values that must not be predictable from earlier ones, such as session keys.
|
|
*/
|
|
template <typename T>
|
|
inline T GenerateUnpredictableNumber() {
|
|
static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>, "Not an unsigned integral type");
|
|
|
|
std::random_device device;
|
|
T value = 0;
|
|
for (uint32_t bytesFilled = 0; bytesFilled < sizeof(T); bytesFilled += sizeof(uint32_t)) {
|
|
// Two half shifts, a full 32 bit shift is not valid on a 32 bit type
|
|
value = static_cast<T>(static_cast<T>(value << 16) << 16) | static_cast<T>(device());
|
|
}
|
|
return value;
|
|
}
|
|
|
|
// https://www.quora.com/How-do-you-round-to-specific-increments-like-0-5-in-C
|
|
// Rounds to the nearest floating point value specified.
|
|
template <typename T, std::enable_if_t<std::is_floating_point_v<T>, int> = 0>
|
|
T RountToNearestEven(const T value, const T modulus) {
|
|
const auto modulo = std::fmod(value, modulus);
|
|
const auto abs_modulo_2 = std::abs(modulo * 2);
|
|
const auto abs_modulus = std::abs(modulus);
|
|
|
|
bool round_away_from_zero = false;
|
|
if (abs_modulo_2 > abs_modulus) {
|
|
round_away_from_zero = true;
|
|
} else if (abs_modulo_2 == abs_modulus) {
|
|
const auto trunc_quot = std::floor(std::abs(value / modulus));
|
|
const auto odd = std::fmod(trunc_quot, T{ 2 }) != 0;
|
|
round_away_from_zero = odd;
|
|
}
|
|
|
|
if (round_away_from_zero) {
|
|
return value + (std::copysign(modulus, value) - modulo);
|
|
} else {
|
|
return value - modulo;
|
|
}
|
|
}
|
|
}
|