893 lines
27 KiB
C++
893 lines
27 KiB
C++
/*****************************************************************************
|
|
* gta5view Grand Theft Auto V Profile Viewer
|
|
* Copyright (C) 2020-2022 Syping
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*****************************************************************************/
|
|
|
|
#include "RagePhoto.h"
|
|
#include <QJsonDocument>
|
|
#include <QBuffer>
|
|
#include <QFile>
|
|
#if QT_VERSION < 0x060000
|
|
#include <QTextCodec>
|
|
#else
|
|
#include <QStringEncoder>
|
|
#include <QStringDecoder>
|
|
#endif
|
|
#ifdef RAGEPHOTO_BENCHMARK
|
|
#include <QFileInfo>
|
|
#include <chrono>
|
|
#endif
|
|
|
|
inline quint32 joaatFromSI(const char *data, size_t size)
|
|
{
|
|
quint32 val = 0xE47AB81CUL;
|
|
for (size_t i = 0; i != size; i++) {
|
|
val += data[i];
|
|
val += (val << 10);
|
|
val ^= (val >> 6);
|
|
}
|
|
val += (val << 3);
|
|
val ^= (val >> 11);
|
|
val += (val << 15);
|
|
return val;
|
|
}
|
|
|
|
RagePhoto::RagePhoto()
|
|
{
|
|
p_photoFormat = PhotoFormat::Undefined;
|
|
p_isLoaded = false;
|
|
p_inputMode = -1;
|
|
}
|
|
|
|
RagePhoto::RagePhoto(const QByteArray &data) : p_fileData(data)
|
|
{
|
|
p_photoFormat = PhotoFormat::Undefined;
|
|
p_isLoaded = false;
|
|
p_inputMode = 0;
|
|
}
|
|
|
|
RagePhoto::RagePhoto(const QString &filePath) : p_filePath(filePath)
|
|
{
|
|
p_photoFormat = PhotoFormat::Undefined;
|
|
p_isLoaded = false;
|
|
p_inputMode = 1;
|
|
}
|
|
|
|
RagePhoto::RagePhoto(QIODevice *ioDevice) : p_ioDevice(ioDevice)
|
|
{
|
|
p_photoFormat = PhotoFormat::Undefined;
|
|
p_isLoaded = false;
|
|
p_inputMode = 2;
|
|
}
|
|
|
|
bool RagePhoto::isLoaded()
|
|
{
|
|
return p_isLoaded;
|
|
}
|
|
|
|
bool RagePhoto::load()
|
|
{
|
|
if (p_inputMode == -1)
|
|
return false;
|
|
|
|
if (p_isLoaded)
|
|
clear();
|
|
|
|
if (p_inputMode == 1) {
|
|
QFile pictureFile(p_filePath);
|
|
if (pictureFile.open(QIODevice::ReadOnly)) {
|
|
p_fileData = pictureFile.readAll();
|
|
}
|
|
pictureFile.close();
|
|
}
|
|
else if (p_inputMode == 2) {
|
|
if (!p_ioDevice->isOpen()) {
|
|
if (!p_ioDevice->open(QIODevice::ReadOnly))
|
|
return false;
|
|
}
|
|
p_fileData = p_ioDevice->readAll();
|
|
}
|
|
|
|
QBuffer dataBuffer(&p_fileData);
|
|
dataBuffer.open(QIODevice::ReadOnly);
|
|
|
|
#ifdef RAGEPHOTO_BENCHMARK
|
|
auto benchmark_parse_start = std::chrono::high_resolution_clock::now();
|
|
#endif
|
|
|
|
char uInt32Buffer[4];
|
|
qint64 size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
quint32 format = charToUInt32LE(uInt32Buffer);
|
|
|
|
if (format == static_cast<quint32>(PhotoFormat::GTA5)) {
|
|
char photoHeader[256];
|
|
size = dataBuffer.read(photoHeader, 256);
|
|
if (size != 256) {
|
|
return false;
|
|
}
|
|
for (const QChar &photoChar : utf16LEToString(photoHeader, 256)) {
|
|
if (photoChar.isNull())
|
|
break;
|
|
p_photoString += photoChar;
|
|
}
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_headerSum = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_endOfFile = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_jsonOffset = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_titlOffset = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_descOffset = charToUInt32LE(uInt32Buffer);
|
|
|
|
char markerBuffer[4];
|
|
size = dataBuffer.read(markerBuffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
if (strncmp(markerBuffer, "JPEG", 4) != 0)
|
|
return false;
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_photoBuffer = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
quint32 t_photoSize = charToUInt32LE(uInt32Buffer);
|
|
|
|
char *photoData = static_cast<char*>(malloc(t_photoSize));
|
|
if (!photoData)
|
|
return false;
|
|
size = dataBuffer.read(photoData, t_photoSize);
|
|
if (size != t_photoSize) {
|
|
free(photoData);
|
|
return false;
|
|
}
|
|
p_photoData = QByteArray(photoData, t_photoSize);
|
|
free(photoData);
|
|
|
|
dataBuffer.seek(p_jsonOffset + 264);
|
|
size = dataBuffer.read(markerBuffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
if (strncmp(markerBuffer, "JSON", 4) != 0)
|
|
return false;
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_jsonBuffer = charToUInt32LE(uInt32Buffer);
|
|
|
|
char *jsonBytes = static_cast<char*>(malloc(p_jsonBuffer));
|
|
if (!jsonBytes)
|
|
return false;
|
|
size = dataBuffer.read(jsonBytes, p_jsonBuffer);
|
|
if (size != p_jsonBuffer) {
|
|
free(jsonBytes);
|
|
return false;
|
|
}
|
|
quint32 i;
|
|
for (i = 0; i != p_jsonBuffer; i++) {
|
|
if (jsonBytes[i] == '\x00')
|
|
break;
|
|
}
|
|
p_jsonData = QByteArray(jsonBytes, i);
|
|
free(jsonBytes);
|
|
QJsonDocument t_jsonDocument = QJsonDocument::fromJson(p_jsonData);
|
|
if (t_jsonDocument.isNull())
|
|
return false;
|
|
p_jsonObject = t_jsonDocument.object();
|
|
|
|
dataBuffer.seek(p_titlOffset + 264);
|
|
size = dataBuffer.read(markerBuffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
if (strncmp(markerBuffer, "TITL", 4) != 0)
|
|
return false;
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_titlBuffer = charToUInt32LE(uInt32Buffer);
|
|
|
|
char *titlBytes = static_cast<char*>(malloc(p_titlBuffer));
|
|
if (!titlBytes)
|
|
return false;
|
|
size = dataBuffer.read(titlBytes, p_titlBuffer);
|
|
if (size != p_titlBuffer){
|
|
free(titlBytes);
|
|
return false;
|
|
}
|
|
for (i = 0; i != p_titlBuffer; i++) {
|
|
if (titlBytes[i] == '\x00')
|
|
break;
|
|
}
|
|
p_titleString = QString::fromUtf8(titlBytes, i);
|
|
free(titlBytes);
|
|
|
|
dataBuffer.seek(p_descOffset + 264);
|
|
size = dataBuffer.read(markerBuffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
if (strncmp(markerBuffer, "DESC", 4) != 0)
|
|
return false;
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_descBuffer = charToUInt32LE(uInt32Buffer);
|
|
|
|
char *descBytes = static_cast<char*>(malloc(p_descBuffer));
|
|
if (!descBytes)
|
|
return false;
|
|
size = dataBuffer.read(descBytes, p_descBuffer);
|
|
if (size != p_descBuffer) {
|
|
free(descBytes);
|
|
return false;
|
|
}
|
|
for (i = 0; i != p_descBuffer; i++) {
|
|
if (descBytes[i] == '\x00')
|
|
break;
|
|
}
|
|
p_descriptionString = QString::fromUtf8(descBytes, i);
|
|
free(descBytes);
|
|
|
|
dataBuffer.seek(p_endOfFile + 260);
|
|
size = dataBuffer.read(markerBuffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
if (strncmp(markerBuffer, "JEND", 4) != 0)
|
|
return false;
|
|
|
|
#ifdef RAGEPHOTO_BENCHMARK
|
|
auto benchmark_parse_end = std::chrono::high_resolution_clock::now();
|
|
auto benchmark_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(benchmark_parse_end - benchmark_parse_start);
|
|
if (p_inputMode == 1) {
|
|
QTextStream(stdout) << QFileInfo(p_filePath).fileName() << ": " << benchmark_ns.count() << "ns" << Qt::endl;
|
|
}
|
|
else {
|
|
QTextStream(stdout) << "PGTA5" << p_jsonObject.value("uid").toInt() << ": " << benchmark_ns.count() << "ns" << Qt::endl;
|
|
}
|
|
#endif
|
|
|
|
if (p_photoFormat != PhotoFormat::G5EX)
|
|
p_photoFormat = PhotoFormat::GTA5;
|
|
|
|
p_fileData.clear();
|
|
p_isLoaded = true;
|
|
return true;
|
|
}
|
|
else if (format == static_cast<quint32>(PhotoFormat::G5EX)) {
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
format = charToUInt32LE(uInt32Buffer);
|
|
if (format == static_cast<quint32>(ExportFormat::G5E3P)) {
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
quint32 compressedSize = charToUInt32LE(uInt32Buffer);
|
|
|
|
char *compressedPhotoHeader = static_cast<char*>(malloc(compressedSize));
|
|
if (!compressedPhotoHeader)
|
|
return false;
|
|
size = dataBuffer.read(compressedPhotoHeader, compressedSize);
|
|
if (size != compressedSize) {
|
|
free(compressedPhotoHeader);
|
|
return false;
|
|
}
|
|
QByteArray t_photoHeader = QByteArray::fromRawData(compressedPhotoHeader, compressedSize);
|
|
t_photoHeader = qUncompress(t_photoHeader);
|
|
free(compressedPhotoHeader);
|
|
if (t_photoHeader.isEmpty())
|
|
return false;
|
|
p_photoString = QString::fromUtf8(t_photoHeader);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_headerSum = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_photoBuffer = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
compressedSize = charToUInt32LE(uInt32Buffer);
|
|
|
|
char *compressedPhoto = static_cast<char*>(malloc(compressedSize));
|
|
if (!compressedPhoto)
|
|
return false;
|
|
size = dataBuffer.read(compressedPhoto, compressedSize);
|
|
if (size != compressedSize) {
|
|
free(compressedPhoto);
|
|
return false;
|
|
}
|
|
QByteArray t_photoData = QByteArray::fromRawData(compressedPhoto, compressedSize);
|
|
p_photoData = qUncompress(t_photoData);
|
|
free(compressedPhoto);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_jsonOffset = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_jsonBuffer = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
compressedSize = charToUInt32LE(uInt32Buffer);
|
|
|
|
char *compressedJson = static_cast<char*>(malloc(compressedSize));
|
|
if (!compressedJson)
|
|
return false;
|
|
size = dataBuffer.read(compressedJson, compressedSize);
|
|
if (size != compressedSize) {
|
|
free(compressedJson);
|
|
return false;
|
|
}
|
|
QByteArray t_jsonBytes = QByteArray::fromRawData(compressedJson, compressedSize);
|
|
p_jsonData = qUncompress(t_jsonBytes);
|
|
free(compressedJson);
|
|
if (p_jsonData.isEmpty())
|
|
return false;
|
|
QJsonDocument t_jsonDocument = QJsonDocument::fromJson(p_jsonData);
|
|
if (t_jsonDocument.isNull())
|
|
return false;
|
|
p_jsonObject = t_jsonDocument.object();
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_titlOffset = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_titlBuffer = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
compressedSize = charToUInt32LE(uInt32Buffer);
|
|
|
|
char *compressedTitl = static_cast<char*>(malloc(compressedSize));
|
|
if (!compressedTitl)
|
|
return false;
|
|
size = dataBuffer.read(compressedTitl, compressedSize);
|
|
if (size != compressedSize) {
|
|
free(compressedTitl);
|
|
return false;
|
|
}
|
|
QByteArray t_titlBytes = QByteArray::fromRawData(compressedTitl, compressedSize);
|
|
t_titlBytes = qUncompress(t_titlBytes);
|
|
free(compressedTitl);
|
|
p_titleString = QString::fromUtf8(t_titlBytes);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_descOffset = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_descBuffer = charToUInt32LE(uInt32Buffer);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
compressedSize = charToUInt32LE(uInt32Buffer);
|
|
|
|
char *compressedDesc = static_cast<char*>(malloc(compressedSize));
|
|
if (!compressedDesc)
|
|
return false;
|
|
size = dataBuffer.read(compressedDesc, compressedSize);
|
|
if (size != compressedSize) {
|
|
free(compressedDesc);
|
|
return false;
|
|
}
|
|
QByteArray t_descBytes = QByteArray::fromRawData(compressedDesc, compressedSize);
|
|
t_descBytes = qUncompress(t_descBytes);
|
|
free(compressedDesc);
|
|
p_descriptionString = QString::fromUtf8(t_descBytes);
|
|
|
|
size = dataBuffer.read(uInt32Buffer, 4);
|
|
if (size != 4)
|
|
return false;
|
|
p_endOfFile = charToUInt32LE(uInt32Buffer);
|
|
|
|
#ifdef RAGEPHOTO_BENCHMARK
|
|
auto benchmark_parse_end = std::chrono::high_resolution_clock::now();
|
|
auto benchmark_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(benchmark_parse_end - benchmark_parse_start);
|
|
if (p_inputMode == 1) {
|
|
QTextStream(stdout) << QFileInfo(p_filePath).fileName() << ": " << benchmark_ns.count() << "ns" << Qt::endl;
|
|
}
|
|
else {
|
|
QTextStream(stdout) << "PGTA5" << p_jsonObject.value("uid").toInt() << ": " << benchmark_ns.count() << "ns" << Qt::endl;
|
|
}
|
|
#endif
|
|
|
|
p_photoFormat = PhotoFormat::G5EX;
|
|
|
|
p_fileData.clear();
|
|
p_isLoaded = true;
|
|
return true;
|
|
}
|
|
else if (format == static_cast<quint32>(ExportFormat::G5E2P)) {
|
|
p_photoFormat = PhotoFormat::G5EX;
|
|
p_fileData = qUncompress(dataBuffer.readAll());
|
|
if (p_fileData.isEmpty())
|
|
return false;
|
|
p_inputMode = 0;
|
|
return load();
|
|
}
|
|
else if (format == static_cast<quint32>(ExportFormat::G5E1P)) {
|
|
#if QT_VERSION >= 0x050A00
|
|
size = dataBuffer.skip(1);
|
|
if (size != 1)
|
|
return false;
|
|
#else
|
|
if (!dataBuffer.seek(dataBuffer.pos() + 1))
|
|
return false;
|
|
#endif
|
|
|
|
char length[1];
|
|
size = dataBuffer.read(length, 1);
|
|
if (size != 1)
|
|
return false;
|
|
int i_length = QByteArray::number(static_cast<int>(length[0]), 16).toInt() + 6;
|
|
|
|
#if QT_VERSION >= 0x050A00
|
|
size = dataBuffer.skip(i_length);
|
|
if (size != i_length)
|
|
return false;
|
|
#else
|
|
if (!dataBuffer.seek(dataBuffer.pos() + i_length))
|
|
return false;
|
|
#endif
|
|
|
|
p_photoFormat = PhotoFormat::G5EX;
|
|
p_fileData = qUncompress(dataBuffer.readAll());
|
|
if (p_fileData.isEmpty())
|
|
return false;
|
|
p_inputMode = 0;
|
|
return load();
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void RagePhoto::clear()
|
|
{
|
|
p_photoFormat = PhotoFormat::Undefined;
|
|
p_jsonObject = QJsonObject();
|
|
p_descriptionString.clear();
|
|
p_jsonData.clear();
|
|
p_photoData.clear();
|
|
p_photoString.clear();
|
|
p_titleString.clear();
|
|
p_headerSum = 0;
|
|
p_isLoaded = false;
|
|
}
|
|
|
|
void RagePhoto::setDescription(const QString &description)
|
|
{
|
|
p_descriptionString = description;
|
|
}
|
|
|
|
void RagePhoto::setFileData(const QByteArray &data)
|
|
{
|
|
p_fileData = data;
|
|
p_inputMode = 0;
|
|
}
|
|
|
|
void RagePhoto::setFilePath(const QString &filePath)
|
|
{
|
|
p_filePath = filePath;
|
|
p_inputMode = 1;
|
|
}
|
|
|
|
void RagePhoto::setIODevice(QIODevice *ioDevice)
|
|
{
|
|
p_ioDevice = ioDevice;
|
|
p_inputMode = 2;
|
|
}
|
|
|
|
bool RagePhoto::setJsonData(const QByteArray &data)
|
|
{
|
|
QJsonDocument t_jsonDocument = QJsonDocument::fromJson(data);
|
|
if (t_jsonDocument.isNull())
|
|
return false;
|
|
p_jsonObject = t_jsonDocument.object();
|
|
// serializer band-aid
|
|
QJsonObject t_jsonObject = p_jsonObject;
|
|
t_jsonObject["sign"] = "__gta5view.sign";
|
|
t_jsonDocument.setObject(t_jsonObject);
|
|
p_jsonData = t_jsonDocument.toJson(QJsonDocument::Compact);
|
|
char sign_char[24];
|
|
sprintf(sign_char, "%llu", (0x100000000000000ULL | joaatFromSI(p_photoData.constData(), p_photoData.size())));
|
|
p_jsonData.replace("\"__gta5view.sign\"", sign_char);
|
|
return true;
|
|
}
|
|
|
|
bool RagePhoto::setPhotoBuffer(quint32 size, bool moveOffsets)
|
|
{
|
|
if (size < static_cast<quint32>(p_photoData.size()))
|
|
return false;
|
|
p_photoBuffer = size;
|
|
if (moveOffsets) {
|
|
p_jsonOffset = size + 28;
|
|
p_titlOffset = p_jsonOffset + p_jsonBuffer + 8;
|
|
p_descOffset = p_titlOffset + p_titlBuffer + 8;
|
|
p_endOfFile = p_descOffset + p_descBuffer + 12;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RagePhoto::setPhotoData(const QByteArray &data)
|
|
{
|
|
quint32 size = data.size();
|
|
if (size > p_photoBuffer)
|
|
return false;
|
|
p_photoData = data;
|
|
// serializer band-aid
|
|
setJsonData(p_jsonData);
|
|
return true;
|
|
}
|
|
|
|
bool RagePhoto::setPhotoData(const char *data, int size)
|
|
{
|
|
if (static_cast<quint32>(size) > p_photoBuffer)
|
|
return false;
|
|
p_photoData = QByteArray(data, size);
|
|
// serializer band-aid
|
|
setJsonData(p_jsonData);
|
|
return true;
|
|
}
|
|
|
|
void RagePhoto::setPhotoFormat(PhotoFormat photoFormat)
|
|
{
|
|
p_photoFormat = photoFormat;
|
|
}
|
|
|
|
void RagePhoto::setTitle(const QString &title)
|
|
{
|
|
p_titleString = title;
|
|
}
|
|
|
|
const QByteArray RagePhoto::jsonData(JsonFormat jsonFormat)
|
|
{
|
|
if (jsonFormat == JsonFormat::Compact) {
|
|
return QJsonDocument(p_jsonObject).toJson(QJsonDocument::Compact);
|
|
}
|
|
else if (jsonFormat == JsonFormat::Indented) {
|
|
return QJsonDocument(p_jsonObject).toJson(QJsonDocument::Indented);
|
|
}
|
|
else {
|
|
return p_jsonData;
|
|
}
|
|
}
|
|
|
|
const QJsonObject RagePhoto::jsonObject()
|
|
{
|
|
return p_jsonObject;
|
|
}
|
|
|
|
const QByteArray RagePhoto::photoData()
|
|
{
|
|
return p_photoData;
|
|
}
|
|
|
|
const QString RagePhoto::description()
|
|
{
|
|
return p_descriptionString;
|
|
}
|
|
|
|
const QString RagePhoto::photoString()
|
|
{
|
|
return p_photoString;
|
|
}
|
|
|
|
const QString RagePhoto::title()
|
|
{
|
|
return p_titleString;
|
|
}
|
|
|
|
quint32 RagePhoto::photoBuffer()
|
|
{
|
|
return p_photoBuffer;
|
|
}
|
|
|
|
quint32 RagePhoto::photoSize()
|
|
{
|
|
return p_photoData.size();
|
|
}
|
|
|
|
RagePhoto::PhotoFormat RagePhoto::photoFormat()
|
|
{
|
|
return p_photoFormat;
|
|
}
|
|
|
|
QByteArray RagePhoto::save(PhotoFormat photoFormat)
|
|
{
|
|
QByteArray data;
|
|
QBuffer dataBuffer(&data);
|
|
dataBuffer.open(QIODevice::WriteOnly);
|
|
save(&dataBuffer, photoFormat);
|
|
return data;
|
|
}
|
|
|
|
void RagePhoto::save(QIODevice *ioDevice, PhotoFormat photoFormat)
|
|
{
|
|
// serializer band-aid
|
|
setJsonData(p_jsonData);
|
|
|
|
if (photoFormat == PhotoFormat::G5EX) {
|
|
char uInt32Buffer[4];
|
|
quint32 format = static_cast<quint32>(PhotoFormat::G5EX);
|
|
uInt32ToCharLE(format, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
format = static_cast<quint32>(ExportFormat::G5E3P);
|
|
uInt32ToCharLE(format, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
QByteArray compressedData = qCompress(p_photoString.toUtf8(), 9);
|
|
quint32 compressedSize = compressedData.size();
|
|
uInt32ToCharLE(compressedSize, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
ioDevice->write(compressedData);
|
|
|
|
uInt32ToCharLE(p_headerSum, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
uInt32ToCharLE(p_photoBuffer, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
compressedData = qCompress(p_photoData, 9);
|
|
compressedSize = compressedData.size();
|
|
uInt32ToCharLE(compressedSize, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
ioDevice->write(compressedData);
|
|
|
|
uInt32ToCharLE(p_jsonOffset, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
uInt32ToCharLE(p_jsonBuffer, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
compressedData = qCompress(p_jsonData, 9);
|
|
compressedSize = compressedData.size();
|
|
uInt32ToCharLE(compressedSize, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
ioDevice->write(compressedData);
|
|
|
|
uInt32ToCharLE(p_titlOffset, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
uInt32ToCharLE(p_titlBuffer, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
compressedData = qCompress(p_titleString.toUtf8(), 9);
|
|
compressedSize = compressedData.size();
|
|
uInt32ToCharLE(compressedSize, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
ioDevice->write(compressedData);
|
|
|
|
uInt32ToCharLE(p_descOffset, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
uInt32ToCharLE(p_descBuffer, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
compressedData = qCompress(p_descriptionString.toUtf8(), 9);
|
|
compressedSize = compressedData.size();
|
|
uInt32ToCharLE(compressedSize, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
ioDevice->write(compressedData);
|
|
|
|
uInt32ToCharLE(p_endOfFile, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
}
|
|
else if (photoFormat == PhotoFormat::GTA5) {
|
|
char uInt32Buffer[4];
|
|
quint32 format = static_cast<quint32>(PhotoFormat::GTA5);
|
|
uInt32ToCharLE(format, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
QByteArray photoHeader = stringToUtf16LE(p_photoString);
|
|
if (photoHeader.startsWith("\xFF\xFE")) {
|
|
photoHeader.remove(0, 2);
|
|
}
|
|
qint64 photoHeaderSize = photoHeader.size();
|
|
if (photoHeaderSize > 256) {
|
|
photoHeader = photoHeader.left(256);
|
|
photoHeaderSize = 256;
|
|
}
|
|
ioDevice->write(photoHeader);
|
|
for (qint64 size = photoHeaderSize; size < 256; size++) {
|
|
ioDevice->write("\x00", 1);
|
|
}
|
|
|
|
uInt32ToCharLE(p_headerSum, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
uInt32ToCharLE(p_endOfFile, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
uInt32ToCharLE(p_jsonOffset, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
uInt32ToCharLE(p_titlOffset, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
uInt32ToCharLE(p_descOffset, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
ioDevice->write("JPEG", 4);
|
|
|
|
uInt32ToCharLE(p_photoBuffer, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
quint32 t_photoSize = p_photoData.size();
|
|
uInt32ToCharLE(t_photoSize, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
ioDevice->write(p_photoData);
|
|
for (qint64 size = t_photoSize; size < p_photoBuffer; size++) {
|
|
ioDevice->write("\x00", 1);
|
|
}
|
|
|
|
ioDevice->seek(p_jsonOffset + 264);
|
|
ioDevice->write("JSON", 4);
|
|
|
|
uInt32ToCharLE(p_jsonBuffer, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
qint64 dataSize = p_jsonData.size();
|
|
ioDevice->write(p_jsonData);
|
|
for (qint64 size = dataSize; size < p_jsonBuffer; size++) {
|
|
ioDevice->write("\x00", 1);
|
|
}
|
|
|
|
ioDevice->seek(p_titlOffset + 264);
|
|
ioDevice->write("TITL", 4);
|
|
|
|
uInt32ToCharLE(p_titlBuffer, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
QByteArray data = p_titleString.toUtf8();
|
|
dataSize = data.size();
|
|
ioDevice->write(data);
|
|
for (qint64 size = dataSize; size < p_titlBuffer; size++) {
|
|
ioDevice->write("\x00", 1);
|
|
}
|
|
|
|
ioDevice->seek(p_descOffset + 264);
|
|
ioDevice->write("DESC", 4);
|
|
|
|
uInt32ToCharLE(p_descBuffer, uInt32Buffer);
|
|
ioDevice->write(uInt32Buffer, 4);
|
|
|
|
data = p_descriptionString.toUtf8();
|
|
dataSize = data.size();
|
|
ioDevice->write(data);
|
|
for (qint64 size = dataSize; size < p_descBuffer; size++) {
|
|
ioDevice->write("\x00", 1);
|
|
}
|
|
|
|
ioDevice->seek(p_endOfFile + 260);
|
|
ioDevice->write("JEND", 4);
|
|
}
|
|
}
|
|
|
|
RagePhoto* RagePhoto::loadFile(const QString &filePath)
|
|
{
|
|
RagePhoto *ragePhoto = new RagePhoto(filePath);
|
|
ragePhoto->load();
|
|
return ragePhoto;
|
|
}
|
|
|
|
quint32 RagePhoto::charToUInt32BE(char *x)
|
|
{
|
|
return (static_cast<unsigned char>(x[0]) << 24 |
|
|
static_cast<unsigned char>(x[1]) << 16 |
|
|
static_cast<unsigned char>(x[2]) << 8 |
|
|
static_cast<unsigned char>(x[3]));
|
|
}
|
|
|
|
quint32 RagePhoto::charToUInt32LE(char *x)
|
|
{
|
|
return (static_cast<unsigned char>(x[3]) << 24 |
|
|
static_cast<unsigned char>(x[2]) << 16 |
|
|
static_cast<unsigned char>(x[1]) << 8 |
|
|
static_cast<unsigned char>(x[0]));
|
|
}
|
|
|
|
void RagePhoto::uInt32ToCharBE(quint32 x, char *y)
|
|
{
|
|
y[0] = x >> 24;
|
|
y[1] = x >> 16;
|
|
y[2] = x >> 8;
|
|
y[3] = x;
|
|
}
|
|
|
|
void RagePhoto::uInt32ToCharLE(quint32 x, char *y)
|
|
{
|
|
y[0] = x;
|
|
y[1] = x >> 8;
|
|
y[2] = x >> 16;
|
|
y[3] = x >> 24;
|
|
}
|
|
|
|
const QByteArray RagePhoto::stringToUtf16LE(const QString &string)
|
|
{
|
|
#if QT_VERSION >= 0x060000
|
|
return QStringEncoder(QStringEncoder::Utf16LE)(string);
|
|
#else
|
|
return QTextCodec::codecForName("UTF-16LE")->fromUnicode(string);
|
|
#endif
|
|
}
|
|
|
|
const QString RagePhoto::utf16LEToString(const QByteArray &data)
|
|
{
|
|
#if QT_VERSION >= 0x060000
|
|
return QStringDecoder(QStringDecoder::Utf16LE)(data);
|
|
#else
|
|
return QTextCodec::codecForName("UTF-16LE")->toUnicode(data);
|
|
#endif
|
|
}
|
|
|
|
const QString RagePhoto::utf16LEToString(const char *data, int size)
|
|
{
|
|
#if QT_VERSION >= 0x060000
|
|
return QStringDecoder(QStringDecoder::Utf16LE)(QByteArray::fromRawData(data, size));
|
|
#else
|
|
return QTextCodec::codecForName("UTF-16LE")->toUnicode(data, size);
|
|
#endif
|
|
}
|