1
0
mirror of https://github.com/OpenRCT2/OpenRCT2 synced 2026-01-26 00:04:43 +01:00
Files
OpenRCT2/src/openrct2/util/Util.cpp
2024-07-09 16:43:03 +02:00

448 lines
12 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 "../Diagnostic.h"
#include "../common.h"
#include "../core/Guard.hpp"
#include "../core/Path.hpp"
#include "../core/UTF8.h"
#include "../interface/Window.h"
#include "../platform/Platform.h"
#include "../scenes/title/TitleScene.h"
#include "zlib.h"
#include <algorithm>
#include <cassert>
#include <cctype>
#include <cmath>
#include <ctime>
#include <random>
#ifdef _WIN32
# ifndef NOMINMAX
# define NOMINMAX
# endif
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# include <windows.h>
#endif // _WIN32
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
}
/* 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;
}
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;
}