mirror of https://github.com/OpenRCT2/OpenRCT2.git
601 lines
17 KiB
C++
601 lines
17 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2024 OpenRCT2 developers
|
|
*
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
*****************************************************************************/
|
|
|
|
#include "Util.h"
|
|
|
|
#include "../common.h"
|
|
#include "../core/Guard.hpp"
|
|
#include "../core/Path.hpp"
|
|
#include "../interface/Window.h"
|
|
#include "../localisation/Localisation.h"
|
|
#include "../platform/Platform.h"
|
|
#include "../scenes/title/TitleScene.h"
|
|
#include "zlib.h"
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cmath>
|
|
#include <ctime>
|
|
#include <random>
|
|
|
|
int32_t SquaredMetresToSquaredFeet(int32_t squaredMetres)
|
|
{
|
|
// 1 metre squared = 10.7639104 feet squared
|
|
// RCT2 approximates as 11
|
|
return squaredMetres * 11;
|
|
}
|
|
|
|
int32_t MetresToFeet(int32_t metres)
|
|
{
|
|
// 1 metre = 3.2808399 feet
|
|
// RCT2 approximates as 3.28125
|
|
return (metres * 840) / 256;
|
|
}
|
|
|
|
int32_t MphToKmph(int32_t mph)
|
|
{
|
|
// 1 mph = 1.60934 kmph
|
|
// RCT2 approximates as 1.609375
|
|
return (mph * 1648) >> 10;
|
|
}
|
|
|
|
int32_t MphToDmps(int32_t mph)
|
|
{
|
|
// 1 mph = 4.4704 decimeters/s
|
|
return (mph * 73243) >> 14;
|
|
}
|
|
|
|
int32_t UtilBitScanForward(int32_t source)
|
|
{
|
|
#if defined(_MSC_VER) && (_MSC_VER >= 1400) // Visual Studio 2005
|
|
DWORD i;
|
|
uint8_t success = _BitScanForward(&i, static_cast<uint32_t>(source));
|
|
return success != 0 ? i : -1;
|
|
#elif defined(__GNUC__)
|
|
int32_t success = __builtin_ffs(source);
|
|
return success - 1;
|
|
#else
|
|
# pragma message("Falling back to iterative bitscan forward, consider using intrinsics")
|
|
// This is a low-hanging optimisation boost, check if your compiler offers
|
|
// any intrinsic.
|
|
// cf. https://github.com/OpenRCT2/OpenRCT2/pull/2093
|
|
for (int32_t i = 0; i < 32; i++)
|
|
if (source & (1u << i))
|
|
return i;
|
|
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
int32_t UtilBitScanForward(int64_t source)
|
|
{
|
|
#if defined(_MSC_VER) && (_MSC_VER >= 1400) && defined(_M_X64) // Visual Studio 2005
|
|
DWORD i;
|
|
uint8_t success = _BitScanForward64(&i, static_cast<uint64_t>(source));
|
|
return success != 0 ? i : -1;
|
|
#elif defined(__GNUC__)
|
|
int32_t success = __builtin_ffsll(source);
|
|
return success - 1;
|
|
#else
|
|
# pragma message("Falling back to iterative bitscan forward, consider using intrinsics")
|
|
// This is a low-hanging optimisation boost, check if your compiler offers
|
|
// any intrinsic.
|
|
// cf. https://github.com/OpenRCT2/OpenRCT2/pull/2093
|
|
for (int32_t i = 0; i < 64; i++)
|
|
if (source & (1uLL << i))
|
|
return i;
|
|
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
#if defined(__GNUC__) && (defined(__x86_64__) || defined(__i386__))
|
|
# include <cpuid.h>
|
|
# define OpenRCT2_CPUID_GNUC_X86
|
|
#elif defined(_MSC_VER) && (_MSC_VER >= 1500) && (defined(_M_X64) || defined(_M_IX86)) // VS2008
|
|
# include <intrin.h>
|
|
# include <nmmintrin.h>
|
|
# define OpenRCT2_CPUID_MSVC_X86
|
|
#endif
|
|
|
|
#ifdef OPENRCT2_X86
|
|
static bool CPUIDX86(uint32_t* cpuid_outdata, int32_t eax)
|
|
{
|
|
# if defined(OpenRCT2_CPUID_GNUC_X86)
|
|
int ret = __get_cpuid(eax, &cpuid_outdata[0], &cpuid_outdata[1], &cpuid_outdata[2], &cpuid_outdata[3]);
|
|
return ret == 1;
|
|
# elif defined(OpenRCT2_CPUID_MSVC_X86)
|
|
__cpuid(reinterpret_cast<int*>(cpuid_outdata), static_cast<int>(eax));
|
|
return true;
|
|
# else
|
|
return false;
|
|
# endif
|
|
}
|
|
#endif // OPENRCT2_X86
|
|
|
|
bool SSE41Available()
|
|
{
|
|
#ifdef OPENRCT2_X86
|
|
// SSE4.1 support is declared as the 19th bit of ECX with CPUID(EAX = 1).
|
|
uint32_t regs[4] = { 0 };
|
|
if (CPUIDX86(regs, 1))
|
|
{
|
|
return (regs[2] & (1 << 19));
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
bool AVX2Available()
|
|
{
|
|
#ifdef OPENRCT2_X86
|
|
// For GCC and similar use the builtin function, as cpuid changed its semantics in
|
|
// https://github.com/gcc-mirror/gcc/commit/132fa33ce998df69a9f793d63785785f4b93e6f1
|
|
// which causes it to ignore subleafs, but the new function is unavailable on
|
|
// Ubuntu 18.04's toolchains.
|
|
# if defined(OpenRCT2_CPUID_GNUC_X86) && (!defined(__FreeBSD__) || (__FreeBSD__ > 10))
|
|
return __builtin_cpu_supports("avx2");
|
|
# else
|
|
// AVX2 support is declared as the 5th bit of EBX with CPUID(EAX = 7, ECX = 0).
|
|
uint32_t regs[4] = { 0 };
|
|
if (CPUIDX86(regs, 7))
|
|
{
|
|
bool avxCPUSupport = (regs[1] & (1 << 5)) != 0;
|
|
if (avxCPUSupport)
|
|
{
|
|
// Need to check if OS also supports the register of xmm/ymm
|
|
// This check has to be conditional, otherwise INVALID_INSTRUCTION exception.
|
|
uint64_t xcrFeatureMask = _xgetbv(_XCR_XFEATURE_ENABLED_MASK);
|
|
avxCPUSupport = (xcrFeatureMask & 0x6) || false;
|
|
}
|
|
return avxCPUSupport;
|
|
}
|
|
# endif
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
static bool BitCountPopcntAvailable()
|
|
{
|
|
#ifdef OPENRCT2_X86
|
|
// POPCNT support is declared as the 23rd bit of ECX with CPUID(EAX = 1).
|
|
uint32_t regs[4] = { 0 };
|
|
if (CPUIDX86(regs, 1))
|
|
{
|
|
return (regs[2] & (1 << 23));
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
static int32_t BitCountPopcnt(uint32_t source)
|
|
{
|
|
// Use CPUID defines to figure out calling style
|
|
#if defined(OpenRCT2_CPUID_GNUC_X86)
|
|
// use asm directly in order to actually emit the instruction : using
|
|
// __builtin_popcount results in an extra call to a library function.
|
|
int32_t rv;
|
|
asm volatile("popcnt %1,%0" : "=r"(rv) : "rm"(source) : "cc");
|
|
return rv;
|
|
#elif defined(OpenRCT2_CPUID_MSVC_X86)
|
|
return _mm_popcnt_u32(source);
|
|
#else
|
|
Guard::Fail("bitcount_popcnt() called, without support compiled in");
|
|
return INT_MAX;
|
|
#endif
|
|
}
|
|
|
|
static int32_t BitCountLut(uint32_t source)
|
|
{
|
|
// https://graphics.stanford.edu/~seander/bithacks.html
|
|
static constexpr uint8_t BitsSetTable256[256] = {
|
|
#define B2(n) n, (n) + 1, (n) + 1, (n) + 2
|
|
#define B4(n) B2(n), B2((n) + 1), B2((n) + 1), B2((n) + 2)
|
|
#define B6(n) B4(n), B4((n) + 1), B4((n) + 1), B4((n) + 2)
|
|
B6(0), B6(1), B6(1), B6(2)
|
|
};
|
|
return BitsSetTable256[source & 0xff] + BitsSetTable256[(source >> 8) & 0xff] + BitsSetTable256[(source >> 16) & 0xff]
|
|
+ BitsSetTable256[source >> 24];
|
|
}
|
|
|
|
static const auto BitCountFn = BitCountPopcntAvailable() ? BitCountPopcnt : BitCountLut;
|
|
|
|
int32_t BitCount(uint32_t source)
|
|
{
|
|
return BitCountFn(source);
|
|
}
|
|
|
|
/* Case insensitive logical compare */
|
|
// Example:
|
|
// - Guest 10
|
|
// - Guest 99
|
|
// - Guest 100
|
|
// - John v2.0
|
|
// - John v2.1
|
|
int32_t StrLogicalCmp(const char* s1, const char* s2)
|
|
{
|
|
for (;;)
|
|
{
|
|
if (*s2 == '\0')
|
|
return *s1 != '\0';
|
|
if (*s1 == '\0')
|
|
return -1;
|
|
if (!(isdigit(static_cast<unsigned char>(*s1)) && isdigit(static_cast<unsigned char>(*s2))))
|
|
{
|
|
if (toupper(*s1) != toupper(*s2))
|
|
return toupper(*s1) - toupper(*s2);
|
|
|
|
++s1;
|
|
++s2;
|
|
}
|
|
else
|
|
{
|
|
char *lim1, *lim2;
|
|
unsigned long n1 = strtoul(s1, &lim1, 10);
|
|
unsigned long n2 = strtoul(s2, &lim2, 10);
|
|
if (n1 > n2)
|
|
return 1;
|
|
if (n1 < n2)
|
|
return -1;
|
|
|
|
s1 = lim1;
|
|
s2 = lim2;
|
|
}
|
|
}
|
|
}
|
|
|
|
char* SafeStrCpy(char* destination, const char* source, size_t size)
|
|
{
|
|
assert(destination != nullptr);
|
|
assert(source != nullptr);
|
|
|
|
if (size == 0)
|
|
return destination;
|
|
|
|
char* result = destination;
|
|
|
|
bool truncated = false;
|
|
const char* sourceLimit = source + size - 1;
|
|
const char* ch = source;
|
|
uint32_t codepoint;
|
|
while ((codepoint = UTF8GetNext(ch, &ch)) != 0)
|
|
{
|
|
if (ch <= sourceLimit)
|
|
{
|
|
destination = UTF8WriteCodepoint(destination, codepoint);
|
|
}
|
|
else
|
|
{
|
|
truncated = true;
|
|
}
|
|
}
|
|
*destination = 0;
|
|
|
|
if (truncated)
|
|
{
|
|
LOG_WARNING("Truncating string \"%s\" to %d bytes.", result, size);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
char* SafeStrCat(char* destination, const char* source, size_t size)
|
|
{
|
|
assert(destination != nullptr);
|
|
assert(source != nullptr);
|
|
|
|
if (size == 0)
|
|
{
|
|
return destination;
|
|
}
|
|
|
|
char* result = destination;
|
|
|
|
size_t i;
|
|
for (i = 0; i < size; i++)
|
|
{
|
|
if (*destination == '\0')
|
|
{
|
|
break;
|
|
}
|
|
|
|
destination++;
|
|
}
|
|
|
|
bool terminated = false;
|
|
for (; i < size; i++)
|
|
{
|
|
if (*source != '\0')
|
|
{
|
|
*destination++ = *source++;
|
|
}
|
|
else
|
|
{
|
|
*destination = *source;
|
|
terminated = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!terminated)
|
|
{
|
|
result[size - 1] = '\0';
|
|
LOG_WARNING("Truncating string \"%s\" to %d bytes.", result, size);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
uint32_t UtilRand()
|
|
{
|
|
thread_local std::mt19937 _prng(std::random_device{}());
|
|
return _prng();
|
|
}
|
|
|
|
// Returns a random floating point number from the Standard Normal Distribution; mean of 0 and standard deviation of 1.
|
|
// TODO: In C++20 this can be templated, where the standard deviation is passed as a value template argument.
|
|
float UtilRandNormalDistributed()
|
|
{
|
|
thread_local std::mt19937 _prng{ std::random_device{}() };
|
|
thread_local std::normal_distribution<float> _distributor{ 0.0f, 1.0f };
|
|
return _distributor(_prng);
|
|
}
|
|
|
|
constexpr size_t CHUNK = 128 * 1024;
|
|
|
|
// Compress the source to gzip-compatible stream, write to dest.
|
|
// Mainly used for compressing the crashdumps
|
|
bool UtilGzipCompress(FILE* source, FILE* dest)
|
|
{
|
|
if (source == nullptr || dest == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
int ret, flush;
|
|
size_t have;
|
|
z_stream strm{};
|
|
strm.zalloc = Z_NULL;
|
|
strm.zfree = Z_NULL;
|
|
strm.opaque = Z_NULL;
|
|
unsigned char in[CHUNK];
|
|
unsigned char out[CHUNK];
|
|
int windowBits = 15;
|
|
int GZIP_ENCODING = 16;
|
|
ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowBits | GZIP_ENCODING, 8, Z_DEFAULT_STRATEGY);
|
|
if (ret != Z_OK)
|
|
{
|
|
LOG_ERROR("Failed to initialise stream");
|
|
return false;
|
|
}
|
|
do
|
|
{
|
|
strm.avail_in = uInt(fread(in, 1, CHUNK, source));
|
|
if (ferror(source))
|
|
{
|
|
deflateEnd(&strm);
|
|
LOG_ERROR("Failed to read data from source");
|
|
return false;
|
|
}
|
|
flush = feof(source) ? Z_FINISH : Z_NO_FLUSH;
|
|
strm.next_in = in;
|
|
do
|
|
{
|
|
strm.avail_out = CHUNK;
|
|
strm.next_out = out;
|
|
ret = deflate(&strm, flush);
|
|
if (ret == Z_STREAM_ERROR)
|
|
{
|
|
LOG_ERROR("Failed to compress data");
|
|
return false;
|
|
}
|
|
have = CHUNK - strm.avail_out;
|
|
if (fwrite(out, 1, have, dest) != have || ferror(dest))
|
|
{
|
|
deflateEnd(&strm);
|
|
LOG_ERROR("Failed to write data to destination");
|
|
return false;
|
|
}
|
|
} while (strm.avail_out == 0);
|
|
} while (flush != Z_FINISH);
|
|
deflateEnd(&strm);
|
|
return true;
|
|
}
|
|
|
|
std::vector<uint8_t> Gzip(const void* data, const size_t dataLen)
|
|
{
|
|
assert(data != nullptr);
|
|
|
|
std::vector<uint8_t> output;
|
|
z_stream strm{};
|
|
strm.zalloc = Z_NULL;
|
|
strm.zfree = Z_NULL;
|
|
strm.opaque = Z_NULL;
|
|
|
|
{
|
|
const auto ret = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY);
|
|
if (ret != Z_OK)
|
|
{
|
|
throw std::runtime_error("deflateInit2 failed with error " + std::to_string(ret));
|
|
}
|
|
}
|
|
|
|
int flush = 0;
|
|
const auto* src = static_cast<const Bytef*>(data);
|
|
size_t srcRemaining = dataLen;
|
|
do
|
|
{
|
|
const auto nextBlockSize = std::min(srcRemaining, CHUNK);
|
|
srcRemaining -= nextBlockSize;
|
|
|
|
flush = srcRemaining == 0 ? Z_FINISH : Z_NO_FLUSH;
|
|
strm.avail_in = static_cast<uInt>(nextBlockSize);
|
|
strm.next_in = const_cast<Bytef*>(src);
|
|
do
|
|
{
|
|
output.resize(output.size() + nextBlockSize);
|
|
strm.avail_out = static_cast<uInt>(nextBlockSize);
|
|
strm.next_out = &output[output.size() - nextBlockSize];
|
|
const auto ret = deflate(&strm, flush);
|
|
if (ret == Z_STREAM_ERROR)
|
|
{
|
|
throw std::runtime_error("deflate failed with error " + std::to_string(ret));
|
|
}
|
|
output.resize(output.size() - strm.avail_out);
|
|
} while (strm.avail_out == 0);
|
|
|
|
src += nextBlockSize;
|
|
} while (flush != Z_FINISH);
|
|
deflateEnd(&strm);
|
|
return output;
|
|
}
|
|
|
|
std::vector<uint8_t> Ungzip(const void* data, const size_t dataLen)
|
|
{
|
|
assert(data != nullptr);
|
|
|
|
std::vector<uint8_t> output;
|
|
z_stream strm{};
|
|
strm.zalloc = Z_NULL;
|
|
strm.zfree = Z_NULL;
|
|
strm.opaque = Z_NULL;
|
|
|
|
{
|
|
const auto ret = inflateInit2(&strm, 15 | 16);
|
|
if (ret != Z_OK)
|
|
{
|
|
throw std::runtime_error("inflateInit2 failed with error " + std::to_string(ret));
|
|
}
|
|
}
|
|
|
|
int flush = 0;
|
|
const auto* src = static_cast<const Bytef*>(data);
|
|
size_t srcRemaining = dataLen;
|
|
do
|
|
{
|
|
const auto nextBlockSize = std::min(srcRemaining, CHUNK);
|
|
srcRemaining -= nextBlockSize;
|
|
|
|
flush = srcRemaining == 0 ? Z_FINISH : Z_NO_FLUSH;
|
|
strm.avail_in = static_cast<uInt>(nextBlockSize);
|
|
strm.next_in = const_cast<Bytef*>(src);
|
|
do
|
|
{
|
|
output.resize(output.size() + nextBlockSize);
|
|
strm.avail_out = static_cast<uInt>(nextBlockSize);
|
|
strm.next_out = &output[output.size() - nextBlockSize];
|
|
const auto ret = inflate(&strm, flush);
|
|
if (ret == Z_STREAM_ERROR)
|
|
{
|
|
throw std::runtime_error("deflate failed with error " + std::to_string(ret));
|
|
}
|
|
output.resize(output.size() - strm.avail_out);
|
|
} while (strm.avail_out == 0);
|
|
|
|
src += nextBlockSize;
|
|
} while (flush != Z_FINISH);
|
|
inflateEnd(&strm);
|
|
return output;
|
|
}
|
|
|
|
// Type-independent code left as macro to reduce duplicate code.
|
|
#define ADD_CLAMP_BODY(value, value_to_add, min_cap, max_cap) \
|
|
if ((value_to_add > 0) && (value > (max_cap - (value_to_add)))) \
|
|
{ \
|
|
value = max_cap; \
|
|
} \
|
|
else if ((value_to_add < 0) && (value < (min_cap - (value_to_add)))) \
|
|
{ \
|
|
value = min_cap; \
|
|
} \
|
|
else \
|
|
{ \
|
|
value += value_to_add; \
|
|
}
|
|
|
|
int8_t AddClamp_int8_t(int8_t value, int8_t value_to_add)
|
|
{
|
|
ADD_CLAMP_BODY(value, value_to_add, INT8_MIN, INT8_MAX);
|
|
return value;
|
|
}
|
|
|
|
int16_t AddClamp_int16_t(int16_t value, int16_t value_to_add)
|
|
{
|
|
ADD_CLAMP_BODY(value, value_to_add, INT16_MIN, INT16_MAX);
|
|
return value;
|
|
}
|
|
|
|
int32_t AddClamp_int32_t(int32_t value, int32_t value_to_add)
|
|
{
|
|
ADD_CLAMP_BODY(value, value_to_add, INT32_MIN, INT32_MAX);
|
|
return value;
|
|
}
|
|
|
|
int64_t AddClamp_int64_t(int64_t value, int64_t value_to_add)
|
|
{
|
|
ADD_CLAMP_BODY(value, value_to_add, INT64_MIN, INT64_MAX);
|
|
return value;
|
|
}
|
|
|
|
money64 AddClamp_money64(money64 value, money64 value_to_add)
|
|
{
|
|
// This function is intended only for clarity, as money64
|
|
// is technically the same as int64_t
|
|
assert_struct_size(money64, sizeof(int64_t));
|
|
return AddClamp_int64_t(value, value_to_add);
|
|
}
|
|
|
|
#undef add_clamp_body
|
|
|
|
uint8_t Lerp(uint8_t a, uint8_t b, float t)
|
|
{
|
|
if (t <= 0)
|
|
return a;
|
|
if (t >= 1)
|
|
return b;
|
|
|
|
int32_t range = b - a;
|
|
int32_t amount = static_cast<int32_t>(range * t);
|
|
return static_cast<uint8_t>(a + amount);
|
|
}
|
|
|
|
float FLerp(float a, float b, float f)
|
|
{
|
|
return (a * (1.0f - f)) + (b * f);
|
|
}
|
|
|
|
uint8_t SoftLight(uint8_t a, uint8_t b)
|
|
{
|
|
float fa = a / 255.0f;
|
|
float fb = b / 255.0f;
|
|
float fr;
|
|
if (fb < 0.5f)
|
|
{
|
|
fr = (2 * fa * fb) + ((fa * fa) * (1 - (2 * fb)));
|
|
}
|
|
else
|
|
{
|
|
fr = (2 * fa * (1 - fb)) + (std::sqrt(fa) * ((2 * fb) - 1));
|
|
}
|
|
return static_cast<uint8_t>(std::clamp(fr, 0.0f, 1.0f) * 255.0f);
|
|
}
|
|
|
|
/**
|
|
* strftime wrapper which appends to an existing string.
|
|
*/
|
|
size_t StrCatFTime(char* buffer, size_t bufferSize, const char* format, const struct tm* tp)
|
|
{
|
|
size_t stringLen = strnlen(buffer, bufferSize);
|
|
if (stringLen < bufferSize)
|
|
{
|
|
char* dst = buffer + stringLen;
|
|
size_t dstMaxSize = bufferSize - stringLen;
|
|
return strftime(dst, dstMaxSize, format, tp);
|
|
}
|
|
return 0;
|
|
}
|