XPlor/libs/dsl/operationjournal.cpp
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

200 lines
4.8 KiB
C++

#include "operationjournal.h"
#include <QMutexLocker>
JournalManager& JournalManager::instance() {
static JournalManager instance;
return instance;
}
int JournalManager::createJournal(const QString& typeName, int parentId) {
QMutexLocker locker(&m_mutex);
int id = m_nextId++;
auto journal = QSharedPointer<OperationJournal>::create();
journal->id = id;
journal->typeName = typeName;
journal->parentJournalId = parentId;
m_journals[id] = journal;
// Link to parent if specified
if (parentId >= 0 && m_journals.contains(parentId)) {
m_journals[parentId]->childJournalIds.append(id);
}
return id;
}
void JournalManager::addEntry(int journalId, const JournalEntry& entry) {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(journalId)) {
return;
}
m_journals[journalId]->entries.append(entry);
}
void JournalManager::setLastEntryField(int journalId, const QString& fieldName) {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(journalId)) {
return;
}
auto& entries = m_journals[journalId]->entries;
if (!entries.isEmpty()) {
entries.last().fieldName = fieldName;
}
}
void JournalManager::markRandomAccess(int journalId, const QString& functionName) {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(journalId)) {
return;
}
auto& journal = m_journals[journalId];
journal->hasRandomAccess = true;
if (!functionName.isEmpty()) {
if (journal->nonExportableReason.isEmpty()) {
journal->nonExportableReason = "Random access: " + functionName;
} else {
journal->nonExportableReason += ", " + functionName;
}
}
}
void JournalManager::markScanningLoop(int journalId) {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(journalId)) {
return;
}
m_journals[journalId]->hasScanningLoop = true;
}
void JournalManager::markUnsupportedCompression(int journalId, const QString& compressionType) {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(journalId)) {
return;
}
auto& journal = m_journals[journalId];
journal->hasUnsupportedCompression = true;
journal->nonExportableReason = compressionType;
}
void JournalManager::setOriginalBytes(int journalId, const QByteArray& bytes) {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(journalId)) {
return;
}
m_journals[journalId]->originalBytes = bytes;
}
OperationJournal* JournalManager::getJournal(int id) {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(id)) {
return nullptr;
}
return m_journals[id].data();
}
const OperationJournal* JournalManager::getJournal(int id) const {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(id)) {
return nullptr;
}
return m_journals[id].data();
}
OperationJournal* JournalManager::getRootJournal(int journalId) {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(journalId)) {
return nullptr;
}
// Walk up to find root
int currentId = journalId;
while (m_journals.contains(currentId)) {
int parentId = m_journals[currentId]->parentJournalId;
if (parentId < 0 || !m_journals.contains(parentId)) {
return m_journals[currentId].data();
}
currentId = parentId;
}
return nullptr;
}
JournalEntry* JournalManager::findEntry(int journalId, const QString& fieldName) {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(journalId)) {
return nullptr;
}
auto& entries = m_journals[journalId]->entries;
for (auto& entry : entries) {
if (entry.fieldName == fieldName) {
return &entry;
}
}
return nullptr;
}
const JournalEntry* JournalManager::findEntry(int journalId, const QString& fieldName) const {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(journalId)) {
return nullptr;
}
const auto& entries = m_journals[journalId]->entries;
for (const auto& entry : entries) {
if (entry.fieldName == fieldName) {
return &entry;
}
}
return nullptr;
}
void JournalManager::linkChildJournal(int parentId, int childId) {
QMutexLocker locker(&m_mutex);
if (!m_journals.contains(parentId) || !m_journals.contains(childId)) {
return;
}
m_journals[childId]->parentJournalId = parentId;
auto& childIds = m_journals[parentId]->childJournalIds;
if (!childIds.contains(childId)) {
childIds.append(childId);
}
}
void JournalManager::clear() {
QMutexLocker locker(&m_mutex);
m_journals.clear();
m_nextId = 0;
}
QList<int> JournalManager::allJournalIds() const {
QMutexLocker locker(&m_mutex);
return m_journals.keys();
}