17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 23:49:19 +02:00
Files
maszyna/utilities/utilities.cpp
jerrrrycho 8125166690 Fix/sonar reliability security (#205)
* fix(sonar): addressing reliability/security issues

* fix(sonar): addressing reliability/security issues

* fix(sonar): addressing reliability/security issues

* fix(sonar): addressing last reliability/security issues

* fix(sonar): addressing last reliability/security issues

* fix(sonar): addressing last reliability/security issues

* refactor(sonar): imgui update

* refactor(sonar): hasChar revert
2026-10-11 15:31:45 +02:00

665 lines
19 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
/*
MaSzyna EU07 - SPKS
Brakes.
Copyright (C) 2007-2014 Maciej Cierniak
*/
//#include "stdafx.h"
//
//#include <sys/types.h>
//#include <sys/stat.h>
#include <charconv>
#include <ranges>
#include <format>
//#ifndef WIN32
//#include <unistd.h>
//#endif
//
//#ifdef WIN32
//#define stat _stat
//#endif
#include "utilities/utilities.h"
#include "utilities/Globals.h"
#include "utilities/parser.h"
#include "utilities/U8.h"
#include "utilities/Logs.h"
// TODO: This shouldn't be in Globals?
bool DebugModeFlag = false;
bool FreeFlyModeFlag = false;
bool EditorModeFlag = false;
bool DebugCameraFlag = false;
bool DebugTractionFlag = false;
std::string Now()
{
#if defined(__APPLE__)
auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
std::tm tm{};
localtime_r(&t, &tm);
char buf[64];
std::strftime(buf, sizeof(buf), "%c", &tm);
return std::string(buf);
#else
auto now = std::chrono::system_clock::now();
auto local = std::chrono::current_zone()->to_local(now);
return std::format("{:%c}", local);
#endif
}
std::filesystem::path user_config_path(const std::string &filename)
{
namespace fs = std::filesystem;
#if defined(_WIN32)
if (const char *appdata = std::getenv("APPDATA"))
return fs::path(appdata) / "MaSzyna" / filename;
#elif defined(__APPLE__)
if (const char *home = std::getenv("HOME"))
return fs::path(home) / "Library" / "Application Support" / "MaSzyna" / filename;
#else
if (const char *home = std::getenv("HOME"))
return fs::path(home) / ".config" / "MaSzyna" / filename;
#endif
return {}; // env var missing → caller falls back to CWD-relative filename
}
// zwraca różnicę czasu
// jeśli pierwsza jest aktualna, a druga rozkładowa, to ujemna oznacza opóżnienie
// na dłuższą metę trzeba uwzględnić datę, jakby opóżnienia miały przekraczać 12h (towarowych)
double CompareTime(double t1h, double t1m, double t2h, double t2m)
{
if (t2h < 0)
return 0;
else
{
auto t = (t2h - t1h) * 60 + t2m - t1m; // jeśli t2=00:05, a t1=23:50, to różnica wyjdzie ujemna
if (t < -720) // jeśli różnica przekracza 12h na minus
t = t + 1440; // to dodanie doby minut;else
if (t > 720) // jeśli przekracza 12h na plus
t = t - 1440; // to odjęcie doby minut
return t;
}
}
bool SetFlag(int &Flag, int const Value)
{
if (Value > 0)
{
if (false == TestFlag(Flag, Value))
{
Flag |= Value;
return true; // true, gdy było wcześniej 0 i zostało ustawione
}
}
else if (Value < 0)
{
// Value jest ujemne, czyli zerowanie flagi
return ClearFlag(Flag, -Value);
}
return false;
}
bool ClearFlag(int &Flag, int const Value)
{
if (true == TestFlag(Flag, Value))
{
Flag &= ~Value;
return true;
}
else
{
return false;
}
}
double Random(double min, double max)
{
if (max < min) { std::swap(min, max); } // std::uniform_real_distribution requires min <= max (inverted bounds are UB)
std::uniform_real_distribution dist(min, max);
return dist(Global.random_engine);
}
int Random(int min, int max)
{
if (max < min) { std::swap(min, max); } // std::uniform_int_distribution requires min <= max (inverted bounds are UB)
std::uniform_int_distribution dist(min, max);
return dist(Global.random_engine);
}
std::uint64_t true_random_seed()
{
try
{
std::random_device rd;
if (rd.entropy() > 0.0)
{
// NOTE: both halves have to be 64 bit wide before the shift - shifting a std::uint32_t
// by 32 is undefined behaviour, and drops the high half of the seed
std::uint64_t const high{rd()};
std::uint64_t const low{rd()};
return (high << 32) | low;
}
}
catch (std::exception const &Error)
{
// std::random_device throws when the machine has no usable hardware entropy source;
// the clock below stands in for it, which is worth saying out loud since it is a
// weaker seed than the caller asked for
WriteLog(std::format("random: no hardware entropy source ({}), seeding from the clock instead", Error.what()));
}
return static_cast<std::uint64_t>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
}
std::uint32_t seed_of(std::string const &Text)
{
if (std::uint32_t number{}; std::from_chars(Text.data(), Text.data() + Text.size(), number).ec == std::errc{})
{
return number;
}
std::uint32_t hash{};
for (auto const character : Text)
{
hash = hash * 31u + static_cast<std::uint32_t>(static_cast<unsigned char>(character));
}
return hash;
}
std::string generate_uuid_v4()
{
std::uniform_int_distribution dist(0, 255);
std::array<uint8_t, 16> bytes;
for (auto &b : bytes)
b = static_cast<uint8_t>(dist(Global.random_engine));
// UUID v4 (RFC 4122)
bytes[6] = bytes[6] & 0x0F | 0x40;
bytes[8] = bytes[8] & 0x3F | 0x80;
return std::format("{:02x}{:02x}{:02x}{:02x}-"
"{:02x}{:02x}-"
"{:02x}{:02x}-"
"{:02x}{:02x}-"
"{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]);
}
double LocalRandom(double a, double b)
{
if (b < a) { std::swap(a, b); } // std::uniform_real_distribution requires a <= b (inverted bounds are UB)
std::uniform_real_distribution dist(a, b);
return dist(Global.local_random_engine);
}
bool FuzzyLogic(double Test, double Threshold, double Probability)
{
if (Test > Threshold && !DebugModeFlag)
return Random() < Probability * Threshold * 1.0 / Test /*im wiekszy Test tym wieksza szansa*/;
else
return false;
}
bool FuzzyLogicAI(double Test, double Threshold, double Probability)
{
if (Test > Threshold)
return Random() < Probability * Threshold * 1.0 / Test /*im wiekszy Test tym wieksza szansa*/;
else
return false;
}
std::vector<std::string> Split(std::string_view s, char delim)
{ // dzieli tekst na wektor tekstow
std::vector<std::string> out;
for (const auto& part : s | std::ranges::views::split(delim))
out.emplace_back(part.begin(), part.end());
return out;
}
std::pair<std::string, int> split_string_and_number(std::string const &Key)
{
auto const indexstart{Key.find_first_of("-1234567890")};
auto const indexend{Key.find_first_not_of("-1234567890", indexstart)};
if (indexstart != std::string::npos)
{
return {Key.substr(0, indexstart), std::stoi(Key.substr(indexstart, indexend - indexstart))};
}
return {Key, 0};
}
std::string to_string(int Value, int width)
{
std::ostringstream o;
o << std::setw(width) << Value;
return std::move(o).str();
};
std::string to_string(double Value, int precision)
{
return std::format("{:.{}f}", Value, precision);
};
std::string to_string(double const Value, int const Precision, int const Width)
{
return std::format("{:>{}.{}f}", Value, Width, Precision);
};
std::string to_hex_str(int const Value, int const Width)
{
// iostreams print negative values in hex as their unsigned representation
return std::format("0x{:0{}X}", static_cast<unsigned int>(Value), Width);
};
std::string const fractionlabels[] = {U8(" "), U8("¹"), U8("²"), U8("³"), U8("⁴"), U8("⁵"), U8("⁶"), U8("⁷"), U8("⁸"), U8("⁹")};
std::string to_minutes_str(float const Minutes, bool const Leadingzero, int const Width)
{
float minutesintegral;
auto const minutesfractional{std::modf(Minutes, &minutesintegral)};
auto const width{Width - 1};
auto minutes = std::string(width - 1, ' ') + (Leadingzero ? std::to_string(100 + minutesintegral).substr(1, 2) : to_string(minutesintegral, 0));
return minutes.substr(minutes.size() - width, width) + fractionlabels[static_cast<int>(std::floor(minutesfractional * 10 + 0.1))];
}
int stol_def(const std::string &str, const int &DefaultValue)
{
int result{DefaultValue};
std::stringstream converter;
converter << str;
converter >> result;
return result;
}
std::string ToLower(std::string const &text)
{
auto lowercase{text};
std::ranges::transform(text, std::begin(lowercase), [](unsigned char c) { return std::tolower(c); });
return lowercase;
}
std::string ToUpper(std::string const &text)
{
auto uppercase{text};
std::ranges::transform(text, std::begin(uppercase), [](unsigned char c) { return std::toupper(c); });
return uppercase;
}
// replaces polish letters with basic ascii
void win1250_to_ascii(std::string &Input)
{
std::unordered_map<char, char> const charmap{{165, 'A'}, {198, 'C'}, {202, 'E'}, {163, 'L'}, {209, 'N'}, {211, 'O'}, {140, 'S'}, {143, 'Z'}, {175, 'Z'},
{185, 'a'}, {230, 'c'}, {234, 'e'}, {179, 'l'}, {241, 'n'}, {243, 'o'}, {156, 's'}, {159, 'z'}, {191, 'z'}};
std::unordered_map<char, char>::const_iterator lookup;
for (auto &input : Input)
{
if ((lookup = charmap.find(input)) != charmap.end())
input = lookup->second;
}
}
std::string win1250_to_utf8(const std::string &Input)
{
std::unordered_map<char, std::string> const charmap{{165, "Ą"}, {198, "Ć"}, {202, "Ę"}, {163, "Ł"}, {209, "Ń"}, {211, "Ó"}, {140, "Ś"}, {143, "Ź"}, {175, "Ż"},
{185, "ą"}, {230, "ć"}, {234, "ę"}, {179, "ł"}, {241, "ń"}, {243, "ó"}, {156, "ś"}, {159, "ź"}, {191, "ż"}};
std::string output;
std::unordered_map<char, std::string>::const_iterator lookup;
for (auto &input : Input)
{
if ((lookup = charmap.find(input)) != charmap.end())
output += lookup->second;
else
output += input;
}
return output;
}
namespace
{
// unicode code points of the upper half of the windows-1250 code page; 0 marks the unassigned bytes
std::array<char32_t, 128> const win1250_upperhalf{
0x20AC, 0x0000, 0x201A, 0x0000, 0x201E, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0160, 0x2039, 0x015A, 0x0164, 0x017D, 0x0179,
0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0161, 0x203A, 0x015B, 0x0165, 0x017E, 0x017A,
0x00A0, 0x02C7, 0x02D8, 0x0141, 0x00A4, 0x0104, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x015E, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x017B,
0x00B0, 0x00B1, 0x02DB, 0x0142, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x0105, 0x015F, 0x00BB, 0x013D, 0x02DD, 0x013E, 0x017C,
0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E,
0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7, 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF,
0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F,
0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7, 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9};
// returns number of bytes of the utf-8 sequence started by provided byte, or 0 if it can't start one
std::size_t utf8_sequence_length(std::byte const Lead)
{
if ((Lead & std::byte{0x80}) == std::byte{0x00})
{
return 1;
}
if ((Lead & std::byte{0xE0}) == std::byte{0xC0})
{
return 2;
}
if ((Lead & std::byte{0xF0}) == std::byte{0xE0})
{
return 3;
}
if ((Lead & std::byte{0xF8}) == std::byte{0xF0})
{
return 4;
}
return 0; // a continuation byte, or a lead of a sequence utf-8 doesn't allow
}
} // namespace
bool utf8_to_utf32(std::string const &Text, std::u32string &Output)
{
// lowest code point which needs given number of bytes; shorter values are overlong forms
static std::array<char32_t, 5> const minimum{0, 0, 0x80, 0x800, 0x10000};
Output.clear();
auto const size{Text.size()};
for (std::size_t idx = 0; idx < size;)
{
auto const lead{static_cast<std::byte>(Text[idx])};
auto const length{utf8_sequence_length(lead)};
if (length == 0 || idx + length > size)
{
return false;
}
auto codepoint{std::to_integer<char32_t>(lead & (std::byte{0xFF} >> (length > 1 ? length + 1 : 1)))};
for (std::size_t offset = 1; offset < length; ++offset)
{
auto const next{static_cast<std::byte>(Text[idx + offset])};
if ((next & std::byte{0xC0}) != std::byte{0x80})
{
return false;
}
codepoint = (codepoint << 6) | std::to_integer<char32_t>(next & std::byte{0x3F});
}
if (codepoint < minimum[length] || codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF))
{
return false;
}
Output += codepoint;
idx += length;
}
return true;
}
void win1250_to_utf32(std::string const &Text, std::u32string &Output)
{
Output.clear();
for (auto const character : Text)
{
auto const value{std::to_integer<std::size_t>(static_cast<std::byte>(character))};
Output += (value < 0x80 ? static_cast<char32_t>(value) : win1250_upperhalf[value - 0x80]);
}
}
// Ra: tymczasowe rozwiązanie kwestii zagranicznych (czeskich) napisów
char const charsetconversiontable[] = "E?,?\"_++?%S<STZZ?`'\"\".--??s>stzz"
" ^^L$A|S^CS<--RZo±,l'uP.,as>L\"lz"
"RAAAALCCCEEEEIIDDNNOOOOxRUUUUYTB"
"raaaalccceeeeiiddnnoooo-ruuuuyt?";
// wycięcie liter z ogonkami
std::string Bezogonkow(std::string Input, bool const Underscorestospaces)
{
char const extendedcharsetbit{static_cast<char>(0x80)};
char const space{' '};
char const underscore{'_'};
for (auto &input : Input)
{
if (input & extendedcharsetbit)
{
input = charsetconversiontable[input ^ extendedcharsetbit];
}
else if (input < space)
{
input = space;
}
else if (Underscorestospaces && input == underscore)
{
input = space;
}
}
return Input;
}
template <> bool extract_value(bool &Variable, std::string const &Key, std::string const &Input, std::string const &Default)
{
auto value = extract_value(Key, Input);
if (false == value.empty())
{
// set the specified variable to retrieved value
Variable = ToLower(value) == "yes";
return true; // located the variable
}
else
{
// set the variable to provided default value
if (false == Default.empty())
{
Variable = ToLower(Default) == "yes";
}
return false; // couldn't locate the variable in provided input
}
}
bool FileExists(std::string const &Filename)
{
return std::filesystem::exists(Filename);
}
std::pair<std::string, std::string> FileExists(std::vector<std::string> const &Names, std::vector<std::string> const &Extensions)
{
for (auto const &name : Names)
{
for (auto const &extension : Extensions)
{
if (FileExists(name + extension))
{
return {name, extension};
}
}
}
// nothing found
return {{}, {}};
}
// returns time of last modification for specified file
std::time_t last_modified(std::string const &Filename)
{
std::string fn = Filename;
struct stat filestat;
if (::stat(fn.c_str(), &filestat) == 0)
return filestat.st_mtime;
else
return 0;
}
std::tm local_tm(std::time_t Time)
{
std::tm tm{};
#ifdef _WIN32
localtime_s(&tm, &Time);
#else
localtime_r(&Time, &tm);
#endif
return tm;
}
std::tm utc_tm(std::time_t Time)
{
std::tm tm{};
#ifdef _WIN32
gmtime_s(&tm, &Time);
#else
gmtime_r(&Time, &tm);
#endif
return tm;
}
// potentially erases file extension from provided file name. returns: true if extension was removed, false otherwise
bool erase_extension(std::string &Filename)
{
auto const extensionpos{Filename.rfind('.')};
if (extensionpos == std::string::npos)
{
return false;
}
if (extensionpos != Filename.rfind("..") + 1)
{
// we can get extension for .mat or, in legacy files, some image format. just trim it and set it to material file extension
Filename.erase(extensionpos);
return true;
}
return false;
}
void erase_leading_slashes(std::string &Filename)
{
auto pos = Filename.find_first_not_of('/');
Filename.erase(0, pos);
}
// potentially replaces backward slashes in provided file path with unix-compatible forward slashes
void replace_slashes(std::string &Filename)
{
std::ranges::replace(Filename, '\\', '/');
}
// returns potential path part from provided file name
std::string substr_path(std::string const &Filename)
{
// String::substr returns new string so substr_path has to return std::string
if (auto pos = Filename.rfind('/'); pos != std::string::npos)
return Filename.substr(0, pos + 1);
return {};
}
// returns length of common prefix between two provided strings
std::ptrdiff_t len_common_prefix(std::string_view a, std::string_view b)
{
auto [it1, it2] = std::ranges::mismatch(a, b);
return std::distance(a.begin(), it1);
}
// returns true if provided string contains another provided string
bool contains(std::string_view const String, std::string_view Substring)
{
// To be replaced with string::contains in C++ 23
return String.find(Substring) != std::string::npos;
}
bool contains(std::string_view const String, char Character)
{
// To be replaced with string::contains in C++ 23
return String.find(Character) != std::string::npos;
}
// helper, restores content of a 3d vector from provided input stream
// TODO: review and clean up the helper routines, there's likely some redundant ones
glm::dvec3 LoadPoint(cParser &Input)
{
// pobranie współrzędnych punktu
glm::dvec3 point;
Input.getTokens(3);
Input >> point.x >> point.y >> point.z;
return point;
}
// extracts a group of tokens from provided data stream, returns one of them picked randomly
std::string deserialize_random_set(cParser &Input, char const *Break)
{
auto token{Input.getToken<std::string>(true, Break)};
std::ranges::replace(token, '\\', '/');
if (token != "[")
{
// simple case, single token
return token;
}
// if instead of a single token we've encountered '[' this marks a beginning of a random set
// we retrieve all entries, then return a random one
std::vector<std::string> tokens;
while ((token = deserialize_random_set(Input, Break)) != "" && token != "]")
{
tokens.emplace_back(token);
}
if (false == tokens.empty())
{
// scenery random set: must replay from the shared run seed, not a security concern
std::shuffle(std::begin(tokens), std::end(tokens), Global.random_engine); // NOSONAR
return tokens.front();
}
else
{
// shouldn't ever get here but, eh
return "";
}
}
// returns hash of provided string, combined with provided seed
std::uint32_t hash_string(std::string const &String, std::uint32_t const Seed)
{
// FNV-1a, 32 bit, with the seed mixed into the standard offset basis
std::uint32_t hash{2166136261u ^ Seed};
for (auto const character : String)
{
hash ^= static_cast<std::uint32_t>(static_cast<unsigned char>(character));
hash *= 16777619u;
}
return hash;
}
// extracts a group of tokens from provided data stream, returns all of them
std::vector<std::string> deserialize_set(cParser &Input, char const *Break)
{
std::vector<std::string> tokens;
auto token{Input.getToken<std::string>(true, Break)};
std::ranges::replace(token, '\\', '/');
if (token != "[")
{
// simple case, single token
if (false == token.empty())
{
tokens.emplace_back(token);
}
return tokens;
}
// '[' marks a beginning of a set, retrieve all entries until it's closed
while ((token = deserialize_random_set(Input, Break)) != "" && token != "]")
{
tokens.emplace_back(token);
}
return tokens;
}