XPlor/libs/dsl/recompiler.h
njohnson 3e3883cfeb Add DSL journaling infrastructure for field editing and write-back
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>
2026-01-12 20:53:20 -05:00

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