#ifndef OPERATIONJOURNAL_H #define OPERATIONJOURNAL_H #include "ast.h" #include #include #include #include #include #include // Types of operations that can be journaled for write-back enum class OpType { // Primitive reads - directly from stream ReadScalar, // u8, u16, u32, u64, i8, i16, i32, i64, f32, f64, bool ReadBytes, // read(n) ReadCString, // cstring() ReadWString, // wstring() or wstring_be() // Position operations - padding/alignment Skip, // skip(n) - creates padding bytes Align, // align(n) - alignment padding Seek, // seek(pos) - absolute position change // Computed values - result of expressions Computed, // result of arithmetic/logic (a + b, etc.) // Decompression - marks compression boundary Decompress, // zlib, xmem, lzo, deflate, oodle // Nested parsing ParseNested, // parse() - creates new stream from bytes ParseHere, // parse_here() - delegates to another type at same position // Random access reads - makes file non-exportable RandomAccess, // u32at(), bytesat(), i32at(), etc. }; // Invertible operation type for reverse evaluation enum class InvertOp { Identity, // x = x Add, // x = y + c -> y = x - c Sub, // x = y - c -> y = x + c SubFrom, // x = c - y -> y = c - x Mul, // x = y * c -> y = x / c (if c != 0) Div, // x = y / c -> y = x * c Shl, // x = y << c -> y = x >> c Shr, // x = y >> c -> y = x << c (lossy for non-zero low bits) Negate, // x = -y -> y = -x // Non-invertible operations (result in readonly fields) BitAnd, // x = y & c -> cannot reverse (information loss) BitOr, // x = y | c -> cannot reverse (information loss) BitXor, // x = y ^ c -> y = x ^ c (actually invertible!) Modulo, // x = y % c -> cannot reverse (information loss) }; // Chain of operations to invert for reverse evaluation struct InverseChain { QString sourceField; // The raw field that was read from stream QVector> operations; // Operations to apply in reverse bool canInvert = true; // false if chain contains non-invertible operation QString reason; // Why it can't be inverted (if canInvert is false) }; // Represents a single journal entry - one parsing operation struct JournalEntry { OpType type = OpType::ReadScalar; QString fieldName; // Variable name this populates (empty for Skip/Align) qint64 streamOffset = 0; // Position in stream where this was read qint64 fieldSize = 0; // Size in bytes (for reads) ByteOrder byteOrder = ByteOrder::LE; // Byte order when read ScalarType scalarType = ScalarType::U8; // For ReadScalar // Original bytes that were read (for comparison/restoration) QByteArray originalBytes; // For computed values - the expression that produced this QString expression; // Original expression string for debugging QVector dependencies; // Field names this depends on InverseChain inverseChain; // How to reverse compute source value // For decompression operations QString compressionType; // "zlib", "xmem", "lzo", "deflate", "oodle" qint64 compressedSize = 0; qint64 uncompressedSize = 0; QByteArray compressionParams; // Algorithm-specific parameters for recompression // For nested parsing QString nestedTypeName; // Type being parsed int nestedJournalId = -1; // ID of child journal // UI metadata bool hasUiAnnotation = false; QString displayName; bool isEditable = false; // ui("Name", edit) flag // For validation QVariant originalValue; // Value that was parsed (for comparison) }; // Holds the complete journal for a parsed structure struct OperationJournal { int id = -1; // Unique journal ID QString typeName; // Type that was parsed QVector entries; // Operations in order QByteArray originalBytes; // Complete original bytes for this structure // Hierarchy int parentJournalId = -1; // -1 for root QVector childJournalIds; // Nested journals // Non-exportable flags bool hasRandomAccess = false; // Uses u32at(), bytesat(), etc. bool hasScanningLoop = false; // Control flow that scans content bool hasUnsupportedCompression = false; // LZO or other unsupported compression QString nonExportableReason; // Human-readable reason if not exportable // Check if this journal can be used for write-back bool isExportable() const { return !hasRandomAccess && !hasScanningLoop && !hasUnsupportedCompression; } // Get human-readable reason why not exportable QString whyNotExportable() const { if (isExportable()) return QString(); if (hasRandomAccess) return "Uses random access reads (u32at, bytesat, etc.)"; if (hasScanningLoop) return "Uses content-dependent scanning"; if (hasUnsupportedCompression) return "Uses unsupported compression: " + nonExportableReason; return nonExportableReason; } }; using OperationJournalPtr = QSharedPointer; // Singleton manager for all operation journals class JournalManager { public: static JournalManager& instance(); // Create new journal for a parse operation int createJournal(const QString& typeName, int parentId = -1); // Add entry to journal void addEntry(int journalId, const JournalEntry& entry); // Set the field name for the last entry (when we know it after the read) void setLastEntryField(int journalId, const QString& fieldName); // Mark journal with non-exportable reason void markRandomAccess(int journalId, const QString& functionName = QString()); void markScanningLoop(int journalId); void markUnsupportedCompression(int journalId, const QString& compressionType); // Store original bytes for the journal void setOriginalBytes(int journalId, const QByteArray& bytes); // Retrieve journals OperationJournal* getJournal(int id); const OperationJournal* getJournal(int id) const; OperationJournal* getRootJournal(int journalId); // Find journal entry by field name JournalEntry* findEntry(int journalId, const QString& fieldName); const JournalEntry* findEntry(int journalId, const QString& fieldName) const; // Link child journal to parent void linkChildJournal(int parentId, int childId); // Clear all journals (for cleanup between parses) void clear(); // Get all journals (for debugging) QList allJournalIds() const; private: JournalManager() = default; JournalManager(const JournalManager&) = delete; JournalManager& operator=(const JournalManager&) = delete; QMap m_journals; int m_nextId = 0; mutable QMutex m_mutex; }; // Helper to get scalar size inline qint64 scalarSize(ScalarType t) { switch (t) { case ScalarType::U8: case ScalarType::I8: case ScalarType::Bool: return 1; case ScalarType::U16: case ScalarType::I16: return 2; case ScalarType::U32: case ScalarType::I32: case ScalarType::F32: return 4; case ScalarType::U64: case ScalarType::I64: case ScalarType::F64: return 8; } return 0; } // Helper to convert ScalarType to string inline QString scalarTypeName(ScalarType t) { switch (t) { case ScalarType::U8: return "u8"; case ScalarType::I8: return "i8"; case ScalarType::U16: return "u16"; case ScalarType::I16: return "i16"; case ScalarType::U32: return "u32"; case ScalarType::I32: return "i32"; case ScalarType::U64: return "u64"; case ScalarType::I64: return "i64"; case ScalarType::F32: return "f32"; case ScalarType::F64: return "f64"; case ScalarType::Bool: return "bool"; } return "unknown"; } // Helper to convert OpType to string inline QString opTypeName(OpType t) { switch (t) { case OpType::ReadScalar: return "ReadScalar"; case OpType::ReadBytes: return "ReadBytes"; case OpType::ReadCString: return "ReadCString"; case OpType::ReadWString: return "ReadWString"; case OpType::Skip: return "Skip"; case OpType::Align: return "Align"; case OpType::Seek: return "Seek"; case OpType::Computed: return "Computed"; case OpType::Decompress: return "Decompress"; case OpType::ParseNested: return "ParseNested"; case OpType::ParseHere: return "ParseHere"; case OpType::RandomAccess: return "RandomAccess"; } return "Unknown"; } #endif // OPERATIONJOURNAL_H