diff --git a/RagePhoto.cpp b/RagePhoto.cpp index 200f0dd..b8152f8 100644 --- a/RagePhoto.cpp +++ b/RagePhoto.cpp @@ -23,18 +23,24 @@ #include #include -RagePhoto::RagePhoto(const QString &filePath) : p_filePath(filePath) +RagePhoto::RagePhoto(const QByteArray &data) : p_fileData(data) { p_inputMode = 0; p_isLoaded = false; } -RagePhoto::RagePhoto(QIODevice *ioDevice) : p_ioDevice(ioDevice) +RagePhoto::RagePhoto(const QString &filePath) : p_filePath(filePath) { p_inputMode = 1; p_isLoaded = false; } +RagePhoto::RagePhoto(QIODevice *ioDevice) : p_ioDevice(ioDevice) +{ + p_inputMode = 2; + p_isLoaded = false; +} + bool RagePhoto::isLoaded() { return p_isLoaded; @@ -45,188 +51,317 @@ bool RagePhoto::load() if (p_isLoaded) clear(); - QByteArray readData; - if (p_inputMode == 0) { + if (p_inputMode == 1) { QFile pictureFile(p_filePath); if (pictureFile.open(QIODevice::ReadOnly)) { - readData = pictureFile.readAll(); + p_fileData = pictureFile.readAll(); } pictureFile.close(); } - else if (p_inputMode == 1) { + else if (p_inputMode == 2) { if (!p_ioDevice->isOpen()) { if (!p_ioDevice->open(QIODevice::ReadOnly)) return false; - readData = p_ioDevice->readAll(); } - } - else { - return false; + p_fileData = p_ioDevice->readAll(); } - QBuffer dataBuffer(&readData); + QBuffer dataBuffer(&p_fileData); dataBuffer.open(QIODevice::ReadOnly); char formatHeader[4]; qint64 size = dataBuffer.read(formatHeader, 4); if (size != 4) return false; - quint32 format = charToUInt32(formatHeader); - if (format != PhotoFormat::GTA5) - return false; + quint32 format = charToUInt32LE(formatHeader); - char photoHeader[256]; - size = dataBuffer.read(photoHeader, 256); - if (size != 256) - return false; - for (const QChar &photoChar : QTextCodec::codecForName("UTF-16LE")->toUnicode(photoHeader, 256)) { - if (photoChar.isNull()) - break; - p_photoString += photoChar; + if (format == PhotoFormat::GTA5) { + char photoHeader[256]; + size = dataBuffer.read(photoHeader, 256); + if (size != 256) + return false; + for (const QChar &photoChar : QTextCodec::codecForName("UTF-16LE")->toUnicode(photoHeader, 256)) { + if (photoChar.isNull()) + break; + p_photoString += photoChar; + } + + char checksum[4]; + size = dataBuffer.read(checksum, 4); + if (size != 4) + return false; + p_headerSum = charToUInt32LE(checksum); + + char endOfFile[4]; + size = dataBuffer.read(endOfFile, 4); + if (size != 4) + return false; + p_endOfFile = charToUInt32LE(endOfFile); + + char jsonOffset[4]; + size = dataBuffer.read(jsonOffset, 4); + if (size != 4) + return false; + p_jsonOffset = charToUInt32LE(jsonOffset); + + char titleOffset[4]; + size = dataBuffer.read(titleOffset, 4); + if (size != 4) + return false; + p_titlOffset = charToUInt32LE(titleOffset); + + char descOffset[4]; + size = dataBuffer.read(descOffset, 4); + if (size != 4) + return false; + p_descOffset = charToUInt32LE(descOffset); + + char jpegMarker[4]; + size = dataBuffer.read(jpegMarker, 4); + if (size != 4) + return false; + if (strncmp(jpegMarker, "JPEG", 4) != 0) + return false; + + char jpegBuffer[4]; + size = dataBuffer.read(jpegBuffer, 4); + if (size != 4) + return false; + p_jpegBuffer = charToUInt32LE(jpegBuffer); + + char photoSize[4]; + size = dataBuffer.read(photoSize, 4); + if (size != 4) + return false; + p_photoSize = charToUInt32LE(photoSize); + + char photoData[p_photoSize]; + size = dataBuffer.read(photoData, p_photoSize); + if (size != p_photoSize) + return false; + p_photoData = QByteArray::fromRawData(photoData, p_photoSize); + + dataBuffer.seek(p_jsonOffset + 264); + char jsonMarker[4]; + size = dataBuffer.read(jsonMarker, 4); + if (size != 4) + return false; + if (strncmp(jsonMarker, "JSON", 4) != 0) + return false; + + char jsonSize[4]; + size = dataBuffer.read(jsonSize, 4); + if (size != 4) + return false; + quint32 i_jsonSize = charToUInt32LE(jsonSize); + + char jsonBytes[i_jsonSize]; + size = dataBuffer.read(jsonBytes, i_jsonSize); + if (size != i_jsonSize) + return false; + QByteArray t_jsonBytes; + for (quint32 i = 0; i != i_jsonSize; i++) { + if (jsonBytes[i] == '\x00') + break; + t_jsonBytes += jsonBytes[i]; + } + QJsonDocument t_jsonDocument = QJsonDocument::fromJson(t_jsonBytes); + if (t_jsonDocument.isNull()) + return false; + p_jsonObject = t_jsonDocument.object(); + + dataBuffer.seek(p_titlOffset + 264); + char titlMarker[4]; + size = dataBuffer.read(titlMarker, 4); + if (size != 4) + return false; + if (strncmp(titlMarker, "TITL", 4) != 0) + return false; + + char titlSize[4]; + size = dataBuffer.read(titlSize, 4); + if (size != 4) + return false; + quint32 i_titlSize = charToUInt32LE(titlSize); + + char titlBytes[i_titlSize]; + size = dataBuffer.read(titlBytes, i_titlSize); + if (size != i_titlSize) + return false; + for (const QChar &titlChar : QString::fromUtf8(titlBytes, i_titlSize)) { + if (titlChar.isNull()) + break; + p_titleString += titlChar; + } + + dataBuffer.seek(p_descOffset + 264); + char descMarker[4]; + size = dataBuffer.read(descMarker, 4); + if (size != 4) + return false; + if (strncmp(descMarker, "DESC", 4) != 0) + return false; + + char descSize[4]; + size = dataBuffer.read(descSize, 4); + if (size != 4) + return false; + quint32 i_descSize = charToUInt32LE(descSize); + + char descBytes[i_descSize]; + size = dataBuffer.read(descBytes, i_descSize); + if (size != i_descSize) + return false; + for (const QChar &descChar : QString::fromUtf8(descBytes, i_descSize)) { + if (descChar.isNull()) + break; + p_descriptionString += descChar; + } + + dataBuffer.seek(p_endOfFile + 260); + char jendMarker[4]; + size = dataBuffer.read(jendMarker, 4); + if (size != 4) + return false; + if (strncmp(jendMarker, "JEND", 4) != 0) + return false; + + p_fileData.clear(); + p_isLoaded = true; + return true; } + else if (format == PhotoFormat::G5EX) { + char formatHeader[4]; + qint64 size = dataBuffer.read(formatHeader, 4); + if (size != 4) + return false; + quint32 format = charToUInt32LE(formatHeader); + if (format == ExportFormat::G5E3P) { + char photoHeaderSize[4]; + size = dataBuffer.peek(photoHeaderSize, 4); + if (size != 4) + return false; + quint32 i_photoHeaderSize = charToUInt32BE(photoHeaderSize) + 4; - char checksum[4]; - size = dataBuffer.read(checksum, 4); - if (size != 4) - return false; - p_headerCRC = charToUInt32(checksum); + char compressedPhotoHeader[i_photoHeaderSize]; + size = dataBuffer.read(compressedPhotoHeader, i_photoHeaderSize); + if (size != i_photoHeaderSize) + return false; + QByteArray t_photoHeaderBytes = QByteArray::fromRawData(compressedPhotoHeader, i_photoHeaderSize); + t_photoHeaderBytes = qUncompress(t_photoHeaderBytes); + p_photoString = QString::fromUtf8(t_photoHeaderBytes); - char endOfFile[4]; - size = dataBuffer.read(endOfFile, 4); - if (size != 4) - return false; - p_endOfFile = charToUInt32(endOfFile); + char checksum[4]; + size = dataBuffer.read(checksum, 4); + if (size != 4) + return false; + p_headerSum = charToUInt32LE(checksum); - char jsonOffset[4]; - size = dataBuffer.read(jsonOffset, 4); - if (size != 4) - return false; - p_jsonOffset = charToUInt32(jsonOffset); + char jpegBuffer[4]; + size = dataBuffer.read(jpegBuffer, 4); + if (size != 4) + return false; + p_jpegBuffer = charToUInt32LE(jpegBuffer); - char titleOffset[4]; - size = dataBuffer.read(titleOffset, 4); - if (size != 4) - return false; - p_titlOffset = charToUInt32(titleOffset); + char photoSize[4]; + size = dataBuffer.peek(photoSize, 4); + if (size != 4) + return false; + quint32 i_photoSize = charToUInt32BE(photoSize) + 4; - char descOffset[4]; - size = dataBuffer.read(descOffset, 4); - if (size != 4) - return false; - p_descOffset = charToUInt32(descOffset); + char compressedPhoto[i_photoSize]; + size = dataBuffer.read(compressedPhoto, i_photoSize); + if (size != i_photoSize) + return false; + QByteArray t_photoData = QByteArray::fromRawData(compressedPhoto, i_photoSize); + p_photoData = qUncompress(t_photoData); + p_photoSize = p_photoData.size(); - char jpegMarker[4]; - size = dataBuffer.read(jpegMarker, 4); - if (size != 4) - return false; - if (strncmp(jpegMarker, "JPEG", 4) != 0) - return false; + char jsonOffset[4]; + size = dataBuffer.read(jsonOffset, 4); + if (size != 4) + return false; + p_jsonOffset = charToUInt32LE(jsonOffset); - char jpegBuffer[4]; - size = dataBuffer.read(jpegBuffer, 4); - if (size != 4) - return false; - p_jpegBuffer = charToUInt32(jpegBuffer); + char jsonSize[4]; + size = dataBuffer.peek(jsonSize, 4); + if (size != 4) + return false; + quint32 i_jsonSize = charToUInt32BE(jsonSize) + 4; - char photoSize[4]; - size = dataBuffer.read(photoSize, 4); - if (size != 4) - return false; - p_photoSize = charToUInt32(photoSize); + char compressedJson[i_jsonSize]; + size = dataBuffer.read(compressedJson, i_jsonSize); + if (size != i_jsonSize) + return false; + QByteArray t_jsonBytes = QByteArray::fromRawData(compressedJson, i_jsonSize); + t_jsonBytes = qUncompress(t_jsonBytes); + QJsonDocument t_jsonDocument = QJsonDocument::fromJson(t_jsonBytes); + if (t_jsonDocument.isNull()) + return false; + p_jsonObject = t_jsonDocument.object(); - char photoData[p_photoSize]; - size = dataBuffer.read(photoData, p_photoSize); - if (size != p_photoSize) - return false; - p_photoData = QByteArray::fromRawData(photoData, p_photoSize); + char titleOffset[4]; + size = dataBuffer.read(titleOffset, 4); + if (size != 4) + return false; + p_titlOffset = charToUInt32LE(titleOffset); - dataBuffer.seek(p_jsonOffset + 264); - char jsonMarker[4]; - size = dataBuffer.read(jsonMarker, 4); - if (size != 4) - return false; - if (strncmp(jsonMarker, "JSON", 4) != 0) - return false; + char titlSize[4]; + size = dataBuffer.peek(titlSize, 4); + if (size != 4) + return false; + quint32 i_titlSize = charToUInt32BE(titlSize) + 4; - char jsonSize[4]; - size = dataBuffer.read(jsonSize, 4); - if (size != 4) - return false; - quint32 i_jsonSize = charToUInt32(jsonSize); + char compressedTitl[i_titlSize]; + size = dataBuffer.read(compressedTitl, i_titlSize); + if (size != i_titlSize) + return false; + QByteArray t_titlBytes = QByteArray::fromRawData(compressedTitl, i_titlSize); + t_titlBytes = qUncompress(t_titlBytes); + p_titleString = QString::fromUtf8(t_titlBytes); - char jsonBytes[i_jsonSize]; - size = dataBuffer.read(jsonBytes, i_jsonSize); - if (size != i_jsonSize) - return false; - QByteArray t_jsonBytes; - for (quint32 i = 0; i != i_jsonSize; i++) { - if (jsonBytes[i] == '\x00') - break; - t_jsonBytes += jsonBytes[i]; + char descOffset[4]; + size = dataBuffer.read(descOffset, 4); + if (size != 4) + return false; + p_descOffset = charToUInt32LE(descOffset); + + char descSize[4]; + size = dataBuffer.peek(descSize, 4); + if (size != 4) + return false; + quint32 i_descSize = charToUInt32BE(descSize) + 4; + + char compressedDesc[i_descSize]; + size = dataBuffer.read(compressedDesc, i_descSize); + if (size != i_descSize) + return false; + QByteArray t_descBytes = QByteArray::fromRawData(compressedDesc, i_descSize); + t_descBytes = qUncompress(t_descBytes); + p_descriptionString = QString::fromUtf8(t_descBytes); + + char endOfFile[4]; + size = dataBuffer.read(endOfFile, 4); + if (size != 4) + return false; + p_endOfFile = charToUInt32LE(endOfFile); + + p_fileData.clear(); + p_isLoaded = true; + return true; + } + else if (format == ExportFormat::G5E2P) { + p_fileData = dataBuffer.readAll(); + p_inputMode = 0; + return load(); + } + else { + return false; + } } - QJsonDocument t_jsonDocument = QJsonDocument::fromJson(t_jsonBytes); - if (t_jsonDocument.isNull()) + else { return false; - p_jsonObject = t_jsonDocument.object(); - - dataBuffer.seek(p_titlOffset + 264); - char titlMarker[4]; - size = dataBuffer.read(titlMarker, 4); - if (size != 4) - return false; - if (strncmp(titlMarker, "TITL", 4) != 0) - return false; - - char titlSize[4]; - size = dataBuffer.read(titlSize, 4); - if (size != 4) - return false; - quint32 i_titlSize = charToUInt32(titlSize); - - char titlBytes[i_titlSize]; - size = dataBuffer.read(titlBytes, i_titlSize); - if (size != i_titlSize) - return false; - for (const QChar &titlChar : QString::fromUtf8(titlBytes, i_titlSize)) { - if (titlChar.isNull()) - break; - p_titleString += titlChar; } - - dataBuffer.seek(p_descOffset + 264); - char descMarker[4]; - size = dataBuffer.read(descMarker, 4); - if (size != 4) - return false; - if (strncmp(descMarker, "DESC", 4) != 0) - return false; - - char descSize[4]; - size = dataBuffer.read(descSize, 4); - if (size != 4) - return false; - quint32 i_descSize = charToUInt32(descSize); - - char descBytes[i_descSize]; - size = dataBuffer.read(descBytes, i_descSize); - if (size != i_descSize) - return false; - for (const QChar &descChar : QString::fromUtf8(descBytes, i_descSize)) { - if (descChar.isNull()) - break; - p_descriptionString += descChar; - } - - dataBuffer.seek(p_endOfFile + 260); - char jendMarker[4]; - size = dataBuffer.read(jendMarker, 4); - if (size != 4) - return false; - if (strncmp(jendMarker, "JEND", 4) != 0) - return false; - - p_isLoaded = true; - return true; } void RagePhoto::clear() @@ -236,7 +371,7 @@ void RagePhoto::clear() p_photoData.clear(); p_photoString.clear(); p_titleString.clear(); - p_headerCRC = 0; + p_headerSum = 0; p_isLoaded = false; } @@ -293,7 +428,12 @@ RagePhoto* RagePhoto::loadFile(const QString &filePath) return ragePhoto; } -quint32 RagePhoto::charToUInt32(char *x) +quint32 RagePhoto::charToUInt32BE(char *x) +{ + return (((unsigned char)x[0] << 24) | ((unsigned char)x[1] << 16) | ((unsigned char)x[2] << 8) | ((unsigned char)x[3])); +} + +quint32 RagePhoto::charToUInt32LE(char *x) { return (((unsigned char)x[3] << 24) | ((unsigned char)x[2] << 16) | ((unsigned char)x[1] << 8) | ((unsigned char)x[0])); } diff --git a/RagePhoto.h b/RagePhoto.h index 3692599..4c0dbbb 100644 --- a/RagePhoto.h +++ b/RagePhoto.h @@ -27,10 +27,18 @@ class RagePhoto : public QObject { Q_OBJECT public: + enum ExportFormat { + G5E2P = 0x01000032U, + G5E2S = 0x02000032U, + G5E3P = 0x01000033U, + G5E3S = 0x02000033U, + }; enum PhotoFormat { + G5EX = 0x45354700U, GTA5 = 0x01000000U, RDR2 = 0x04000000U, }; + explicit RagePhoto(const QByteArray &data); explicit RagePhoto(const QString &filePath = QString()); explicit RagePhoto(QIODevice *device); bool isLoaded(); @@ -48,8 +56,10 @@ public: static RagePhoto* loadFile(const QString &filePath); private: - inline quint32 charToUInt32(char *x); + inline quint32 charToUInt32BE(char *x); + inline quint32 charToUInt32LE(char *x); QJsonObject p_jsonObject; + QByteArray p_fileData; QByteArray p_photoData; QIODevice *p_ioDevice; QString p_descriptionString; @@ -58,7 +68,7 @@ private: QString p_titleString; quint32 p_descOffset; quint32 p_endOfFile; - quint32 p_headerCRC; + quint32 p_headerSum; quint32 p_jpegBuffer; quint32 p_jsonOffset; quint32 p_photoSize;