566 lines
21 KiB
C++
566 lines
21 KiB
C++
|
|
#include "recompiler.h"
|
||
|
|
#include <QBuffer>
|
||
|
|
#include <QtEndian>
|
||
|
|
#include <stdexcept>
|
||
|
|
#include <cstring>
|
||
|
|
|
||
|
|
Recompiler::Recompiler(QObject* parent)
|
||
|
|
: QObject(parent)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
bool Recompiler::canRecompile(int journalId) const {
|
||
|
|
const OperationJournal* journal = JournalManager::instance().getJournal(journalId);
|
||
|
|
if (!journal) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return journal->isExportable();
|
||
|
|
}
|
||
|
|
|
||
|
|
QString Recompiler::whyNotRecompilable(int journalId) const {
|
||
|
|
const OperationJournal* journal = JournalManager::instance().getJournal(journalId);
|
||
|
|
if (!journal) {
|
||
|
|
return "Journal not found";
|
||
|
|
}
|
||
|
|
return journal->whyNotExportable();
|
||
|
|
}
|
||
|
|
|
||
|
|
QByteArray Recompiler::recompile(int journalId, const QVariantMap& values) {
|
||
|
|
QStringList errors;
|
||
|
|
QByteArray result = recompileWithErrors(journalId, values, errors);
|
||
|
|
if (!errors.isEmpty()) {
|
||
|
|
throw std::runtime_error(errors.join("; ").toStdString());
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
QByteArray Recompiler::recompileWithErrors(int journalId, const QVariantMap& values, QStringList& outErrors) {
|
||
|
|
outErrors.clear();
|
||
|
|
m_visitedJournals.clear(); // Clear recursion tracking
|
||
|
|
|
||
|
|
OperationJournal* journal = JournalManager::instance().getJournal(journalId);
|
||
|
|
if (!journal) {
|
||
|
|
outErrors << "Journal not found";
|
||
|
|
return QByteArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!journal->isExportable()) {
|
||
|
|
outErrors << journal->whyNotExportable();
|
||
|
|
return QByteArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
emit progressChanged(0, "Starting recompilation");
|
||
|
|
|
||
|
|
// Create output buffer
|
||
|
|
QByteArray output;
|
||
|
|
QBuffer buffer(&output);
|
||
|
|
buffer.open(QIODevice::WriteOnly);
|
||
|
|
QDataStream out(&buffer);
|
||
|
|
|
||
|
|
const int totalEntries = journal->entries.size();
|
||
|
|
int processedEntries = 0;
|
||
|
|
|
||
|
|
// Walk journal entries in order and write each one
|
||
|
|
for (const JournalEntry& entry : journal->entries) {
|
||
|
|
try {
|
||
|
|
switch (entry.type) {
|
||
|
|
case OpType::ReadScalar: {
|
||
|
|
QVariant value = getValueForField(values, entry);
|
||
|
|
writeScalar(out, entry.scalarType, entry.byteOrder, value);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::ReadBytes: {
|
||
|
|
// For raw bytes, check if we have modified bytes in values
|
||
|
|
QVariant value = getValueForField(values, entry);
|
||
|
|
if (value.typeId() == QMetaType::QByteArray) {
|
||
|
|
writeBytes(out, value.toByteArray());
|
||
|
|
} else if (!entry.originalBytes.isEmpty()) {
|
||
|
|
writeBytes(out, entry.originalBytes);
|
||
|
|
} else {
|
||
|
|
// Write zeros if we have no data
|
||
|
|
QByteArray zeros(entry.fieldSize, '\0');
|
||
|
|
writeBytes(out, zeros);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::ReadCString: {
|
||
|
|
QVariant value = getValueForField(values, entry);
|
||
|
|
QString str = value.toString();
|
||
|
|
writeCString(out, str);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::ReadWString: {
|
||
|
|
QVariant value = getValueForField(values, entry);
|
||
|
|
QString str = value.toString();
|
||
|
|
writeWString(out, str, entry.byteOrder);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::Skip: {
|
||
|
|
// Write original padding bytes if available, otherwise zeros
|
||
|
|
writeSkip(out, entry.fieldSize, entry.originalBytes);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::Align: {
|
||
|
|
// Write original alignment padding if available, otherwise zeros
|
||
|
|
writeSkip(out, entry.fieldSize, entry.originalBytes);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::Seek: {
|
||
|
|
// Seek operations are tricky - we need to pad to the target position
|
||
|
|
// The target position is stored in fieldSize
|
||
|
|
qint64 currentPos = buffer.pos();
|
||
|
|
qint64 targetPos = entry.fieldSize;
|
||
|
|
if (targetPos > currentPos) {
|
||
|
|
// Need to pad to reach target position
|
||
|
|
qint64 padding = targetPos - currentPos;
|
||
|
|
QByteArray zeros(padding, '\0');
|
||
|
|
writeBytes(out, zeros);
|
||
|
|
} else if (targetPos < currentPos) {
|
||
|
|
// Seeking backwards - this is problematic for linear recompilation
|
||
|
|
// For now, we'll log a warning but continue
|
||
|
|
outErrors << QString("Warning: Backward seek from %1 to %2 in recompilation")
|
||
|
|
.arg(currentPos).arg(targetPos);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::Computed: {
|
||
|
|
// Computed values don't directly contribute bytes to output
|
||
|
|
// They are derived from other fields
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::Decompress: {
|
||
|
|
// Compression write-back: need to recompress nested content
|
||
|
|
// The nested journal contains the decompressed content that may have been modified
|
||
|
|
if (entry.nestedJournalId >= 0) {
|
||
|
|
// Check if compression type is supported
|
||
|
|
if (!isCompressionSupported(entry.compressionType)) {
|
||
|
|
outErrors << QString("Unsupported compression type for write-back: %1")
|
||
|
|
.arg(entry.compressionType);
|
||
|
|
// Fall back to original compressed data
|
||
|
|
if (!entry.originalBytes.isEmpty()) {
|
||
|
|
writeBytes(out, entry.originalBytes);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Recompile the nested (decompressed) content
|
||
|
|
QByteArray uncompressedData = recompileNested(entry.nestedJournalId, values,
|
||
|
|
entry.fieldName, outErrors);
|
||
|
|
if (uncompressedData.isEmpty()) {
|
||
|
|
// Fallback to original compressed bytes
|
||
|
|
if (!entry.originalBytes.isEmpty()) {
|
||
|
|
writeBytes(out, entry.originalBytes);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Recompress the data
|
||
|
|
QByteArray compressedData = compress(uncompressedData, entry.compressionType,
|
||
|
|
entry.compressionParams);
|
||
|
|
if (compressedData.isEmpty()) {
|
||
|
|
outErrors << QString("Failed to recompress data with %1")
|
||
|
|
.arg(entry.compressionType);
|
||
|
|
// Fall back to original
|
||
|
|
if (!entry.originalBytes.isEmpty()) {
|
||
|
|
writeBytes(out, entry.originalBytes);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
writeBytes(out, compressedData);
|
||
|
|
} else {
|
||
|
|
// No nested journal - just use original bytes
|
||
|
|
if (!entry.originalBytes.isEmpty()) {
|
||
|
|
writeBytes(out, entry.originalBytes);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::ParseNested:
|
||
|
|
case OpType::ParseHere: {
|
||
|
|
// Nested parsing - recursive recompilation
|
||
|
|
if (entry.nestedJournalId >= 0) {
|
||
|
|
QByteArray nestedBytes = recompileNested(entry.nestedJournalId, values,
|
||
|
|
entry.fieldName, outErrors);
|
||
|
|
if (!nestedBytes.isEmpty()) {
|
||
|
|
writeBytes(out, nestedBytes);
|
||
|
|
} else {
|
||
|
|
// Fallback to original bytes if recompilation failed
|
||
|
|
OperationJournal* nestedJournal = JournalManager::instance().getJournal(entry.nestedJournalId);
|
||
|
|
if (nestedJournal && !nestedJournal->originalBytes.isEmpty()) {
|
||
|
|
writeBytes(out, nestedJournal->originalBytes);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::RandomAccess: {
|
||
|
|
// Random access entries don't contribute to output
|
||
|
|
// (and shouldn't exist in exportable journals)
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (const std::exception& e) {
|
||
|
|
outErrors << QString("Error processing entry '%1': %2")
|
||
|
|
.arg(entry.fieldName).arg(e.what());
|
||
|
|
}
|
||
|
|
|
||
|
|
processedEntries++;
|
||
|
|
int percent = (processedEntries * 100) / qMax(1, totalEntries);
|
||
|
|
emit progressChanged(percent, QString("Processing %1").arg(entry.fieldName));
|
||
|
|
}
|
||
|
|
|
||
|
|
buffer.close();
|
||
|
|
emit progressChanged(100, "Recompilation complete");
|
||
|
|
|
||
|
|
return output;
|
||
|
|
}
|
||
|
|
|
||
|
|
QVariant Recompiler::getValueForField(const QVariantMap& values, const JournalEntry& entry) const {
|
||
|
|
// First check if we have a modified value in the values map
|
||
|
|
if (!entry.fieldName.isEmpty() && values.contains(entry.fieldName)) {
|
||
|
|
return values.value(entry.fieldName);
|
||
|
|
}
|
||
|
|
// Fall back to original value from journal
|
||
|
|
return entry.originalValue;
|
||
|
|
}
|
||
|
|
|
||
|
|
void Recompiler::writeScalar(QDataStream& out, ScalarType type, ByteOrder order, const QVariant& value) {
|
||
|
|
// Set byte order for this write
|
||
|
|
out.setByteOrder(order == ByteOrder::LE ? QDataStream::LittleEndian : QDataStream::BigEndian);
|
||
|
|
|
||
|
|
switch (type) {
|
||
|
|
case ScalarType::U8: {
|
||
|
|
quint8 v = static_cast<quint8>(value.toUInt());
|
||
|
|
out << v;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case ScalarType::I8: {
|
||
|
|
qint8 v = static_cast<qint8>(value.toInt());
|
||
|
|
out << v;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case ScalarType::U16: {
|
||
|
|
quint16 v = static_cast<quint16>(value.toUInt());
|
||
|
|
out << v;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case ScalarType::I16: {
|
||
|
|
qint16 v = static_cast<qint16>(value.toInt());
|
||
|
|
out << v;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case ScalarType::U32: {
|
||
|
|
quint32 v = static_cast<quint32>(value.toUInt());
|
||
|
|
out << v;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case ScalarType::I32: {
|
||
|
|
qint32 v = static_cast<qint32>(value.toInt());
|
||
|
|
out << v;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case ScalarType::U64: {
|
||
|
|
quint64 v = static_cast<quint64>(value.toULongLong());
|
||
|
|
out << v;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case ScalarType::I64: {
|
||
|
|
qint64 v = static_cast<qint64>(value.toLongLong());
|
||
|
|
out << v;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case ScalarType::F32: {
|
||
|
|
float f = value.toFloat();
|
||
|
|
quint32 bits;
|
||
|
|
memcpy(&bits, &f, 4);
|
||
|
|
out << bits;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case ScalarType::F64: {
|
||
|
|
double d = value.toDouble();
|
||
|
|
quint64 bits;
|
||
|
|
memcpy(&bits, &d, 8);
|
||
|
|
out << bits;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
case ScalarType::Bool: {
|
||
|
|
quint8 v = value.toBool() ? 1 : 0;
|
||
|
|
out << v;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
void Recompiler::writeBytes(QDataStream& out, const QByteArray& data) {
|
||
|
|
out.writeRawData(data.constData(), data.size());
|
||
|
|
}
|
||
|
|
|
||
|
|
void Recompiler::writeCString(QDataStream& out, const QString& str) {
|
||
|
|
QByteArray utf8 = str.toUtf8();
|
||
|
|
out.writeRawData(utf8.constData(), utf8.size());
|
||
|
|
// Write null terminator
|
||
|
|
char null = '\0';
|
||
|
|
out.writeRawData(&null, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
void Recompiler::writeWString(QDataStream& out, const QString& str, ByteOrder order) {
|
||
|
|
// Write each character as UTF-16
|
||
|
|
for (const QChar& ch : str) {
|
||
|
|
quint16 code = ch.unicode();
|
||
|
|
if (order == ByteOrder::LE) {
|
||
|
|
code = qToLittleEndian(code);
|
||
|
|
} else {
|
||
|
|
code = qToBigEndian(code);
|
||
|
|
}
|
||
|
|
out.writeRawData(reinterpret_cast<const char*>(&code), 2);
|
||
|
|
}
|
||
|
|
// Write null terminator (2 bytes)
|
||
|
|
quint16 null = 0;
|
||
|
|
out.writeRawData(reinterpret_cast<const char*>(&null), 2);
|
||
|
|
}
|
||
|
|
|
||
|
|
void Recompiler::writeSkip(QDataStream& out, qint64 count, const QByteArray& originalBytes) {
|
||
|
|
if (!originalBytes.isEmpty() && originalBytes.size() >= count) {
|
||
|
|
// Use original padding bytes
|
||
|
|
out.writeRawData(originalBytes.constData(), count);
|
||
|
|
} else {
|
||
|
|
// Write zeros
|
||
|
|
QByteArray zeros(count, '\0');
|
||
|
|
out.writeRawData(zeros.constData(), count);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
QByteArray Recompiler::recompileNested(int nestedJournalId, const QVariantMap& parentValues,
|
||
|
|
const QString& fieldName, QStringList& outErrors) {
|
||
|
|
// Check for circular reference
|
||
|
|
if (m_visitedJournals.contains(nestedJournalId)) {
|
||
|
|
outErrors << QString("Circular reference detected in nested journal %1").arg(nestedJournalId);
|
||
|
|
return QByteArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
OperationJournal* nestedJournal = JournalManager::instance().getJournal(nestedJournalId);
|
||
|
|
if (!nestedJournal) {
|
||
|
|
outErrors << QString("Nested journal %1 not found").arg(nestedJournalId);
|
||
|
|
return QByteArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check exportability
|
||
|
|
if (!nestedJournal->isExportable()) {
|
||
|
|
// Non-exportable nested structure - return original bytes
|
||
|
|
return nestedJournal->originalBytes;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Mark as visited
|
||
|
|
m_visitedJournals.insert(nestedJournalId);
|
||
|
|
|
||
|
|
// Extract values for the nested structure
|
||
|
|
QVariantMap nestedValues = extractNestedValues(parentValues, fieldName, nestedJournal);
|
||
|
|
|
||
|
|
// Create output buffer for nested structure
|
||
|
|
QByteArray output;
|
||
|
|
QBuffer buffer(&output);
|
||
|
|
buffer.open(QIODevice::WriteOnly);
|
||
|
|
QDataStream out(&buffer);
|
||
|
|
|
||
|
|
// Walk nested journal entries
|
||
|
|
for (const JournalEntry& entry : nestedJournal->entries) {
|
||
|
|
try {
|
||
|
|
switch (entry.type) {
|
||
|
|
case OpType::ReadScalar: {
|
||
|
|
QVariant value = getValueForField(nestedValues, entry);
|
||
|
|
writeScalar(out, entry.scalarType, entry.byteOrder, value);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::ReadBytes: {
|
||
|
|
QVariant value = getValueForField(nestedValues, entry);
|
||
|
|
if (value.typeId() == QMetaType::QByteArray) {
|
||
|
|
writeBytes(out, value.toByteArray());
|
||
|
|
} else if (!entry.originalBytes.isEmpty()) {
|
||
|
|
writeBytes(out, entry.originalBytes);
|
||
|
|
} else {
|
||
|
|
QByteArray zeros(entry.fieldSize, '\0');
|
||
|
|
writeBytes(out, zeros);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::ReadCString: {
|
||
|
|
QVariant value = getValueForField(nestedValues, entry);
|
||
|
|
writeCString(out, value.toString());
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::ReadWString: {
|
||
|
|
QVariant value = getValueForField(nestedValues, entry);
|
||
|
|
writeWString(out, value.toString(), entry.byteOrder);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::Skip:
|
||
|
|
case OpType::Align: {
|
||
|
|
writeSkip(out, entry.fieldSize, entry.originalBytes);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::Seek: {
|
||
|
|
qint64 currentPos = buffer.pos();
|
||
|
|
qint64 targetPos = entry.fieldSize;
|
||
|
|
if (targetPos > currentPos) {
|
||
|
|
qint64 padding = targetPos - currentPos;
|
||
|
|
QByteArray zeros(padding, '\0');
|
||
|
|
writeBytes(out, zeros);
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::Computed:
|
||
|
|
// Computed values don't contribute bytes
|
||
|
|
break;
|
||
|
|
|
||
|
|
case OpType::Decompress:
|
||
|
|
// Compression handled at outer level
|
||
|
|
break;
|
||
|
|
|
||
|
|
case OpType::ParseNested:
|
||
|
|
case OpType::ParseHere: {
|
||
|
|
// Recursively recompile deeper nested structures
|
||
|
|
if (entry.nestedJournalId >= 0) {
|
||
|
|
QByteArray deepNestedBytes = recompileNested(entry.nestedJournalId, nestedValues,
|
||
|
|
entry.fieldName, outErrors);
|
||
|
|
if (!deepNestedBytes.isEmpty()) {
|
||
|
|
writeBytes(out, deepNestedBytes);
|
||
|
|
} else {
|
||
|
|
OperationJournal* deepJournal = JournalManager::instance().getJournal(entry.nestedJournalId);
|
||
|
|
if (deepJournal && !deepJournal->originalBytes.isEmpty()) {
|
||
|
|
writeBytes(out, deepJournal->originalBytes);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case OpType::RandomAccess:
|
||
|
|
// Should not be in exportable journal
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
} catch (const std::exception& e) {
|
||
|
|
outErrors << QString("Error in nested '%1.%2': %3")
|
||
|
|
.arg(fieldName).arg(entry.fieldName).arg(e.what());
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
buffer.close();
|
||
|
|
|
||
|
|
// Remove from visited (allow reuse in other branches)
|
||
|
|
m_visitedJournals.remove(nestedJournalId);
|
||
|
|
|
||
|
|
return output;
|
||
|
|
}
|
||
|
|
|
||
|
|
QVariantMap Recompiler::extractNestedValues(const QVariantMap& parentValues, const QString& fieldName,
|
||
|
|
const OperationJournal* nestedJournal) const {
|
||
|
|
QVariantMap nestedValues;
|
||
|
|
|
||
|
|
// First, try to get nested values from parent values map
|
||
|
|
// Values may be stored as a nested QVariantMap under the field name
|
||
|
|
if (!fieldName.isEmpty() && parentValues.contains(fieldName)) {
|
||
|
|
QVariant fieldValue = parentValues.value(fieldName);
|
||
|
|
if (fieldValue.typeId() == QMetaType::QVariantMap) {
|
||
|
|
nestedValues = fieldValue.toMap();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Also check for dotted keys like "nested.fieldname"
|
||
|
|
QString prefix = fieldName + ".";
|
||
|
|
for (auto it = parentValues.begin(); it != parentValues.end(); ++it) {
|
||
|
|
if (it.key().startsWith(prefix)) {
|
||
|
|
QString subKey = it.key().mid(prefix.length());
|
||
|
|
nestedValues[subKey] = it.value();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fill in missing values from journal's original values
|
||
|
|
if (nestedJournal) {
|
||
|
|
for (const JournalEntry& entry : nestedJournal->entries) {
|
||
|
|
if (!entry.fieldName.isEmpty() && !nestedValues.contains(entry.fieldName)) {
|
||
|
|
nestedValues[entry.fieldName] = entry.originalValue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return nestedValues;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool Recompiler::isCompressionSupported(const QString& compressionType) const {
|
||
|
|
// Supported compression types for write-back
|
||
|
|
static const QSet<QString> supported = {
|
||
|
|
"zlib", "zlib_auto", "deflate", "xmem", "oodle"
|
||
|
|
};
|
||
|
|
return supported.contains(compressionType.toLower());
|
||
|
|
}
|
||
|
|
|
||
|
|
QByteArray Recompiler::compress(const QByteArray& data, const QString& compressionType,
|
||
|
|
const QByteArray& params) const {
|
||
|
|
QString type = compressionType.toLower();
|
||
|
|
|
||
|
|
if (type == "zlib" || type == "zlib_auto") {
|
||
|
|
// Use settings from params if available
|
||
|
|
if (!params.isEmpty()) {
|
||
|
|
QDataStream paramsStream(params);
|
||
|
|
int level = Z_BEST_COMPRESSION;
|
||
|
|
int windowBits = MAX_WBITS;
|
||
|
|
int memLevel = 8;
|
||
|
|
int strategy = Z_DEFAULT_STRATEGY;
|
||
|
|
|
||
|
|
// Read parameters if available
|
||
|
|
if (params.size() >= 4) {
|
||
|
|
paramsStream >> level >> windowBits >> memLevel >> strategy;
|
||
|
|
}
|
||
|
|
|
||
|
|
return Compression::CompressZLIBWithSettings(data, level, windowBits, memLevel, strategy);
|
||
|
|
}
|
||
|
|
return Compression::CompressZLIB(data);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (type == "deflate") {
|
||
|
|
// Use settings from params if available
|
||
|
|
if (!params.isEmpty()) {
|
||
|
|
QDataStream paramsStream(params);
|
||
|
|
int level = Z_BEST_COMPRESSION;
|
||
|
|
int windowBits = -MAX_WBITS; // Raw deflate
|
||
|
|
int memLevel = 8;
|
||
|
|
int strategy = Z_DEFAULT_STRATEGY;
|
||
|
|
|
||
|
|
if (params.size() >= 4) {
|
||
|
|
paramsStream >> level >> windowBits >> memLevel >> strategy;
|
||
|
|
}
|
||
|
|
|
||
|
|
return Compression::CompressDeflateWithSettings(data, level, windowBits, memLevel, strategy);
|
||
|
|
}
|
||
|
|
return Compression::CompressDeflate(data);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (type == "xmem") {
|
||
|
|
return Compression::CompressXMem(data);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (type == "oodle") {
|
||
|
|
return Compression::CompressOodle(data);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Unsupported compression type
|
||
|
|
return QByteArray();
|
||
|
|
}
|