Introduce operation journaling to track how values are read from the binary stream, enabling future write-back of edited fields. New components: - OperationJournal: Records read operations with stream offsets, sizes, byte order, and original values during parsing - ReverseEval: Analyzes expressions for invertibility (e.g., x+5 can be reversed to compute the source field when editing the result) - Recompiler: Infrastructure for recompiling modified values back to binary format Interpreter enhancements: - Add runTypeInternalWithJournal() for journaled parsing - Journal scalar reads, byte reads, strings, skip/seek operations - Add deadrising_lzx() built-in for Dead Rising 2 LZX decompression - Support _deadrising_lzx_stem context variable for nested archives Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
158 lines
5.6 KiB
C++
158 lines
5.6 KiB
C++
#ifndef RECOMPILER_H
|
|
#define RECOMPILER_H
|
|
|
|
#include "operationjournal.h"
|
|
#include "../compression/compression.h"
|
|
#include <QObject>
|
|
#include <QByteArray>
|
|
#include <QVariantMap>
|
|
#include <QDataStream>
|
|
#include <QSet>
|
|
|
|
/**
|
|
* @brief The Recompiler class walks an operation journal and produces binary output.
|
|
*
|
|
* Given a journal (recorded during parsing) and a set of modified values,
|
|
* the Recompiler produces a QByteArray representing the recompiled binary.
|
|
*
|
|
* For write-back to work, the journal must be "exportable" (no random access,
|
|
* no unsupported compression, etc.).
|
|
*/
|
|
class Recompiler : public QObject {
|
|
Q_OBJECT
|
|
|
|
public:
|
|
explicit Recompiler(QObject* parent = nullptr);
|
|
|
|
/**
|
|
* @brief Recompile a structure to bytes using journal and values.
|
|
* @param journalId The journal ID from parsing
|
|
* @param values The current values (may be modified from original)
|
|
* @return The recompiled binary data
|
|
* @throws std::runtime_error if recompilation fails or journal not exportable
|
|
*/
|
|
QByteArray recompile(int journalId, const QVariantMap& values);
|
|
|
|
/**
|
|
* @brief Check if a journal can be recompiled.
|
|
* @param journalId The journal ID to check
|
|
* @return true if recompilation is possible
|
|
*/
|
|
bool canRecompile(int journalId) const;
|
|
|
|
/**
|
|
* @brief Get human-readable reason why recompilation is not possible.
|
|
* @param journalId The journal ID to check
|
|
* @return Empty string if exportable, otherwise the reason
|
|
*/
|
|
QString whyNotRecompilable(int journalId) const;
|
|
|
|
/**
|
|
* @brief Recompile and return both the bytes and any errors/warnings.
|
|
* @param journalId The journal ID
|
|
* @param values The values to use
|
|
* @param outErrors Output parameter for error messages
|
|
* @return The recompiled bytes (empty if failed)
|
|
*/
|
|
QByteArray recompileWithErrors(int journalId, const QVariantMap& values, QStringList& outErrors);
|
|
|
|
signals:
|
|
/**
|
|
* @brief Emitted during recompilation to report progress.
|
|
* @param percent Progress percentage (0-100)
|
|
* @param stage Description of current stage
|
|
*/
|
|
void progressChanged(int percent, const QString& stage);
|
|
|
|
private:
|
|
/**
|
|
* @brief Write a scalar value to the output stream.
|
|
* @param out Output stream
|
|
* @param type Scalar type
|
|
* @param order Byte order
|
|
* @param value Value to write
|
|
*/
|
|
void writeScalar(QDataStream& out, ScalarType type, ByteOrder order, const QVariant& value);
|
|
|
|
/**
|
|
* @brief Write raw bytes to the output stream.
|
|
* @param out Output stream
|
|
* @param data Bytes to write
|
|
*/
|
|
void writeBytes(QDataStream& out, const QByteArray& data);
|
|
|
|
/**
|
|
* @brief Write a null-terminated C string.
|
|
* @param out Output stream
|
|
* @param str String to write
|
|
*/
|
|
void writeCString(QDataStream& out, const QString& str);
|
|
|
|
/**
|
|
* @brief Write a null-terminated wide string (UTF-16).
|
|
* @param out Output stream
|
|
* @param str String to write
|
|
* @param order Byte order (LE or BE)
|
|
*/
|
|
void writeWString(QDataStream& out, const QString& str, ByteOrder order);
|
|
|
|
/**
|
|
* @brief Write padding/skip bytes.
|
|
* @param out Output stream
|
|
* @param count Number of bytes to skip (writes zeros)
|
|
* @param originalBytes Original bytes to use (if available)
|
|
*/
|
|
void writeSkip(QDataStream& out, qint64 count, const QByteArray& originalBytes = QByteArray());
|
|
|
|
/**
|
|
* @brief Get the value for a field, using modified value if present, otherwise original.
|
|
* @param values Modified values map
|
|
* @param entry Journal entry with original value
|
|
* @return The value to use for recompilation
|
|
*/
|
|
QVariant getValueForField(const QVariantMap& values, const JournalEntry& entry) const;
|
|
|
|
/**
|
|
* @brief Recursively recompile a nested structure.
|
|
* @param nestedJournalId The nested journal ID
|
|
* @param parentValues Values from parent (may contain nested values)
|
|
* @param fieldName The field name for the nested structure
|
|
* @param outErrors Output for error messages
|
|
* @return The recompiled bytes for the nested structure
|
|
*/
|
|
QByteArray recompileNested(int nestedJournalId, const QVariantMap& parentValues,
|
|
const QString& fieldName, QStringList& outErrors);
|
|
|
|
/**
|
|
* @brief Extract nested values from parent values map.
|
|
* @param parentValues The parent values map
|
|
* @param fieldName The field name for the nested structure
|
|
* @param nestedJournal The nested journal (to get original values)
|
|
* @return Values map for the nested structure
|
|
*/
|
|
QVariantMap extractNestedValues(const QVariantMap& parentValues, const QString& fieldName,
|
|
const OperationJournal* nestedJournal) const;
|
|
|
|
/**
|
|
* @brief Compress data using the specified compression type.
|
|
* @param data The uncompressed data
|
|
* @param compressionType The compression algorithm ("zlib", "deflate", "xmem", "oodle")
|
|
* @param params Algorithm-specific parameters (e.g., compression level, window size)
|
|
* @return The compressed data
|
|
*/
|
|
QByteArray compress(const QByteArray& data, const QString& compressionType,
|
|
const QByteArray& params = QByteArray()) const;
|
|
|
|
/**
|
|
* @brief Check if a compression type is supported for write-back.
|
|
* @param compressionType The compression type name
|
|
* @return true if compression is supported
|
|
*/
|
|
bool isCompressionSupported(const QString& compressionType) const;
|
|
|
|
// Track visited journals to prevent infinite recursion
|
|
QSet<int> m_visitedJournals;
|
|
};
|
|
|
|
#endif // RECOMPILER_H
|