diff --git a/src/data/FF7Save.cpp b/src/data/FF7Save.cpp index 5f5369435..434053ea0 100644 --- a/src/data/FF7Save.cpp +++ b/src/data/FF7Save.cpp @@ -61,6 +61,29 @@ FF7SaveInfo::FORMAT FF7Save::fileDataFormat(QFile &file) : QStringLiteral("Unknown")); return FF7SaveInfo::FORMAT::UNKNOWN; } + if ( (file_size == FF7SaveInfo::fileSize(FF7SaveInfo::FORMAT::PS4)) && file.peek(0x00B0 + FF7SaveInfo::fileIdentifier(FF7SaveInfo::FORMAT::PS4).length()).mid(0x00B0,FF7SaveInfo::get()->fileIdentifier(FF7SaveInfo::FORMAT::PS4).length()) == FF7SaveInfo::get()->fileIdentifier(FF7SaveInfo::FORMAT::PS4)) { + QTextStream(stdout) + << "[FF7Save::loadFile] PS4 Save " + << file.peek(0x00B0+FF7SaveInfo::get()->fileIdentifier(FF7SaveInfo::FORMAT::PS4).length()).mid(0x00B0,FF7SaveInfo::get()->fileIdentifier(FF7SaveInfo::FORMAT::PS4).length()).toHex( ) + << "\n"; + QFile ps4binfile(QFileInfo(file).path() + "/" + QFileInfo(file).fileName() + ".bin");//QFileInfo(file).baseName() + if (!ps4binfile.open(QIODevice::ReadOnly)) { + QTextStream(stdout) << "[FF7Save::loadFile] PS4 BIN File error: missing file: " << QFileInfo(ps4binfile).absoluteFilePath(); + return FF7SaveInfo::FORMAT::UNKNOWN; + } else { + QTextStream(stdout) << "[FF7Save::loadFile] PS4 BIN File loaded: " << QFileInfo(ps4binfile).absoluteFilePath() << "\n"; + if(ps4binfile.size() == FF7SaveInfo::get()->fileSize(FF7SaveInfo::FORMAT::PS4BIN) && (ps4binfile.peek(FF7SaveInfo::fileHeaderSize(FF7SaveInfo::FORMAT::PS4BIN)) == FF7SaveInfo::fileIdentifier(FF7SaveInfo::FORMAT::PS4BIN))) { + auto ps4bin = ps4binfile.readAll(); + m_ps4_iv = ps4bin.mid(0x10, 0x10); + m_ps4_key = ps4bin.mid(0x20, 0x20); + QTextStream(stdout) + << "[FF7Save::loadFile] PS4 BIN File Size: " << FF7SaveInfo::get()->fileSize(FF7SaveInfo::FORMAT::PS4BIN) << "\n" + << "[FF7Save::loadFile] PS4 BIN pfsSKKey IV: " << m_ps4_iv.toHex() << "\n" + << "[FF7Save::loadFile] PS4 BIN pfsSKKey KEY: " << m_ps4_key.toHex() << "\n"; + return FF7SaveInfo::FORMAT::PS4; + } + } + } if ((file_size == FF7SaveInfo::fileSize(FF7SaveInfo::FORMAT::PSP)) && (file.peek(25)).startsWith(FF7SaveInfo::fileIdentifier(FF7SaveInfo::FORMAT::PSP))) return FF7SaveInfo::FORMAT::PSP; if ((file_size == FF7SaveInfo::fileSize(FF7SaveInfo::FORMAT::VGS)) && (file.peek(25)).startsWith(FF7SaveInfo::fileIdentifier(FF7SaveInfo::FORMAT::VGS))) @@ -91,13 +114,28 @@ bool FF7Save::loadFile(const QString &fileName) return false; setFormat(fileFormat); - /*~~~~~~~~~~Start Load~~~~~~~~~~*/ - setFileHeader(file.read(FF7SaveInfo::fileHeaderSize(fileFormat))); - for (int i = 0; i < FF7SaveInfo::slotCount(fileFormat); i++) { - setSlotHeader(i, file.read(FF7SaveInfo::slotHeaderSize(fileFormat))); - setSlotFF7Data(i, file.read(FF7SaveInfo::slotSize())); - setSlotFooter(i, file.read(FF7SaveInfo::slotFooterSize(fileFormat))); + + + if(fileFormat == FF7SaveInfo::FORMAT::PS4) { + qDebug() << "Trying to Decrypt"; + QFile ofile("outdata.dat"); + ofile.open(QFile::WriteOnly); + ofile.write(decryptPS4Save(file.readAll())); + for (int i = 0; i < FF7SaveInfo::slotCount(fileFormat); i++) { + setSlotHeader(i, QByteArray()); + setSlotFF7Data(i, decryptPS4Save(file.readAll())); + setSlotFooter(i, QByteArray()); + } + } else { + /*~~~~~~~~~~Start Load~~~~~~~~~~*/ + setFileHeader(file.read(FF7SaveInfo::fileHeaderSize(fileFormat))); + for (int i = 0; i < FF7SaveInfo::slotCount(fileFormat); i++) { + setSlotHeader(i, file.read(FF7SaveInfo::slotHeaderSize(fileFormat))); + setSlotFF7Data(i, file.read(FF7SaveInfo::slotSize())); + setSlotFooter(i, file.read(FF7SaveInfo::slotFooterSize(fileFormat))); + } } + /*~~~~~~~End Load~~~~~~~~~~~~~~*/ if (FF7SaveInfo::isTypePC(fileFormat)) { for (int i = 0; i < 15; i++) { @@ -913,7 +951,8 @@ char FF7Save::psx_block_type(int s) case FF7SaveInfo::FORMAT::PC: case FF7SaveInfo::FORMAT::SWITCH: case FF7SaveInfo::FORMAT::PSX: - case FF7SaveInfo::FORMAT::PS3: return 0x00; + case FF7SaveInfo::FORMAT::PS3: + case FF7SaveInfo::FORMAT::PS4: return 0x00; case FF7SaveInfo::FORMAT::PGE: return _fileHeader.at(0); default: int index = 128 + (128 * s); @@ -930,7 +969,8 @@ void FF7Save::setPsx_block_type(int s, char block_type) case FF7SaveInfo::FORMAT::PC: case FF7SaveInfo::FORMAT::SWITCH: case FF7SaveInfo::FORMAT::PSX: - case FF7SaveInfo::FORMAT::PS3: return; + case FF7SaveInfo::FORMAT::PS3: + case FF7SaveInfo::FORMAT::PS4: return; case FF7SaveInfo::FORMAT::PGE: _fileHeader.replace(0, 1, QByteArray(1, block_type)); break; default: int index = 128 + (128 * s); @@ -950,7 +990,8 @@ void FF7Save::setPsx_block_next(int s, int next) case FF7SaveInfo::FORMAT::PC: case FF7SaveInfo::FORMAT::SWITCH: case FF7SaveInfo::FORMAT::PSX: - case FF7SaveInfo::FORMAT::PS3: return; + case FF7SaveInfo::FORMAT::PS3: + case FF7SaveInfo::FORMAT::PS4: return; case FF7SaveInfo::FORMAT::PGE: _fileHeader.replace(8, 1, QByteArray(1, next)); break; default: int index = 128 + (128 * s); @@ -967,7 +1008,8 @@ quint8 FF7Save::psx_block_next(int s) case FF7SaveInfo::FORMAT::PC: case FF7SaveInfo::FORMAT::SWITCH: case FF7SaveInfo::FORMAT::PSX: - case FF7SaveInfo::FORMAT::PS3: return 0x00; + case FF7SaveInfo::FORMAT::PS3: + case FF7SaveInfo::FORMAT::PS4: return 0x00; case FF7SaveInfo::FORMAT::PGE: return quint8(_fileHeader.at(0x08)); default: int index = 128 + (128 * s); @@ -984,7 +1026,8 @@ void FF7Save::setPsx_block_size(int s, int blockSize) case FF7SaveInfo::FORMAT::PC: case FF7SaveInfo::FORMAT::SWITCH: case FF7SaveInfo::FORMAT::PSX: - case FF7SaveInfo::FORMAT::PS3: return; + case FF7SaveInfo::FORMAT::PS3: + case FF7SaveInfo::FORMAT::PS4: return; default: break; } @@ -1007,6 +1050,7 @@ quint8 FF7Save::psx_block_size(int s) switch (fileFormat) { case FF7SaveInfo::FORMAT::UNKNOWN: case FF7SaveInfo::FORMAT::PC: + case FF7SaveInfo::FORMAT::PS4: case FF7SaveInfo::FORMAT::SWITCH: return 1; case FF7SaveInfo::FORMAT::PSX: return quint8(QFile(fileName()).size() / FF7SaveInfo::fileSize(FF7SaveInfo::FORMAT::PSX)); case FF7SaveInfo::FORMAT::PS3: return quint8((QFile(fileName()).size() - 0x84) / FF7SaveInfo::fileSize(FF7SaveInfo::FORMAT::PSX)); @@ -2747,6 +2791,16 @@ bool FF7Save::fixMetaData(QString fileName, QString UserID) return 1; } +QByteArray FF7Save::decryptPS4Save(QByteArray in) +{ + QByteArray buffer = in; + struct AES_ctx ctx; + AES_init_ctx_iv(&ctx, reinterpret_cast(m_ps4_key.data()), reinterpret_cast(m_ps4_iv.data())); + AES_CBC_decrypt_buffer(&ctx, reinterpret_cast(buffer.data()), 256); + qDebug() << "Decrypted Size" << buffer.size(); + return buffer; + +} QByteArray FF7Save::generatePsSaveSignature(QByteArray data, QByteArray keySeed) { FF7SaveInfo::FORMAT saveFormat = format(); diff --git a/src/data/FF7Save.h b/src/data/FF7Save.h index 3552a1f1f..9ccdb2414 100644 --- a/src/data/FF7Save.h +++ b/src/data/FF7Save.h @@ -1020,6 +1020,8 @@ class FF7TKDATA_EXPORT FF7Save: public QObject */ QString md5sum(QString fileName, QString UserID); + + QByteArray decryptPS4Save(QByteArray in); QString fileblock(const QString &fileName); QString filetimestamp(QString fileName); void checksumSlots(); @@ -1042,10 +1044,11 @@ class FF7TKDATA_EXPORT FF7Save: public QObject bool fileHasChanged; QString buffer_region; // hold the buffers region data. QList SG_Region_String; + QByteArray m_ps4_iv; + QByteArray m_ps4_key; QString filename;//opened file QVector< SubContainer > parseXML(const QString &fileName, const QString &metadataPath, const QString &UserID); QVector< SubContainer > createMetadata(const QString &fileName, const QString &UserID); - inline static const auto allDigetRegEx = QRegularExpression(QStringLiteral("^\\d+$")); inline static const QString invalidRegion = QStringLiteral("\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000"); }; diff --git a/src/data/FF7SaveInfo.cpp b/src/data/FF7SaveInfo.cpp index d4baf36a3..c6af9d943 100644 --- a/src/data/FF7SaveInfo.cpp +++ b/src/data/FF7SaveInfo.cpp @@ -30,6 +30,8 @@ int FF7SaveInfo::fileSize(FF7SaveInfo::FORMAT format) case FORMAT::VMC: return get()->d->VMC_FILE_SIZE; case FORMAT::PSP: return get()->d->PSP_FILE_SIZE; case FORMAT::PS3: return get()->d->PS3_FILE_SIZE; + case FORMAT::PS4: return get()->d->PS4_FILE_SIZE; + case FORMAT::PS4BIN: return get()->d->PS4_BINFILE_SIZE; case FORMAT::DEX: return get()->d->DEX_FILE_SIZE; case FORMAT::VGS: return get()->d->VGS_FILE_SIZE; case FORMAT::SWITCH: return get()->d->SWITCH_FILE_SIZE; @@ -46,6 +48,8 @@ int FF7SaveInfo::fileHeaderSize(FF7SaveInfo::FORMAT format) case FORMAT::VMC: return get()->d->VMC_FILE_HEADER_SIZE; case FORMAT::PSP: return get()->d->PSP_FILE_HEADER_SIZE; case FORMAT::PS3: return get()->d->PS3_FILE_HEADER_SIZE; + case FORMAT::PS4: return get()->d->PS4_FILE_HEADER_SIZE; + case FORMAT::PS4BIN: return get()->d->PS4_BINFILE_FILE_ID_SIZE; case FORMAT::DEX: return get()->d->DEX_FILE_HEADER_SIZE; case FORMAT::VGS: return get()->d->VGS_FILE_HEADER_SIZE; case FORMAT::SWITCH: return get()->d->SWITCH_FILE_HEADER_SIZE; @@ -62,6 +66,7 @@ int FF7SaveInfo::slotHeaderSize(FF7SaveInfo::FORMAT format) case FORMAT::VMC: case FORMAT::PSP: case FORMAT::PS3: + case FORMAT::PS4: case FORMAT::DEX: case FORMAT::PGE: case FORMAT::PDA: @@ -77,6 +82,7 @@ int FF7SaveInfo::slotFooterSize(FF7SaveInfo::FORMAT format) case FORMAT::VMC: case FORMAT::PSP: case FORMAT::PS3: + case FORMAT::PS4: case FORMAT::DEX: case FORMAT::PGE: case FORMAT::PDA: @@ -91,7 +97,8 @@ int FF7SaveInfo::slotCount(FF7SaveInfo::FORMAT format) case FORMAT::PDA: case FORMAT::PGE: case FORMAT::PSX: - case FORMAT::PS3: return 1; + case FORMAT::PS3: + case FORMAT::PS4: return 1; case FORMAT::VMC: case FORMAT::PSP: case FORMAT::DEX: @@ -110,6 +117,8 @@ QByteArray FF7SaveInfo::fileIdentifier(FF7SaveInfo::FORMAT format) case FORMAT::VMC: return get()->d->VMC_FILE_ID; case FORMAT::PSP: return get()->d->PSP_FILE_ID; case FORMAT::PS3: return get()->d->PS3_FILE_ID; + case FORMAT::PS4: return get()->d->PS4_FILE_ID; + case FORMAT::PS4BIN: return get()->d->PS4_BINFILE_FILE_ID; // case FORMAT::DEX: return get()->d->DEX_FILE_ID; case FORMAT::VGS: return get()->d->VGS_FILE_ID; case FORMAT::SWITCH: return get()->d->SWITCH_FILE_ID; @@ -127,6 +136,7 @@ QByteArray FF7SaveInfo::fileHeader(FF7SaveInfo::FORMAT format) case FORMAT::VMC: return QByteArray(fileIdentifier(format)).append(fileHeaderSize(format) - fileIdentifier(format).length(), 0x00); case FORMAT::PSP: return get()->d->PSP_FILE_HEADER; case FORMAT::PS3: return get()->d->PS3_FILE_HEADER; + case FORMAT::PS4: return get()->d->PS4_FILE_HEADER; default: return QByteArray(); } } @@ -140,6 +150,7 @@ QByteArray FF7SaveInfo::slotHeader(FF7SaveInfo::FORMAT format, int slot) case FORMAT::PSX: case FORMAT::PSP: case FORMAT::PS3: + case FORMAT::PS4: case FORMAT::DEX: case FORMAT::VGS: case FORMAT::VMC: return QByteArray(get()->d->PSX_SLOT_HEADER.at(slot)).append(256, 0x00); @@ -155,6 +166,7 @@ QByteArray FF7SaveInfo::slotFooter(FF7SaveInfo::FORMAT format) case FORMAT::PSX: case FORMAT::PSP: case FORMAT::PS3: + case FORMAT::PS4: case FORMAT::DEX: case FORMAT::VGS: case FORMAT::VMC: return QByteArray(get()->d->PSX_SLOT_FOOTER_SIZE, 0x00); @@ -167,6 +179,7 @@ QByteArray FF7SaveInfo::signingKey(FF7SaveInfo::FORMAT format) switch (format) { case FORMAT::PSP: case FORMAT::PS3: return get()->d->PS_SIGNING_KEY; + case FORMAT::PS4: return get()->d->PS4_SIGNING_KEY; default: return QByteArray(); } } @@ -187,6 +200,7 @@ QByteArray FF7SaveInfo::signingIV(FF7SaveInfo::FORMAT format) switch (format) { case FORMAT::PSP: case FORMAT::PS3: return get()->d->PS_SIGNING_IV; + case FORMAT::PS4: return get()->d->PS4_SIGNING_IV; default: return QByteArray(); } } @@ -196,6 +210,7 @@ int FF7SaveInfo::fileSeedOffset(FF7SaveInfo::FORMAT format) switch (format) { case FORMAT::PSP: return get()->d->PSP_SEED_OFFSET; case FORMAT::PS3: return get()->d->PS3_SEED_OFFSET; + case FORMAT::PS4: return get()->d->PS4_SEED_OFFSET; default: return -1; } } @@ -205,6 +220,7 @@ int FF7SaveInfo::fileSignatureOffset(FF7SaveInfo::FORMAT format) switch (format) { case FORMAT::PSP: return get()->d->PSP_SIGNATURE_OFFSET; case FORMAT::PS3: return get()->d->PS3_SIGNATURE_OFFSET; + case FORMAT::PS4: return get()->d->PS4_SIGNATURE_OFFSET; default: return -1; } } @@ -214,6 +230,7 @@ int FF7SaveInfo::fileSignatureSize(FF7SaveInfo::FORMAT format) switch (format) { case FORMAT::PSP: case FORMAT::PS3: return get()->d->PS_SIGNATURE_SIZE; + case FORMAT::PS4: return get()->d->PS4_SIGNATURE_SIZE; default: return 0; } } @@ -230,6 +247,7 @@ QRegularExpression FF7SaveInfo::validNames(FF7SaveInfo::FORMAT format) case FORMAT::PSX: return get()->d->PSX_VALID_NAME_REGEX; case FORMAT::PSP: return get()->d->PSP_VALID_NAME_REGEX; case FORMAT::PS3: return get()->d->PS3_VALID_NAME_REGEX; + case FORMAT::PS4: return get()->d->PS4_VALID_NAME_REGEX; case FORMAT::DEX: return get()->d->DEX_VALID_NAME_REGEX; case FORMAT::VGS: return get()->d->VGS_VALID_NAME_REGEX; case FORMAT::VMC: return get()->d->VMC_VALID_NAME_REGEX; @@ -247,6 +265,7 @@ QString FF7SaveInfo::typeDescription(FF7SaveInfo::FORMAT format) case FORMAT::PSX: return tr(get()->d->PSX_FILE_DESCRIPTION.toUtf8()); case FORMAT::PSP: return tr(get()->d->PSP_FILE_DESCRIPTION.toUtf8()); case FORMAT::PS3: return tr(get()->d->PS3_FILE_DESCRIPTION.toUtf8()); + case FORMAT::PS4: return tr(get()->d->PS4_FILE_DESCRIPTION.toUtf8()); case FORMAT::DEX: return tr(get()->d->DEX_FILE_DESCRIPTION.toUtf8()); case FORMAT::VGS: return tr(get()->d->VGS_FILE_DESCRIPTION.toUtf8()); case FORMAT::VMC: return tr(get()->d->VMC_FILE_DESCRIPTION.toUtf8()); @@ -264,6 +283,7 @@ QStringList FF7SaveInfo::typeExtension(FF7SaveInfo::FORMAT format) case FORMAT::PSX: return get()->d->PSX_VALID_EXTENSIONS; case FORMAT::PSP: return get()->d->PSP_VALID_EXTENSIONS; case FORMAT::PS3: return get()->d->PS3_VALID_EXTENSIONS; + case FORMAT::PS4: return get()->d->PS4_VALID_EXTENSIONS; case FORMAT::DEX: return get()->d->DEX_VALID_EXTENSIONS; case FORMAT::VGS: return get()->d->VGS_VALID_EXTENSIONS; case FORMAT::VMC: return get()->d->VMC_VALID_EXTENSIONS; @@ -284,11 +304,12 @@ QString FF7SaveInfo::typeFilter(FF7SaveInfo::FORMAT format) QString FF7SaveInfo::knownTypesFilter() { QString space = QStringLiteral(" "); - QString allTypes = QStringLiteral("%1 %2 %3 %4 %5 %6 %7 %8 %9 %10") + QString allTypes = QStringLiteral("%1 %2 %3 %4 %5 %6 %7 %8 %9 %10 %11") .arg(get()->d->PC_VALID_EXTENSIONS.join(space) , get()->d->PSX_VALID_EXTENSIONS.join(space) , get()->d->PSP_VALID_EXTENSIONS.join(space) , get()->d->PS3_VALID_EXTENSIONS.join(space) + , get()->d->PS4_VALID_EXTENSIONS.join(space) , get()->d->DEX_VALID_EXTENSIONS.join(space) , get()->d->VGS_VALID_EXTENSIONS.join(space) , get()->d->VMC_VALID_EXTENSIONS.join(space) @@ -296,13 +317,14 @@ QString FF7SaveInfo::knownTypesFilter() , get()->d->PGE_VALID_EXTENSIONS.join(space) , get()->d->PDA_VALID_EXTENSIONS.join(space)); - return QStringLiteral("%1;;%2;;%3;;%4;;%5;;%6;;%7;;%8;;%9;;%10;;%11;;%12") + return QStringLiteral("%1;;%2;;%3;;%4;;%5;;%6;;%7;;%8;;%9;;%10;;%11;;%12;;%13") .arg(tr("Known FF7 Save Types (%1)").arg(allTypes) , typeFilter(FORMAT::PC) , typeFilter(FORMAT::SWITCH) , typeFilter(FORMAT::VMC) , typeFilter(FORMAT::PSX) , typeFilter(FORMAT::PS3) + , typeFilter(FORMAT::PS4) , typeFilter(FORMAT::PSP) , typeFilter(FORMAT::DEX) , typeFilter(FORMAT::VGS) @@ -315,6 +337,7 @@ bool FF7SaveInfo::isTypePC(FF7SaveInfo::FORMAT format) { switch(format) { case FORMAT::SWITCH: + case FORMAT::PS4: case FORMAT::PC: return true; default: return false; }; @@ -368,4 +391,3 @@ QByteArray FF7SaveInfo::defaultSaveData() { return get()->d->DEFAULT_SAVE; } - diff --git a/src/data/FF7SaveInfo.h b/src/data/FF7SaveInfo.h index af5da6909..c730210a1 100644 --- a/src/data/FF7SaveInfo.h +++ b/src/data/FF7SaveInfo.h @@ -3,6 +3,17 @@ #pragma once +#ifdef _MSC_VER +# define PACK(structure) \ + __pragma(pack(push, 1)) \ + structure \ + __pragma(pack(pop)) +#elif defined(__MINGW32__) +#define PACK(structure) structure __attribute__ ((gcc_struct, __packed__)) +#else +#define PACK(structure) structure Q_PACKED +#endif + #include #include #if QT_VERSION < QT_VERSION_CHECK(6, 5, 0) @@ -36,6 +47,8 @@ class FF7TKDATA_EXPORT FF7SaveInfo : public QObject SWITCH = 7, //!< Switch Format PGE = 8, //!< Psx Game Edit Style Single Save PDA = 9, //!< GS , Dantel Style Save + PS4 = 10, //!< PS4 Save Format + PS4BIN = 11 //!< PS4 BIN File (pfsSKKey) }; Q_ENUM(FORMAT) @@ -63,6 +76,21 @@ class FF7TKDATA_EXPORT FF7SaveInfo : public QObject }; Q_ENUM(PSVINFO) + /** + * \struct pfsSKKey + * \brief Extra bin file (Sealedkey) used for PS4 format + */ +PACK( + struct pfsSKKey { + quint8 MAGIC[8]; /**< [0x0000] MAGIC (0x08) */ + quint16 KEYSET; /**< [0x0008] KEYSET (0x02) */ + quint8 pad[6]; /**< [0x000A] Padding zeros (0x06) */ + quint8 IV[16]; /**< [0x0010] AES IV (0x10) */ + quint8 KEY[32]; /**< [0x0020] AES KEY (0x20) */ + quint8 SHA256[32]; /**< [0x0040] AES SHA256 (0x20) */ + } +); + /** * @brief Get the FF7SaveInfo Instance. */ @@ -325,6 +353,28 @@ class FF7TKDATA_EXPORT FF7SaveInfo : public QObject inline static const int PS_SIGNATURE_SIZE = 0x0014; inline static const QByteArray PS_SIGNING_KEY= QByteArray::fromRawData("\xAB\x5A\xBC\x9F\xC1\xF4\x9D\xE6\xA0\x51\xDB\xAE\xFA\x51\x88\x59", 0x10); inline static const QByteArray PS_SIGNING_IV= QByteArray::fromRawData("\xB3\x0F\xFE\xED\xB7\xDC\x5E\xB7\x13\x3D\xA6\x0D\x1B\x6B\x2C\xDC", 0x10); + /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~PS4 SAVE INFO~~~~~~~~~~~~~~~~~~~~~~~~~*/ + static const int PS4_FILE_SIZE = 0xA00000; + static const int PS4_FILE_HEADER_SIZE = 0x00AF; + inline static const QString PS4_FILE_DESCRIPTION = QT_TR_NOOP("PS4 Save File"); + inline static const QStringList PS4_VALID_EXTENSIONS { QStringLiteral("*.ff7") }; + inline static const QRegularExpression PS4_VALID_NAME_REGEX = QRegularExpression(QStringLiteral("save0[0-9].ff7")); + inline static const QByteArray PS4_FILE_ID = QByteArray::fromRawData("\x17\x00\x00\x00\x00\x00\x00\x00", 8); + inline static const QByteArray PS4_BINFILE_FILE_ID = QByteArray::fromRawData("\x70\x66\x73\x53\x4B\x4B\x65\x79", 8); + static const int PS4_BINFILE_FILE_ID_SIZE = 8; + static const int PS4_BINFILE_SIZE = 0x60; + static const int PS4_BINFILE_IV_OFFSET= 0x10; + static const int PS4_BINFILE_IV_SIZE= 0x10; + static const int PS4_SEED_OFFSET = 0x0008; + static const int PS4_SIGNATURE_OFFSET = 0x001C; + static const int PS4_FILE_TYPE_OFFSET = 0x0038; + static const int PS4_FILE_DISP_SIZE_OFFSET = 0x0040; + static const int PS4_FILE_SIZE_OFFSET = 0x005C; + inline static const QByteArray PS4_FILE_HEADER = QByteArray::fromRawData("\x00\x56\x53\x50\x00\x00\x00\x00\x04\xbc\x97\x58\x11\x0f\x7e\x85\xc7\x4f\x2f\xd0\x5a\x28\xb6\x25\xe6\x9a\x6e\xa1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x01\x00\x00\x00\x00\x20\x00\x00\x84\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x03\x90\x00\x00\x42\x41\x53\x43\x55\x53\x2d\x39\x34\x31\x36\x33\x46\x46\x37\x2d\x53\x30\x31\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00", PS3_FILE_HEADER_SIZE); + /*~~~~~~~ PS4 Signing ~~~~~~~~~~~~~~*/ + static const int PS4_SIGNATURE_SIZE = 0x0014; + inline static const QByteArray PS4_SIGNING_KEY= QByteArray::fromRawData("\xAB\x5A\xBC\x9F\xC1\xF4\x9D\xE6\xA0\x51\xDB\xAE\xFA\x51\x88\x59", 0x10); + inline static const QByteArray PS4_SIGNING_IV= QByteArray::fromRawData("\xB3\x0F\xFE\xED\xB7\xDC\x5E\xB7\x13\x3D\xA6\x0D\x1B\x6B\x2C\xDC", 0x10); /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~Mem Card Format~~~~~~~~~~~~~~~~~~~*/ inline static const int VMC_FILE_SIZE = 0x20000; inline static const int VMC_FILE_HEADER_SIZE = 0x2000; diff --git a/src/data/crypto/aes.c b/src/data/crypto/aes.c index c0899d7cc..e04df16b1 100644 --- a/src/data/crypto/aes.c +++ b/src/data/crypto/aes.c @@ -326,3 +326,33 @@ void XorWithByte(uint8_t* buf, uint8_t byte, int length) buf[i] ^= byte; } } + +void AES_CBC_encrypt_buffer(struct AES_ctx *ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t *Iv = ctx->Iv; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + XorWithIv(buf, Iv); + Cipher((state_t*)buf, ctx->RoundKey); + Iv = buf; + buf += AES_BLOCKLEN; + } + /* store Iv in ctx for next call */ + memcpy(ctx->Iv, Iv, AES_BLOCKLEN); +} + +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) +{ + size_t i; + uint8_t storeNextIv[AES_BLOCKLEN]; + for (i = 0; i < length; i += AES_BLOCKLEN) + { + memcpy(storeNextIv, buf, AES_BLOCKLEN); + InvCipher((state_t*)buf, ctx->RoundKey); + XorWithIv(buf, ctx->Iv); + memcpy(ctx->Iv, storeNextIv, AES_BLOCKLEN); + buf += AES_BLOCKLEN; + } + +} diff --git a/src/data/crypto/aes.h b/src/data/crypto/aes.h index 3ff946c73..575eb94b8 100644 --- a/src/data/crypto/aes.h +++ b/src/data/crypto/aes.h @@ -25,6 +25,8 @@ struct AES_ctx }; void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +void AES_init_ctx_iv(struct AES_ctx* ctx, const uint8_t* key, const uint8_t* iv); +void AES_ctx_set_iv(struct AES_ctx* ctx, const uint8_t* iv); void XorWithIv(uint8_t* buf, const uint8_t* Iv); void XorWithByte(uint8_t* buf, uint8_t byte, int length); @@ -34,6 +36,13 @@ void XorWithByte(uint8_t* buf, uint8_t byte, int length); void AES_ECB_encrypt(const struct AES_ctx* ctx, uint8_t* buf); void AES_ECB_decrypt(const struct AES_ctx* ctx, uint8_t* buf); +// buffer size MUST be mutile of AES_BLOCKLEN; +// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme +// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv() +// no IV should ever be reused with the same key +void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); +void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length); + #ifdef __cplusplus } #endif diff --git a/translations/ff7tk_de.ts b/translations/ff7tk_de.ts index 1635cbc81..84db92d94 100644 --- a/translations/ff7tk_de.ts +++ b/translations/ff7tk_de.ts @@ -6943,6 +6943,10 @@ Die km / h beschleunigt berechnet werden während des Spielens XP AR GS Caetla SmartLink Dantel XP AR GS Caetla SmartLink Dantel + + PS4 Save File + PS4 Spielstand + ItemPreview diff --git a/translations/ff7tk_en.ts b/translations/ff7tk_en.ts index 85540b3f0..ee481dc22 100644 --- a/translations/ff7tk_en.ts +++ b/translations/ff7tk_en.ts @@ -6943,6 +6943,10 @@ The km/h speeds are calculated while playing XP AR GS Caetla SmartLink Dantel XP AR GS Caetla SmartLink Dantel + + PS4 Save File + PS4 Save File + ItemPreview diff --git a/translations/ff7tk_es.ts b/translations/ff7tk_es.ts index c85999afd..73d598094 100644 --- a/translations/ff7tk_es.ts +++ b/translations/ff7tk_es.ts @@ -6943,6 +6943,10 @@ Los km/h son calculados mientras se juega XP AR GS Caetla SmartLink Dantel XP AR GS Caetla SmartLink Dantel + + PS4 Save File + Partida Guardada PS4 + ItemPreview diff --git a/translations/ff7tk_fr.ts b/translations/ff7tk_fr.ts index 44db710d8..99ec81202 100644 --- a/translations/ff7tk_fr.ts +++ b/translations/ff7tk_fr.ts @@ -6943,6 +6943,10 @@ Les vitesses en km/h sont calculés pendant le jeu XP AR GS Caetla SmartLink Dantel XP AR GS Caetla SmartLink Dantel + + PS4 Save File + Sauvegarde PS4 + ItemPreview diff --git a/translations/ff7tk_it.ts b/translations/ff7tk_it.ts index 285f23859..f202f2ae0 100644 --- a/translations/ff7tk_it.ts +++ b/translations/ff7tk_it.ts @@ -6954,6 +6954,10 @@ Battaglia %1 Known FF7 Save Types (%1) File di salvataggio FF7 noti (%1) + + PS4 Save File + File di salvataggio PS4 + ItemPreview diff --git a/translations/ff7tk_ja.ts b/translations/ff7tk_ja.ts index 4440a6d4e..9a70bc785 100644 --- a/translations/ff7tk_ja.ts +++ b/translations/ff7tk_ja.ts @@ -6943,6 +6943,10 @@ The km/h speeds are calculated while playing XP AR GS Caetla SmartLink Dantel XP AR GS Caetla SmartLink Dantel + + PS4 Save File + PS4 セーブ + ItemPreview diff --git a/translations/ff7tk_pl.ts b/translations/ff7tk_pl.ts index b90b6f32f..5284b31bf 100644 --- a/translations/ff7tk_pl.ts +++ b/translations/ff7tk_pl.ts @@ -6942,6 +6942,10 @@ The km/h speeds are calculated while playing Known FF7 Save Types (%1) Znane Typy Plików FF7 (%1) + + PS4 Save File + Zapisz Plik PS4 + ItemPreview diff --git a/translations/ff7tk_re.ts b/translations/ff7tk_re.ts index 914d426b0..0867158cf 100644 --- a/translations/ff7tk_re.ts +++ b/translations/ff7tk_re.ts @@ -6943,6 +6943,10 @@ The km/h speeds are calculated while playing Known FF7 Save Types (%1) Known FF7 Save Types (%1) + + PS4 Save File + PS4 Save File + ItemPreview