XPlor/libs/core/utils.h

516 lines
20 KiB
C
Raw Normal View History

2025-01-09 17:54:44 -05:00
#ifndef UTILS_H
#define UTILS_H
#include "enums.h"
2025-07-10 00:10:49 -04:00
#include "QtZlib/zlib.h"
#include "qdir.h"
#include "qicon.h"
2025-09-05 18:35:17 -04:00
#include "xasset.h"
#include "xassettype.h"
2025-01-09 17:54:44 -05:00
2025-07-10 00:10:49 -04:00
#include <QMetaEnum>
2025-06-04 22:32:34 -04:00
#include <QPainter>
#include <QCryptographicHash>
2025-01-09 17:54:44 -05:00
2025-07-10 00:10:49 -04:00
class Utils : public QObject {
Q_OBJECT
2025-09-05 18:35:17 -04:00
public:
2025-04-04 20:34:24 -04:00
static bool ExportData(const QString aFileName, const QByteArray aData) {
QDir workingDir = QDir::currentPath();
workingDir.mkdir("exports");
QFile testFile("exports/" + aFileName);
if(!testFile.open(QIODevice::WriteOnly)) {
return false;
}
testFile.write(aData);
testFile.close();
return true;
}
2025-06-04 22:32:34 -04:00
static quint8 ReverseBits(quint8 b) {
b = (b & 0xF0) >> 4 | (b & 0x0F) << 4;
b = (b & 0xCC) >> 2 | (b & 0x33) << 2;
b = (b & 0xAA) >> 1 | (b & 0x55) << 1;
return b;
}
2025-09-05 18:35:17 -04:00
static QIcon CreateAssetIcon(XAssetType aAssetType, QColor color = QColor()) {
const QString assetTypeStr = XAsset::XAssetTypeToString(aAssetType);
2025-07-10 00:10:49 -04:00
2025-09-05 18:35:17 -04:00
QString assetAbbr;
for (int i = 0; i < assetTypeStr.length(); i++)
{
if (assetTypeStr[i].isUpper())
{
assetAbbr += assetTypeStr[i];
}
2025-07-10 00:10:49 -04:00
}
2025-09-05 18:35:17 -04:00
return CreateAssetIcon(assetAbbr, color);
// QString name;
// const QStringList parts = assetTypeStr.split('_').mid(1);
// foreach (const QString part, parts) {
// name += part[0];
// }
// if (parts.size() == 1) {
// name += parts.first()[1];
// }
// return CreateAssetIcon(name, color);
2025-07-10 00:10:49 -04:00
}
2025-06-04 22:32:34 -04:00
static QIcon CreateAssetIcon(const QString& name, QColor color = QColor()) {
constexpr int iconSize = 32;
constexpr int padding = 4;
QImage result(iconSize, iconSize, QImage::Format_ARGB32);
result.fill(Qt::transparent);
if (!color.isValid()) {
color = Utils::StringToColor(name);
}
QPainter painter(&result);
painter.setRenderHint(QPainter::Antialiasing, true);
painter.setRenderHint(QPainter::TextAntialiasing, true);
painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
// Draw background
QBrush brush(color);
brush.setStyle(Qt::SolidPattern);
painter.setBrush(color);
painter.setPen(Qt::NoPen);
painter.drawRoundedRect(0, 0, iconSize, iconSize, 4, 4);
// Set base font
QFont font("Heavitas");
font.setPixelSize(iconSize); // Start large
painter.setFont(font);
// Adjust font size to fit text width (only reduce, not increase)
QFontMetrics fm(font);
int textWidth = fm.horizontalAdvance(name);
while (textWidth > iconSize - 2 * padding && font.pixelSize() > 1) {
font.setPixelSize(font.pixelSize() - 1);
painter.setFont(font);
fm = QFontMetrics(font);
textWidth = fm.horizontalAdvance(name);
}
// Calculate vertical scaling factor
qreal scaleY = 1.2 * iconSize / fm.height();
// Apply transform: scale vertically, center align
painter.save();
painter.translate(iconSize / 2.0, iconSize / 2.0);
painter.scale(1.0, scaleY);
painter.translate(-iconSize / 2.0, -iconSize / 2.0);
QRect textRect(0, 0, iconSize, iconSize);
// Draw stroke
painter.setPen(Qt::black);
for (int dx = 0; dx <= 1; ++dx) {
for (int dy = 0; dy <= 1; ++dy) {
if (dx || dy)
painter.drawText(textRect.translated(dx, dy), Qt::AlignCenter, name);
}
}
// Draw main text
painter.setPen(Qt::white);
painter.drawText(textRect, Qt::AlignCenter, name);
painter.restore();
// Debug output
QDir().mkdir(QDir().absoluteFilePath(".") + "/icons/");
result.save(QDir().absoluteFilePath(".") + "/icons/" + name + ".png");
return QIcon(QPixmap::fromImage(result));
}
static QIcon CreateGameIcon(const int gameNum, QColor color = QColor()) {
constexpr int size = 32;
constexpr int padding = 4; // pixels of padding on all sides
const int contentSize = size - 2 * padding;
QImage result(size, size, QImage::Format_ARGB32);
result.fill(Qt::transparent);
if (!color.isValid()) {
color = Utils::StringToColor(QString("COD%1").arg(gameNum));
}
QPainter painter(&result);
painter.setRenderHint(QPainter::Antialiasing, false);
painter.setRenderHint(QPainter::TextAntialiasing, false);
painter.setRenderHint(QPainter::SmoothPixmapTransform, false);
// Draw background
painter.setBrush(color);
painter.setPen(Qt::NoPen);
painter.drawRoundedRect(0, 0, size, size, 4, 4);
// === Font Setup ===
QFont codFont("Heavitas");
codFont.setPixelSize(contentSize * 0.40);
QFont numFont("Heavitas");
numFont.setPixelSize(contentSize);
// === Layout Areas (inside padding) ===
const QRect contentRect(padding, padding, contentSize, contentSize);
const int col1 = contentRect.left();
const int col2 = contentRect.left() + contentRect.width() / 3;
const int thirdH = contentRect.height() / 3;
const QRect codRects[] = {
QRect(col1, 2 + contentRect.top(), contentRect.width() / 3, thirdH),
QRect(col1, contentRect.top() + thirdH, contentRect.width() / 3, thirdH),
QRect(col1, -2 + contentRect.top() + 2 * thirdH, contentRect.width() / 3, thirdH),
};
const QRect numRect(col2, contentRect.top(), contentRect.width() * 2 / 3, contentRect.height());
const QString codLetters[] = { "C", "O", "D" };
const QString numText = QString::number(gameNum);
// === Stroke pass ===
for (int dx = -1; dx <= 2; ++dx) {
for (int dy = -1; dy <= 2; ++dy) {
if (dx == 0 && dy == 0) continue;
painter.setPen(Qt::black);
painter.setFont(codFont);
for (int i = 0; i < 3; ++i)
painter.drawText(codRects[i].translated(dx, dy), Qt::AlignCenter, codLetters[i]);
painter.setFont(numFont);
painter.drawText(numRect.translated(dx, dy), Qt::AlignCenter, numText);
}
}
// === Fill pass ===
painter.setPen(Qt::white);
painter.setFont(codFont);
for (int i = 0; i < 3; ++i)
painter.drawText(codRects[i], Qt::AlignCenter, codLetters[i]);
painter.setFont(numFont);
painter.drawText(numRect, Qt::AlignCenter, numText);
// Save & return icon
QDir().mkdir(QDir().absoluteFilePath(".") + "/icons/");
result.save(QDir().absoluteFilePath(".") + QString("/icons/COD%1.png").arg(gameNum));
return QIcon(QPixmap::fromImage(result));
}
static QColor StringToColor(const QString& str) {
// 1. Hash the string using Qt's built-in hash (MD5, SHA1, or any)
QByteArray hash = QCryptographicHash::hash(str.toUtf8(), QCryptographicHash::Md5);
// 2. Use first 3 bytes of hash for RGB
// This guarantees same string = same color every time
int r = static_cast<unsigned char>(hash[0]);
int g = static_cast<unsigned char>(hash[1]);
int b = static_cast<unsigned char>(hash[2]);
// 3. Optionally adjust brightness or saturation to avoid too dark/light colors
QColor color(r, g, b);
if (color.value() < 128) { // brighten if too dark
color = color.lighter(150);
}
return color;
}
static bool ReadUntilString(XDataStream* stream, const QString& targetString) {
2025-02-14 16:06:27 -05:00
if (!stream || targetString.isEmpty()) {
return false; // Invalid input
}
QByteArray buffer;
QByteArray targetBytes = targetString.toUtf8(); // Handle multibyte characters
const int targetLength = targetBytes.size();
qDebug() << targetBytes << targetLength;
// Read as unsigned bytes to handle all possible values (0-255)
unsigned char byte;
while (!stream->atEnd()) {
// Read one byte at a time
*stream >> byte;
buffer.append(static_cast<char>(byte)); // Append as char for QByteArray
// Keep buffer size limited to the target length
if (buffer.size() > targetLength) {
buffer.remove(0, 1);
}
// Check if the buffer matches the target string in raw bytes
if (buffer == targetBytes) {
// Backup to the start of the matched string
stream->device()->seek(stream->device()->pos() - targetLength);
return true;
}
}
// Target string not found
return false;
}
static bool ReadUntilHex(XDataStream* stream, const QString& hexString) {
2025-02-14 16:06:27 -05:00
if (!stream || hexString.isEmpty() || hexString.size() % 2 != 0) {
return false; // Invalid input
}
// Convert hex string to byte array
QByteArray targetBytes = QByteArray::fromHex(hexString.toUtf8());
const int targetLength = targetBytes.size();
QByteArray buffer;
unsigned char byte;
while (!stream->atEnd()) {
// Read one byte at a time
*stream >> byte;
buffer.append(static_cast<char>(byte)); // Append as char for QByteArray
// Keep buffer size limited to the target length
if (buffer.size() > targetLength) {
buffer.remove(0, 1);
}
// Check if the buffer matches the target byte sequence
if (buffer == targetBytes) {
// Backup to the start of the matched sequence
stream->device()->seek(stream->device()->pos() - targetLength);
return true;
}
}
// Target sequence not found
return false;
}
2025-01-09 17:54:44 -05:00
/*
LumpTypeToString()
Convert BSP lump type to desc string
*/
static QString LumpTypeToString(LUMP_TYPE aLumpType) {
switch (aLumpType) {
case LUMP_MATERIALS: return "LUMP_MATERIALS";
case LUMP_LIGHTBYTES: return "LUMP_LIGHTBYTES";
case LUMP_LIGHTGRIDENTRIES: return "LUMP_LIGHTGRIDENTRIES";
case LUMP_LIGHTGRIDCOLORS: return "LUMP_LIGHTGRIDCOLORS";
case LUMP_PLANES: return "LUMP_PLANES";
case LUMP_BRUSHSIDES: return "LUMP_BRUSHSIDES";
case LUMP_BRUSHSIDEEDGECOUNTS: return "LUMP_BRUSHSIDEEDGECOUNTS";
case LUMP_BRUSHEDGES: return "LUMP_BRUSHEDGES";
case LUMP_BRUSHES: return "LUMP_BRUSHES";
case LUMP_TRIANGLES: return "LUMP_TRIANGLES";
case LUMP_DRAWVERTS: return "LUMP_DRAWVERTS";
case LUMP_DRAWINDICES: return "LUMP_DRAWINDICES";
case LUMP_CULLGROUPS: return "LUMP_CULLGROUPS";
case LUMP_CULLGROUPINDICES: return "LUMP_CULLGROUPINDICES";
case LUMP_OBSOLETE_1: return "LUMP_OBSOLETE_1";
case LUMP_OBSOLETE_2: return "LUMP_OBSOLETE_2";
case LUMP_OBSOLETE_3: return "LUMP_OBSOLETE_3";
case LUMP_OBSOLETE_4: return "LUMP_OBSOLETE_4";
case LUMP_OBSOLETE_5: return "LUMP_OBSOLETE_5";
case LUMP_PORTALVERTS: return "LUMP_PORTALVERTS";
case LUMP_OBSOLETE_6: return "LUMP_OBSOLETE_6";
case LUMP_UINDS: return "LUMP_UINDS";
case LUMP_BRUSHVERTSCOUNTS: return "LUMP_BRUSHVERTSCOUNTS";
case LUMP_BRUSHVERTS: return "LUMP_BRUSHVERTS";
case LUMP_AABBTREES: return "LUMP_AABBTREES";
case LUMP_CELLS: return "LUMP_CELLS";
case LUMP_PORTALS: return "LUMP_PORTALS";
case LUMP_NODES: return "LUMP_NODES";
case LUMP_LEAFS: return "LUMP_LEAFS";
case LUMP_LEAFBRUSHES: return "LUMP_LEAFBRUSHES";
case LUMP_LEAFSURFACES: return "LUMP_LEAFSURFACES";
case LUMP_COLLISIONVERTS: return "LUMP_COLLISIONVERTS";
case LUMP_COLLISIONTRIS: return "LUMP_COLLISIONTRIS";
case LUMP_COLLISIONEDGEWALKABLE: return "LUMP_COLLISIONEDGEWALKABLE";
case LUMP_COLLISIONBORDERS: return "LUMP_COLLISIONBORDERS";
case LUMP_COLLISIONPARTITIONS: return "LUMP_COLLISIONPARTITIONS";
case LUMP_COLLISIONAABBS: return "LUMP_COLLISIONAABBS";
case LUMP_MODELS: return "LUMP_MODELS";
case LUMP_VISIBILITY: return "LUMP_VISIBILITY";
case LUMP_ENTITIES: return "LUMP_ENTITIES";
case LUMP_PATHCONNECTIONS: return "LUMP_PATHCONNECTIONS";
case LUMP_REFLECTION_PROBES: return "LUMP_REFLECTION_PROBES";
case LUMP_VERTEX_LAYER_DATA: return "LUMP_VERTEX_LAYER_DATA";
case LUMP_PRIMARY_LIGHTS: return "LUMP_PRIMARY_LIGHTS";
case LUMP_LIGHTGRIDHEADER: return "LUMP_LIGHTGRIDHEADER";
case LUMP_LIGHTGRIDROWS: return "LUMP_LIGHTGRIDROWS";
case LUMP_OBSOLETE_10: return "LUMP_OBSOLETE_10";
case LUMP_UNLAYERED_TRIANGLES: return "LUMP_UNLAYERED_TRIANGLES";
case LUMP_UNLAYERED_DRAWVERTS: return "LUMP_UNLAYERED_DRAWVERTS";
case LUMP_UNLAYERED_DRAWINDICES: return "LUMP_UNLAYERED_DRAWINDICES";
case LUMP_UNLAYERED_CULLGROUPS: return "LUMP_UNLAYERED_CULLGROUPS";
case LUMP_UNLAYERED_AABBTREES: return "LUMP_UNLAYERED_AABBTREES";
case LUMP_WATERHEADER: return "LUMP_WATERHEADER";
case LUMP_WATERCELLS: return "LUMP_WATERCELLS";
case LUMP_WATERCELLDATA: return "LUMP_WATERCELLDATA";
case LUMP_BURNABLEHEADER: return "LUMP_BURNABLEHEADER";
case LUMP_BURNABLECELLS: return "LUMP_BURNABLECELLS";
case LUMP_BURNABLECELLDATA: return "LUMP_BURNABLECELLDATA";
case LUMP_SIMPLELIGHTMAPBYTES: return "LUMP_SIMPLELIGHTMAPBYTES";
case LUMP_LODCHAINS: return "LUMP_LODCHAINS";
case LUMP_LODINFOS: return "LUMP_LODINFOS";
case LUMP_LODSURFACES: return "LUMP_LODSURFACES";
case LUMP_LIGHTREGIONS: return "LUMP_LIGHTREGIONS";
case LUMP_LIGHTREGION_HULLS: return "LUMP_LIGHTREGION_HULLS";
case LUMP_LIGHTREGION_AXES: return "LUMP_LIGHTREGION_AXES";
case LUMP_WIILIGHTGRID: return "LUMP_WIILIGHTGRID";
case LUMP_LIGHTGRID2D_LIGHTS: return "LUMP_LIGHTGRID2D_LIGHTS";
case LUMP_LIGHTGRID2D_INDICES: return "LUMP_LIGHTGRID2D_INDICES";
case LUMP_LIGHTGRID2D_POINTS: return "LUMP_LIGHTGRID2D_POINTS";
case LUMP_LIGHTGRID2D_CELLS: return "LUMP_LIGHTGRID2D_CELLS";
case LUMP_LIGHT_CORONAS: return "LUMP_LIGHT_CORONAS";
case LUMP_SHADOWMAP_VOLUMES: return "LUMP_SHADOWMAP_VOLUMES";
case LUMP_SHADOWMAP_VOLUME_PLANES: return "LUMP_SHADOWMAP_VOLUME_PLANES";
case LUMP_EXPOSURE_VOLUMES: return "LUMP_EXPOSURE_VOLUMES";
case LUMP_EXPOSURE_VOLUME_PLANES: return "LUMP_EXPOSURE_VOLUME_PLANES";
case LUMP_OCCLUDERS: return "LUMP_OCCLUDERS";
case LUMP_OUTDOORBOUNDS: return "LUMP_OUTDOORBOUNDS";
case LUMP_HERO_ONLY_LIGHTS: return "LUMP_HERO_ONLY_LIGHTS";
default: return "Unknown Lump Type";
}
return "Unknown Lump Type";
}
static QString ZLibErrorToString(int result) {
switch (result) {
case Z_ERRNO:
return "Encountered file operation error!";
case Z_STREAM_ERROR:
return "Stream was not initialized properly!";
case Z_DATA_ERROR:
return "The input data was corrupted or incomplete!";
case Z_MEM_ERROR:
return "There was not enough memory!";
case Z_BUF_ERROR:
return "There was not enough room in the output buffer!";
case Z_VERSION_ERROR:
return "Linked libraries are out of date!";
}
return "";
}
static quint32 PadInt4(quint32 size) {
return (size + 3) & ~3; // Align size to 4 bytes
}
static quint32 PaddingSize(quint32 size) {
return PadInt4(size) - size;
}
static QString CompanyEnumToStr(FF_COMPANY aCompany) {
switch (aCompany) {
case COMPANY_NONE:
return "None";
case COMPANY_INFINITY_WARD:
return "Infinity Ward";
case COMPANY_TREYARCH:
return "Treyarch";
case COMPANY_SLEDGEHAMMER:
return "Sledgehammer";
case COMPANY_NEVERSOFT:
return "Neversoft";
}
2025-02-08 19:58:54 -05:00
return "None";
}
static QString FileTypeEnumToStr(FF_FILETYPE aFileType) {
switch (aFileType) {
case FILETYPE_NONE:
return "None";
case FILETYPE_FAST_FILE:
return "Fast File";
}
2025-02-08 19:58:54 -05:00
return "None";
}
static QString SignageEnumToStr(FF_SIGNAGE aSignage) {
switch (aSignage) {
case SIGNAGE_NONE:
return "None";
case SIGNAGE_SIGNED:
return "Signed";
case SIGNAGE_UNSIGNED:
return "Unsigned";
}
2025-02-08 19:58:54 -05:00
return "None";
}
static QString MenuVAlignToStr(MENU_V_ALIGNMENT align) {
if (align == VERTICAL_ALIGN_SUBTOP) {
return "VERTICAL_ALIGN_SUBTOP";
} else if (align == VERTICAL_ALIGN_TOP) {
return "VERTICAL_ALIGN_TOP";
} else if (align == VERTICAL_ALIGN_CENTER) {
return "VERTICAL_ALIGN_CENTER";
} else if (align == VERTICAL_ALIGN_BOTTOM) {
return "VERTICAL_ALIGN_BOTTOM";
} else if (align == VERTICAL_ALIGN_FULLSCREEN) {
return "VERTICAL_ALIGN_FULLSCREEN";
} else if (align == VERTICAL_ALIGN_NOSCALE) {
return "VERTICAL_ALIGN_NOSCALE";
} else if (align == VERTICAL_ALIGN_TO480) {
return "VERTICAL_ALIGN_TO480";
} else if (align == VERTICAL_ALIGN_CENTER_SAFEAREA) {
return "VERTICAL_ALIGN_CENTER_SAFEAREA";
} else if (align == VERTICAL_ALIGN_MAX) {
return "VERTICAL_ALIGN_CENTER_SAFEAREA";
} else if (align == VERTICAL_ALIGN_DEFAULT) {
return "VERTICAL_ALIGN_SUBTOP";
}
return "VERTICAL_ALIGN_SUBTOP";
}
static QString MenuHAlignToStr(MENU_H_ALIGNMENT align) {
if (align == HORIZONTAL_ALIGN_SUBLEFT) {
return "HORIZONTAL_ALIGN_SUBLEFT";
} else if (align == HORIZONTAL_ALIGN_LEFT) {
return "HORIZONTAL_ALIGN_LEFT";
} else if (align == HORIZONTAL_ALIGN_CENTER) {
return "HORIZONTAL_ALIGN_CENTER";
} else if (align == HORIZONTAL_ALIGN_RIGHT) {
return "HORIZONTAL_ALIGN_RIGHT";
} else if (align == HORIZONTAL_ALIGN_FULLSCREEN) {
return "HORIZONTAL_ALIGN_FULLSCREEN";
} else if (align == HORIZONTAL_ALIGN_NOSCALE) {
return "HORIZONTAL_ALIGN_NOSCALE";
} else if (align == HORIZONTAL_ALIGN_TO640) {
return "HORIZONTAL_ALIGN_TO640";
} else if (align == HORIZONTAL_ALIGN_CENTER_SAFEAREA) {
return "HORIZONTAL_ALIGN_CENTER_SAFEAREA";
} else if (align == HORIZONTAL_ALIGN_MAX) {
return "HORIZONTAL_ALIGN_CENTER_SAFEAREA";
} else if (align == HORIZONTAL_ALIGN_DEFAULT) {
return "HORIZONTAL_ALIGN_SUBLEFT";
}
return "HORIZONTAL_ALIGN_SUBLEFT";
}
static QColor ColorFromNormalized(float r, float g, float b, float a) {
// Ensure values are clamped between 0 and 1
r = qBound(0.0f, r, 1.0f);
g = qBound(0.0f, g, 1.0f);
b = qBound(0.0f, b, 1.0f);
a = qBound(0.0f, a, 1.0f);
// Convert to 0-255 scale
int red = static_cast<int>(r * 255);
int green = static_cast<int>(g * 255);
int blue = static_cast<int>(b * 255);
int alpha = static_cast<int>(a * 255);
return QColor(red, green, blue, alpha);
}
2025-01-09 17:54:44 -05:00
};
#endif // UTILS_H