eq2go/old/common/misc_functions.hpp
2025-08-06 19:00:30 -05:00

1154 lines
28 KiB
C++

// EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - GPL v3
#pragma once
#include <stdio.h>
#include <ctype.h>
#include <vector>
#include <map>
#include <tuple>
#include <cstdint>
#include <string>
#include <iostream>
#include <iomanip>
#include <chrono>
#include <random>
#include <algorithm>
#include <memory>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>
#include <netdb.h>
#include <errno.h>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <stdarg.h>
#include "log.hpp"
#include "types.hpp"
#include "debug.hpp"
#include "timer.hpp"
#include "separator.hpp"
#include "packet/packet_dump.hpp"
#ifndef ERRBUF_SIZE
#define ERRBUF_SIZE 1024
#endif
#ifndef _ITOA_BUFLEN
#define _ITOA_BUFLEN 25
#endif
#ifndef PATCHER
extern std::map<int16, int16> EQOpcodeVersions;
#endif
using namespace std;
// Function declarations
int32 hextoi(char* num);
int64 hextoi64(char* num);
sint32 filesize(FILE* fp);
int32 ResolveIP(const char* hostname, char* errbuf = 0);
void CoutTimestamp(bool ms = true);
string loadInt32String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_32BitString* eq_string = nullptr);
string loadInt16String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_16BitString* eq_string = nullptr);
string loadInt8String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_8BitString* eq_string = nullptr);
sint16 storeInt32String(uchar* buffer, int16 buffer_size, string in_str);
sint16 storeInt16String(uchar* buffer, int16 buffer_size, string in_str);
sint16 storeInt8String(uchar* buffer, int16 buffer_size, string in_str);
int MakeRandomInt(int low, int high);
float MakeRandomFloat(float low, float high);
float TransformToFloat(sint16 data, int8 bits);
sint16 TransformFromFloat(float data, int8 bits);
int32 GenerateEQ2Color(float* r, float* g, float* b);
int32 GenerateEQ2Color(float* rgb[3]);
void SetColor(EQ2_Color* color, long data);
int8 MakeInt8(float* input);
bool Unpack(int32 srcLen, uchar* data, uchar* dst, int16 dstLen, int16 version = 0, bool reverse = true);
bool Unpack(uchar* data, uchar* dst, int16 dstLen, int16 version = 0, bool reverse = true);
int32 Pack(uchar* data, uchar* src, int16 srcLen, int16 dstLen, int16 version = 0, bool reverse = true);
void Reverse(uchar* input, int32 srcLen);
void Encode(uchar* dst, uchar* src, int16 len);
void Decode(uchar* dst, uchar* src, int16 len);
string ToUpper(string input);
string ToLower(string input);
int32 ParseIntValue(string input);
int64 ParseLongLongValue(string input);
std::map<string, string> TranslateBrokerRequest(string request);
void MovementDecode(uchar* dst, uchar* newval, uchar* orig, int16 len);
vector<string>* SplitString(string str, char delim);
int8 DoOverLoad(int32 val, uchar* data);
int8 CheckOverLoadSize(int32 val);
int32 CountWordsInString(const char* text);
bool IsNumber(const char *num);
void PrintSep(Seperator *sep, const char *name = 0);
string GetDeviceName(string device);
int32 GetDeviceID(string device);
int16 GetItemPacketType(int32 version);
int16 GetOpcodeVersion(int16 version);
void SleepMS(int32 milliseconds);
size_t strlcpy(char *dst, const char *src, size_t size);
float short_to_float(const ushort x);
uint32 float_to_int(const float x);
uint32 as_uint(const float x);
float as_float(const uint32 x);
int64 getCurrentTimestamp();
std::tuple<int64, int64, int64, int64> convertTimestampDuration(int64 total_milliseconds);
bool INIReadBool(FILE *f, const char *section, const char *property, bool *out);
bool INIReadInt(FILE *f, const char *section, const char *property, int *out);
const char* itoa(int value);
char* itoa(int value, char *result, int base);
// Template function definitions
template<class Type>
void AddData(Type input, string* datastring)
{
if(datastring)
datastring->append((char*)&input, sizeof(input));
}
template<class Type>
void AddData(Type input, int32 array_size, string* datastring)
{
if(array_size > 0) {
for(int32 i = 0; i < array_size; i++)
AddData(input[i], datastring);
}
else
AddData(input, datastring);
}
template<class T>
class AutoDelete
{
public:
AutoDelete(T** iVar, T* iSetTo = 0)
{
init(iVar, iSetTo);
}
AutoDelete() {}
void init(T** iVar, T* iSetTo = 0)
{
pVar = iVar;
if (iSetTo)
*pVar = iSetTo;
}
~AutoDelete()
{
safe_delete(*pVar);
}
private:
T** pVar; // Pointer to the variable being managed
};
class VersionRange
{
public:
VersionRange(int32 in_min_version, int32 in_max_version)
{
min_version = in_min_version;
max_version = in_max_version;
}
int32 GetMinVersion() { return min_version; }
int32 GetMaxVersion() { return max_version; }
private:
int32 min_version; // Minimum supported version
int32 max_version; // Maximum supported version
};
// Utility function to check if IP address is private
static bool IsPrivateAddress(uint32_t ip)
{
uint8_t b1, b2;
b1 = (uint8_t)(ip >> 24);
b2 = (uint8_t)((ip >> 16) & 0x0ff);
// 10.x.y.z
if (b1 == 10)
return true;
// 172.16.0.0 - 172.31.255.255
if ((b1 == 172) && (b2 >= 16) && (b2 <= 31))
return true;
// 192.168.0.0 - 192.168.255.255
if ((b1 == 192) && (b2 == 168))
return true;
return false;
}
// Implementation
// Outputs current timestamp to cout with optional millisecond precision
void CoutTimestamp(bool ms)
{
time_t rawtime;
struct tm* gmt_t;
time(&rawtime);
gmt_t = gmtime(&rawtime);
struct timeval read_time;
gettimeofday(&read_time, 0);
cout << (gmt_t->tm_year + 1900) << "/" << setw(2) << setfill('0') << (gmt_t->tm_mon + 1) << "/" << setw(2) << setfill('0') << gmt_t->tm_mday << " " << setw(2) << setfill('0') << gmt_t->tm_hour << ":" << setw(2) << setfill('0') << gmt_t->tm_min << ":" << setw(2) << setfill('0') << gmt_t->tm_sec;
if (ms)
cout << "." << setw(3) << setfill('0') << (read_time.tv_usec / 1000);
cout << " GMT";
}
// Loads a 32-bit length prefixed string from buffer at specified position
string loadInt32String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_32BitString* eq_string)
{
buffer += *pos;
int32 size = *(int32*)buffer;
if((size + *pos + sizeof(int16)) > buffer_size) {
cout << "Error in loadInt32String: Corrupt packet.\n";
return string("");
}
buffer += sizeof(int32);
string ret((char*)buffer, 0, size);
if(eq_string) {
eq_string->size = size;
eq_string->data = ret;
}
*pos += (size + sizeof(int32));
return ret;
}
// Loads a 16-bit length prefixed string from buffer at specified position
string loadInt16String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_16BitString* eq_string)
{
buffer += *pos;
int16 size = *(int16*)buffer;
if((size + *pos + sizeof(int16)) > buffer_size) {
cout << "Error in loadInt16String: Corrupt packet.\n";
return string("");
}
buffer += sizeof(int16);
string ret((char*)buffer, 0, size);
if(eq_string) {
eq_string->size = size;
eq_string->data = ret;
}
*pos += (size + sizeof(int16));
return ret;
}
// Loads an 8-bit length prefixed string from buffer at specified position
string loadInt8String(uchar* buffer, int16 buffer_size, int16* pos, EQ2_8BitString* eq_string)
{
buffer += *pos;
int8 size = *(int8*)buffer;
if((size + *pos + sizeof(int16)) > buffer_size) {
cout << "Error in loadInt8String: Corrupt packet.\n";
return string("");
}
buffer += sizeof(int8);
string ret((char*)buffer, 0, size);
if(eq_string) {
eq_string->size = size;
eq_string->data = ret;
}
*pos += (size + sizeof(int8));
return ret;
}
// Stores a string with 32-bit length prefix into buffer
sint16 storeInt32String(uchar* buffer, int16 buffer_size, string in_str)
{
sint16 string_size = in_str.length();
if((string_size + sizeof(int32)) > buffer_size)
return -1;
memcpy(buffer, &string_size, sizeof(int32));
buffer += sizeof(int32);
memcpy(buffer, in_str.c_str(), string_size);
buffer += string_size;
return (buffer_size - (string_size + sizeof(int32)));
}
// Stores a string with 16-bit length prefix into buffer
sint16 storeInt16String(uchar* buffer, int16 buffer_size, string in_str)
{
sint16 string_size = in_str.length();
if((string_size + sizeof(int16)) > buffer_size)
return -1;
memcpy(buffer, &string_size, sizeof(int16));
buffer += sizeof(int16);
memcpy(buffer, in_str.c_str(), string_size);
buffer += string_size;
return (buffer_size - (string_size + sizeof(int16)));
}
// Stores a string with 8-bit length prefix into buffer
sint16 storeInt8String(uchar* buffer, int16 buffer_size, string in_str)
{
sint16 string_size = in_str.length();
if((string_size + sizeof(int8)) > buffer_size)
return -1;
memcpy(buffer, &string_size, sizeof(int8));
buffer += sizeof(int8);
memcpy(buffer, in_str.c_str(), string_size);
buffer += string_size;
return (buffer_size - (string_size + sizeof(int8)));
}
// Returns file size for given file pointer
sint32 filesize(FILE* fp)
{
struct stat file_stat;
fstat(fileno(fp), &file_stat);
return (sint32) file_stat.st_size;
}
// Resolves hostname to IP address, returns 0 on failure
int32 ResolveIP(const char* hostname, char* errbuf)
{
if (errbuf)
errbuf[0] = 0;
if (hostname == 0) {
if (errbuf)
snprintf(errbuf, ERRBUF_SIZE, "ResolveIP(): hostname == 0");
return 0;
}
struct sockaddr_in server_sin;
struct hostent *phostent = nullptr;
server_sin.sin_family = AF_INET;
if ((phostent = gethostbyname(hostname)) == nullptr) {
if (errbuf)
snprintf(errbuf, ERRBUF_SIZE, "Unable to get the host name. Error: %s", strerror(errno));
return 0;
}
memcpy((char*)&(server_sin.sin_addr), phostent->h_addr, phostent->h_length);
return server_sin.sin_addr.s_addr;
}
// Converts integer to string representation
const char* itoa(int value)
{
static char temp[_ITOA_BUFLEN];
memset(temp, 0, _ITOA_BUFLEN);
snprintf(temp, _ITOA_BUFLEN, "%d", value);
return temp;
}
// Converts integer to string representation with specified base
char* itoa(int value, char *result, int base)
{
char *ptr1, *ptr2;
char c;
int tmp_value;
// Need a valid base
if (base < 2 || base > 36) {
*result = '\0';
return result;
}
ptr1 = ptr2 = result;
do {
tmp_value = value;
value /= base;
*ptr1++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)];
}
while (value > 0);
// Apply a negative sign if needed
if (tmp_value < 0)
*ptr1++ = '-';
*ptr1-- = '\0';
while (ptr2 < ptr1) {
c = *ptr1;
*ptr1-- = *ptr2;
*ptr2++ = c;
}
return result;
}
// Generate a random integer in the range low-high (inclusive)
int MakeRandomInt(int low, int high)
{
return (int)MakeRandomFloat((double)low, (double)high + 0.999);
}
// Converts hexadecimal string to 32-bit integer
int32 hextoi(char* num)
{
int len = strlen(num);
if (len < 3)
return 0;
if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X'))
return 0;
int32 ret = 0;
int mul = 1;
for (int i = len - 1; i >= 2; i--) {
if (num[i] >= 'A' && num[i] <= 'F')
ret += ((num[i] - 'A') + 10) * mul;
else if (num[i] >= 'a' && num[i] <= 'f')
ret += ((num[i] - 'a') + 10) * mul;
else if (num[i] >= '0' && num[i] <= '9')
ret += (num[i] - '0') * mul;
else
return 0;
mul *= 16;
}
return ret;
}
// Converts hexadecimal string to 64-bit integer
int64 hextoi64(char* num)
{
int len = strlen(num);
if (len < 3)
return 0;
if (num[0] != '0' || (num[1] != 'x' && num[1] != 'X'))
return 0;
int64 ret = 0;
int mul = 1;
for (int i = len - 1; i >= 2; i--) {
if (num[i] >= 'A' && num[i] <= 'F')
ret += ((num[i] - 'A') + 10) * mul;
else if (num[i] >= 'a' && num[i] <= 'f')
ret += ((num[i] - 'a') + 10) * mul;
else if (num[i] >= '0' && num[i] <= '9')
ret += (num[i] - '0') * mul;
else
return 0;
mul *= 16;
}
return ret;
}
// Generate a random float between low and high values with thread-safe random generator
float MakeRandomFloat(float low, float high)
{
// Handle edge case where range is zero or inverted
float diff = high - low;
if(!diff) return low;
if (low == high) return low;
if (low > high) std::swap(low, high);
// Use a thread-local random generator for thread safety
thread_local std::mt19937 generator(std::random_device{}()); // Seed once per thread
std::uniform_real_distribution<float> distribution(low, high);
return distribution(generator);
}
// Generates EQ2 color value from RGB float components
int32 GenerateEQ2Color(float* r, float* g, float* b)
{
int8 rgb[4] = {0};
rgb[0] = (int8)((*r) * 255);
rgb[1] = (int8)((*b) * 255);
rgb[2] = (int8)((*g) * 255);
int32 color = 0;
memcpy(&color, rgb, sizeof(int32));
return color;
}
// Generates EQ2 color value from RGB float array
int32 GenerateEQ2Color(float* rgb[3])
{
return GenerateEQ2Color(rgb[0], rgb[1], rgb[2]);
}
// Converts float input to 8-bit integer value
int8 MakeInt8(float* input)
{
float input2 = *input;
if(input2 < 0)
input2 *= -1;
return (int8)(input2 * 255);
}
// Splits string by delimiter and returns vector of substrings
vector<string>* SplitString(string str, char delim)
{
vector<string>* results = new vector<string>;
int32 pos;
while((pos = str.find_first_of(delim)) != str.npos) {
if(pos > 0) {
results->push_back(str.substr(0, pos));
}
if(str.length() > pos)
str = str.substr(pos + 1);
else
break;
}
if(str.length() > 0)
results->push_back(str);
return results;
}
// Unpacks compressed data with optional reversal
bool Unpack(uchar* data, uchar* dst, int16 dstLen, int16 version, bool reverse)
{
int32 srcLen = 0;
memcpy(&srcLen, data, sizeof(int32));
return Unpack(srcLen, data + 4, dst, dstLen, version, reverse);
}
// Unpacks compressed data from source buffer to destination buffer
bool Unpack(int32 srcLen, uchar* data, uchar* dst, int16 dstLen, int16 version, bool reverse)
{
if(reverse)
Reverse(data, srcLen);
int16 pos = 0;
int16 real_pos = 0;
while(srcLen && pos < dstLen) {
if(srcLen >= 0 && !srcLen--)
return false;
int8 code = data[real_pos++];
if(code >= 128) {
for(int8 index = 0; index < 7; index++) {
if(code & 1) {
if(pos >= dstLen)
return false;
if(srcLen >= 0 && !srcLen--)
return false;
dst[pos++] = data[real_pos++];
} else {
if(pos < dstLen) dst[pos++] = 0;
}
code >>= 1;
}
} else {
if(pos + code > dstLen)
return false;
memset(dst + pos, 0, code);
pos += code;
}
}
return srcLen <= 0;
}
// Packs source data into compressed format
int32 Pack(uchar* data, uchar* src, int16 srcLen, int16 dstLen, int16 version, bool reverse)
{
int16 real_pos = 4;
int32 pos = 0;
int32 code = 0;
int codePos = 0;
int codeLen = 0;
int8 zeroLen = 0;
memset(data, 0, dstLen);
if (version > 1 && version <= 374)
reverse = false;
while(pos < srcLen) {
if(src[pos] || codeLen) {
if(!codeLen) {
if (zeroLen) {
data[real_pos++] = zeroLen;
zeroLen = 0;
}
codePos = real_pos;
code = 0;
data[real_pos++] = 0;
}
if(src[pos]) {
data[real_pos++] = src[pos];
code |= 0x80;
}
code >>= 1;
codeLen++;
if(codeLen == 7) {
data[codePos] = int8(0x80 | code);
codeLen = 0;
}
} else {
if(zeroLen == 0x7F) {
data[real_pos++] = zeroLen;
zeroLen = 0;
}
zeroLen++;
}
pos++;
}
if(codeLen) {
code >>= (7 - codeLen);
data[codePos] = int8(0x80 | code);
} else if(zeroLen) {
data[real_pos++] = zeroLen;
}
if(reverse)
Reverse(data + 4, real_pos - 4);
int32 dataLen = real_pos - 4;
memcpy(&data[0], &dataLen, sizeof(int32));
return dataLen + 4;
}
// Reverses byte order in packed data segments
void Reverse(uchar* input, int32 srcLen)
{
int16 real_pos = 0;
int16 orig_pos = 0;
int8 reverse_count = 0;
while(srcLen > 0 && srcLen < 0xFFFFFFFF) { // XXX it was >=0 before. but i think it was a bug
int8 code = input[real_pos++];
srcLen--;
if(code >= 128) {
for(int8 index = 0; index < 7; index++) {
if(code & 1) {
if(srcLen >= 0 && !srcLen--)
return;
real_pos++;
reverse_count++;
}
code >>= 1;
}
}
if(reverse_count > 0) {
int8 tmp_data[8] = {0};
for(int8 i = 0; i < reverse_count; i++) {
tmp_data[i] = input[orig_pos + reverse_count - i];
}
memcpy(input + orig_pos + 1, tmp_data, reverse_count);
reverse_count = 0;
}
orig_pos = real_pos;
}
}
// Decodes movement data using XOR operation
void MovementDecode(uchar* dst, uchar* newval, uchar* orig, int16 len)
{
int16 pos = len;
while(pos--)
dst[pos] = newval[pos] ^ orig[pos];
}
// Decodes data using XOR operation and updates source
void Decode(uchar* dst, uchar* src, int16 len)
{
int16 pos = len;
while(pos--)
dst[pos] ^= src[pos];
memcpy(src, dst, len);
}
// Encodes data using XOR operation
void Encode(uchar* dst, uchar* src, int16 len)
{
uchar* data = new uchar[len];
int16 pos = len;
while(pos--)
data[pos] = int8(src[pos] ^ dst[pos]);
memcpy(src, dst, len);
memcpy(dst, data, len);
safe_delete_array(data);
}
// Transforms 16-bit integer to float using bit shifting
float TransformToFloat(sint16 data, int8 bits)
{
return (float)(data / (float)(1 << bits));
}
// Transforms float to 16-bit integer using bit shifting
sint16 TransformFromFloat(float data, int8 bits)
{
return (sint16)(data * (1 << bits));
}
// Sets EQ2 color structure from long data value
void SetColor(EQ2_Color* color, long data)
{
memcpy(color, &data, sizeof(EQ2_Color));
}
// Converts string to uppercase
string ToUpper(string input)
{
string ret = input;
transform(input.begin(), input.end(), ret.begin(), ::toupper);
return ret;
}
// Converts string to lowercase
string ToLower(string input)
{
string ret = input;
transform(input.begin(), input.end(), ret.begin(), ::tolower);
return ret;
}
// Parses string to integer value, returns 0xFFFFFFFF on error
int32 ParseIntValue(string input)
{
int32 ret = 0xFFFFFFFF;
try {
if(input.length() > 0) {
ret = atoul(input.c_str());
}
}
catch(...) {}
return ret;
}
// Parses string to 64-bit integer value
int64 ParseLongLongValue(string input)
{
int64 ret = 0xFFFFFFFFFFFFFFFF;
try {
if(input.length() > 0) {
ret = strtoull(input.c_str(), 0, 10);
}
}
catch(...) {}
return ret;
}
// Translates broker request string into key-value map
std::map<string, string> TranslateBrokerRequest(string request)
{
std::map<string, string> ret;
string key;
string value;
int32 start_pos = 0;
int32 end_pos = 0;
int32 pos = request.find("=");
bool str_val = false;
while(pos < 0xFFFFFFFF) {
str_val = false;
key = request.substr(start_pos, pos - start_pos);
if(request.find("|", pos) == pos + 1) {
pos++;
end_pos = request.find("|", pos + 1);
str_val = true;
}
else
end_pos = request.find(" ", pos);
if(end_pos < 0xFFFFFFFF) {
value = request.substr(pos + 1, end_pos - pos - 1);
start_pos = end_pos + 1;
if(str_val) {
start_pos++;
ret[key] = ToLower(value);
}
else
ret[key] = value;
pos = request.find("=", start_pos);
}
else {
value = request.substr(pos + 1);
if(str_val) {
start_pos++;
ret[key] = ToLower(value);
}
else
ret[key] = value;
break;
}
}
return ret;
}
// Checks size needed for overload encoding
int8 CheckOverLoadSize(int32 val)
{
int8 ret = 1;
if(val >= 0xFFFF) // int32
ret = sizeof(int16) + sizeof(int32);
else if(val >= 0xFF)
ret = sizeof(int8) + sizeof(int16);
return ret;
}
// Encodes value using overload format
int8 DoOverLoad(int32 val, uchar* data)
{
int8 ret = 1;
if(val >= 0xFFFF) { // int32
memset(data, 0xFF, sizeof(int16));
memcpy(data + sizeof(int16), &val, sizeof(int32));
ret = sizeof(int16) + sizeof(int32);
}
else if(val >= 0xFF) { // int16
memset(data, 0xFF, sizeof(int8));
memcpy(data + sizeof(int8), &val, sizeof(int16));
ret = sizeof(int8) + sizeof(int16);
}
else
memcpy(data, &val, sizeof(int8));
return ret;
}
// Counts words in text string, treating contiguous spaces as one space
int32 CountWordsInString(const char* text)
{
int32 words = 0;
if (text && strlen(text) > 0) {
bool on_word = false;
for (int32 i = 0; i < strlen(text); i++) {
char letter = text[i];
if (on_word && !((letter >= 48 && letter <= 57) || (letter >= 65 && letter <= 90) || (letter >= 97 && letter <= 122)))
on_word = false;
else if (!on_word && ((letter >= 48 && letter <= 57) || (letter >= 65 && letter <= 90) || (letter >= 97 && letter <= 122))) {
on_word = true;
words++;
}
}
}
return words;
}
// Checks if string contains only numeric characters
bool IsNumber(const char *num)
{
size_t len, i;
if (!num)
return false;
len = strlen(num);
if (len == 0)
return false;
for (i = 0; i < len; i++) {
if (!isdigit(num[i]))
return false;
}
return true;
}
// Prints separator contents for debugging
void PrintSep(Seperator *sep, const char *name)
{
int32 i = 0;
LogWrite(MISC__DEBUG, 0, "Misc", "Printing sep %s", name ? name : "No Name");
if (!sep)
LogWrite(MISC__DEBUG, 0, "Misc", "\tSep is null");
else {
while (sep->arg[i] && strlen(sep->arg[i]) > 0) {
LogWrite(MISC__DEBUG, 0, "Misc", "\t%i => %s", i, sep->arg[i]);
i++;
}
}
}
#define INI_IGNORE(c) (c == '\n' || c == '\r' || c == '#')
// Seeks to specified section in INI file
static bool INIGoToSection(FILE *f, const char *section)
{
size_t size = strlen(section) + 3;
char line[256], *buf, *tmp;
bool found = false;
if ((buf = (char *)malloc(size)) == nullptr) {
fprintf(stderr, "%s: %u: Unable to allocate %zu bytes\n", __FUNCTION__, __LINE__, size);
return false;
}
sprintf(buf, "[%s]", section);
while (fgets(line, sizeof(line), f) != nullptr) {
if (INI_IGNORE(line[0]))
continue;
if (line[0] == '[') {
if ((tmp = strstr(line, "\n")) != nullptr)
*tmp = '\0';
if ((tmp = strstr(line, "\r")) != nullptr)
*tmp = '\0';
if (strcasecmp(buf, line) == 0) {
found = true;
break;
}
}
}
free(buf);
return found;
}
// Finds property value in INI file section
static char* INIFindValue(FILE *f, const char *section, const char *property)
{
char line[256], *key, *val;
if (section != nullptr && !INIGoToSection(f, section))
return nullptr;
while (fgets(line, sizeof(line), f) != nullptr) {
if (INI_IGNORE(line[0]))
continue;
if (section != nullptr && line[0] == '[')
return nullptr;
if ((key = strtok(line, "=")) == nullptr)
continue;
if (strcasecmp(key, property) == 0) {
val = strtok(nullptr, "\n\r");
if (val == nullptr)
return nullptr;
return strdup(val);
}
}
return nullptr;
}
// Reads integer value from INI file
bool INIReadInt(FILE *f, const char *section, const char *property, int *out)
{
char *value;
rewind(f);
if ((value = INIFindValue(f, section, property)) == nullptr)
return false;
if (!IsNumber(value)) {
free(value);
return false;
}
*out = atoi(value);
free(value);
return true;
}
// Reads boolean value from INI file
bool INIReadBool(FILE *f, const char *section, const char *property, bool *out)
{
char *value;
rewind(f);
if ((value = INIFindValue(f, section, property)) == nullptr)
return false;
*out = (strcasecmp(value, "1") == 0 || strcasecmp(value, "true") == 0 || strcasecmp(value, "on") == 0 || strcasecmp(value, "yes") == 0);
free(value);
return true;
}
// Converts device string to display name
string GetDeviceName(string device)
{
if (device == "chemistry_table")
device = "Chemistry Table";
else if (device == "work_desk")
device = "Engraved Desk";
else if (device == "forge")
device = "Forge";
else if (device == "stove and keg")
device = "Stove & Keg";
else if (device == "sewing_table")
device = "Sewing Table & Mannequin";
else if (device == "woodworking_table")
device = "Woodworking Table";
else if (device == "work_bench")
device = "Work Bench";
else if (device == "crafting_intro_anvil")
device = "Mender's Anvil";
return device;
}
// Gets numeric device ID from device name
int32 GetDeviceID(string device)
{
if (device == "Chemistry Table")
return 3;
else if (device == "Engraved Desk")
return 4;
else if (device == "Forge")
return 2;
else if (device == "Stove & Keg")
return 7;
else if (device == "Sewing Table & Mannequin")
return 1;
else if (device == "Woodworking Table")
return 6;
else if (device == "Work Bench")
return 5;
else if (device == "Mender's Anvil")
return 0xFFFFFFFF;
return 0;
}
// Gets item packet type for specified client version
int16 GetItemPacketType(int32 version)
{
int16 item_version;
if (version >= 64707)
item_version = 0x5CFE;
else if (version >= 63119)
item_version = 0x56FE;
else if (version >= 60024)
item_version = 0x51FE;
else if (version >= 57107)
item_version = 0x4CFE;
else if (version >= 57048)
item_version = 0x48FE;
else if (version >= 1199)
item_version = 0x44FE;
else if (version >= 1195)
item_version = 0x40FE;
else if (version >= 1193)
item_version = 0x3FFE;
else if (version >= 1190)
item_version = 0x3EFE;
else if (version >= 1188)
item_version = 0x3DFE;
else if (version >= 1096)
item_version = 0x35FE;
else if (version >= 1027)
item_version = 0x31FE;
else if (version >= 1008)
item_version = 0x2CFE;
else if (version >= 927)
item_version = 0x23FE;
else if (version >= 893)
item_version = 0x22FE;
else if (version >= 860)
item_version = 0x20FE;
else if (version > 546)
item_version = 0x1CFE;
else
item_version = 0;
return item_version;
}
#ifndef PATCHER
// Gets opcode version range for specified client version
int16 GetOpcodeVersion(int16 version)
{
int16 ret = version;
int16 version1 = 0;
int16 version2 = 0;
std::map<int16, int16>::iterator itr;
for (itr = EQOpcodeVersions.begin(); itr != EQOpcodeVersions.end(); itr++) {
version1 = itr->first;
version2 = itr->second;
if (version >= version1 && version <= version2) {
ret = version1;
break;
}
}
return ret;
}
#endif
// Cross-platform sleep function
void SleepMS(int32 milliseconds)
{
usleep(milliseconds * 1000);
}
// Safe string copy with size limit
size_t strlcpy(char *dst, const char *src, size_t size)
{
char *d = dst;
const char *s = src;
size_t n = size;
if (n != 0 && --n != 0) {
do {
if ((*d++ = *s++) == 0)
break;
} while (--n != 0);
}
if (n == 0) {
if (size != 0)
*d = '\0';
while (*s++)
;
}
return(s - src - 1);
}
// Converts 16-bit float to 32-bit float (IEEE-754 format)
float short_to_float(const ushort x)
{
const uint32 e = (x & 0x7C00) >> 10; // exponent
const uint32 m = (x & 0x03FF) << 13; // mantissa
const uint32 v = as_uint((float)m) >> 23; // evil log2 bit hack to count leading zeros in denormalized format
return as_float((x & 0x8000) << 16 | (e != 0) * ((e + 112) << 23 | m) | ((e == 0) & (m != 0)) * ((v - 37) << 23 | ((m << (150 - v)) & 0x007FE000))); // sign : normalized : denormalized
}
// Converts 32-bit float to 16-bit representation
uint32 float_to_int(const float x)
{
const uint32 b = as_uint(x) + 0x00001000; // round-to-nearest-even: add last bit after truncated mantissa
const uint32 e = (b & 0x7F800000) >> 23; // exponent
const uint32 m = b & 0x007FFFFF; // mantissa; in line below: 0x007FF000 = 0x00800000-0x00001000 = decimal indicator flag - initial rounding
return (b & 0x80000000) >> 16 | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13) | ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1) | (e > 143) * 0x7FFF; // sign : normalized : denormalized : saturate
}
// Type punning: interprets float as uint32
uint32 as_uint(const float x)
{
return *(uint32*)&x;
}
// Type punning: interprets uint32 as float
float as_float(const uint32 x)
{
return *(float*)&x;
}
// Gets current timestamp in milliseconds using steady clock
int64 getCurrentTimestamp()
{
auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
return duration.count();
}
// Converts timestamp duration to days, hours, minutes, seconds
std::tuple<int64, int64, int64, int64> convertTimestampDuration(int64 total_milliseconds)
{
std::chrono::milliseconds duration(total_milliseconds);
// Convert to days, hours, minutes, and seconds
auto hours = std::chrono::duration_cast<std::chrono::hours>(duration);
duration -= hours;
auto days = hours / 24;
hours -= days * 24;
auto minutes = std::chrono::duration_cast<std::chrono::minutes>(duration);
duration -= minutes;
auto seconds = std::chrono::duration_cast<std::chrono::seconds>(duration);
// Return the result as a tuple
return std::make_tuple(days.count(), hours.count(), minutes.count(), seconds.count());
}