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>
202 lines
6.8 KiB
C++
202 lines
6.8 KiB
C++
#ifndef DSLUISCHEMA_H
|
|
#define DSLUISCHEMA_H
|
|
|
|
#include "ast.h"
|
|
|
|
#include <QMap>
|
|
|
|
enum class UiFieldKind {
|
|
ScalarU8, ScalarI8, ScalarU16, ScalarI16, ScalarU32, ScalarI32, ScalarU64, ScalarI64, GenericValue, Bool
|
|
};
|
|
|
|
struct UiField {
|
|
QString name;
|
|
QString displayName;
|
|
UiFieldKind kind = UiFieldKind::GenericValue;
|
|
bool visible = true;
|
|
bool readOnly = false;
|
|
|
|
int firstLine = 1;
|
|
int firstCol = 1;
|
|
|
|
bool isTable = false;
|
|
QString tableTitle;
|
|
QStringList columns;
|
|
QHash<QString, QString> formats;
|
|
};
|
|
|
|
inline UiFieldKind toUiKind(ScalarType t) {
|
|
switch (t) {
|
|
case ScalarType::U8: return UiFieldKind::ScalarU8;
|
|
case ScalarType::I8: return UiFieldKind::ScalarI8;
|
|
case ScalarType::U16: return UiFieldKind::ScalarU16;
|
|
case ScalarType::I16: return UiFieldKind::ScalarI16;
|
|
case ScalarType::U32: return UiFieldKind::ScalarU32;
|
|
case ScalarType::I32: return UiFieldKind::ScalarI32;
|
|
case ScalarType::U64: return UiFieldKind::ScalarU64;
|
|
case ScalarType::I64: return UiFieldKind::ScalarI64;
|
|
case ScalarType::Bool: return UiFieldKind::Bool;
|
|
}
|
|
return UiFieldKind::GenericValue;
|
|
}
|
|
|
|
inline bool isBoolExpr(const Expr& e) {
|
|
if (std::holds_alternative<Expr::Unary>(e.node)) {
|
|
const auto& u = std::get<Expr::Unary>(e.node);
|
|
if (u.op == "!") return true;
|
|
return isBoolExpr(*u.rhs);
|
|
}
|
|
if (std::holds_alternative<Expr::Binary>(e.node)) {
|
|
const auto& b = std::get<Expr::Binary>(e.node);
|
|
const QString& op = b.op;
|
|
if (op == "==" || op == "!=" || op == "<" || op == "<=" || op == ">" || op == ">=" ||
|
|
op == "&&" || op == "||") {
|
|
return true;
|
|
}
|
|
return isBoolExpr(*b.lhs) || isBoolExpr(*b.rhs);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
inline UiFieldKind inferKindFromAssignExpr(const ExprPtr& value) {
|
|
if (!value) return UiFieldKind::GenericValue;
|
|
if (isBoolExpr(*value)) return UiFieldKind::Bool;
|
|
return UiFieldKind::GenericValue;
|
|
}
|
|
|
|
inline QStringList splitCsvCols(const QString& s) {
|
|
QStringList out;
|
|
for (const QString& part : s.split(',', Qt::SkipEmptyParts)) {
|
|
const QString trimmed = part.trimmed();
|
|
if (!trimmed.isEmpty()) out.push_back(trimmed);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
inline void collectFieldsFromBlock(const QVector<StmtPtr>& blk, QMap<QString, UiField>& out, const Module* mod = nullptr);
|
|
|
|
inline void collectFieldsFromStmt(const Stmt& s, QMap<QString, UiField>& out, const Module* mod = nullptr) {
|
|
auto add = [&](const QString& name,
|
|
UiFieldKind kind,
|
|
bool visible,
|
|
bool readOnly,
|
|
const QString& display,
|
|
const UiFlags* flags)
|
|
{
|
|
if (name.isEmpty()) return;
|
|
if (!visible) return;
|
|
|
|
if (!out.contains(name)) {
|
|
UiField f;
|
|
f.name = name;
|
|
f.displayName = display.isEmpty() ? name : display;
|
|
f.kind = kind;
|
|
f.visible = visible;
|
|
f.readOnly = readOnly;
|
|
f.firstLine = s.line;
|
|
f.firstCol = s.col;
|
|
|
|
if (flags && flags->isTable()) {
|
|
f.isTable = true;
|
|
f.tableTitle = flags->tableTitle;
|
|
f.columns = splitCsvCols(flags->columnsCsv);
|
|
f.formats = flags->formats;
|
|
}
|
|
|
|
out.insert(name, f);
|
|
} else {
|
|
if (!display.isEmpty())
|
|
out[name].displayName = display;
|
|
|
|
if (out[name].kind == UiFieldKind::GenericValue && kind != UiFieldKind::GenericValue)
|
|
out[name].kind = kind;
|
|
|
|
out[name].readOnly = out[name].readOnly || readOnly;
|
|
|
|
if (flags && flags->isTable() && !out[name].isTable) {
|
|
out[name].isTable = true;
|
|
out[name].tableTitle = flags->tableTitle;
|
|
out[name].columns = splitCsvCols(flags->columnsCsv);
|
|
out[name].formats = flags->formats;
|
|
}
|
|
}
|
|
};
|
|
|
|
if (std::holds_alternative<Stmt::ReadScalar>(s.node)) {
|
|
const auto& rs = std::get<Stmt::ReadScalar>(s.node);
|
|
add(rs.name, toUiKind(rs.type), rs.ui.visible(), rs.ui.readOnly, rs.ui.display, &rs.ui);
|
|
return;
|
|
}
|
|
|
|
if (std::holds_alternative<Stmt::Assign>(s.node)) {
|
|
const auto& as = std::get<Stmt::Assign>(s.node);
|
|
const bool vis = as.ui.visible();
|
|
// Respect the parser's readOnly flag - use ui("name", edit) for editable fields
|
|
const bool ro = as.ui.readOnly;
|
|
add(as.name, inferKindFromAssignExpr(as.value), vis, ro, as.ui.display, &as.ui);
|
|
return;
|
|
}
|
|
|
|
if (std::holds_alternative<Stmt::If>(s.node)) {
|
|
const auto& iff = std::get<Stmt::If>(s.node);
|
|
collectFieldsFromBlock(iff.thenBody, out, mod);
|
|
collectFieldsFromBlock(iff.elseBody, out, mod);
|
|
return;
|
|
}
|
|
|
|
if (std::holds_alternative<Stmt::While>(s.node)) {
|
|
const auto& wh = std::get<Stmt::While>(s.node);
|
|
collectFieldsFromBlock(wh.body, out, mod);
|
|
return;
|
|
}
|
|
|
|
if (std::holds_alternative<Stmt::Repeat>(s.node)) {
|
|
const auto& rp = std::get<Stmt::Repeat>(s.node);
|
|
collectFieldsFromBlock(rp.body, out, mod);
|
|
return;
|
|
}
|
|
|
|
if (std::holds_alternative<Stmt::ForRange>(s.node)) {
|
|
const auto& fr = std::get<Stmt::ForRange>(s.node);
|
|
add(fr.var, UiFieldKind::GenericValue, false, true, "", {});
|
|
collectFieldsFromBlock(fr.body, out, mod);
|
|
return;
|
|
}
|
|
|
|
// Handle CallStmt for parse_here - merge UI fields from referenced type
|
|
if (std::holds_alternative<Stmt::CallStmt>(s.node)) {
|
|
const auto& cs = std::get<Stmt::CallStmt>(s.node);
|
|
if (cs.call && std::holds_alternative<Expr::Call>(cs.call->node)) {
|
|
const auto& call = std::get<Expr::Call>(cs.call->node);
|
|
|
|
// Handle parse_here - merge UI fields from referenced type
|
|
if (call.fn == "parse_here" && !call.args.isEmpty() && mod) {
|
|
if (std::holds_alternative<Expr::String>(call.args[0]->node)) {
|
|
const QString typeName = std::get<Expr::String>(call.args[0]->node).v;
|
|
if (mod->types.contains(typeName)) {
|
|
const TypeDef& childTd = mod->types[typeName];
|
|
collectFieldsFromBlock(childTd.body, out, mod);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
inline void collectFieldsFromBlock(const QVector<StmtPtr>& blk, QMap<QString, UiField>& out, const Module* mod) {
|
|
for (const auto& sp : blk) {
|
|
if (!sp) continue;
|
|
collectFieldsFromStmt(*sp, out, mod);
|
|
}
|
|
}
|
|
|
|
inline QMap<QString, UiField> buildUiSchemaForType(const TypeDef& td, const Module* mod = nullptr) {
|
|
QMap<QString, UiField> fields;
|
|
collectFieldsFromBlock(td.criteria, fields, mod);
|
|
collectFieldsFromBlock(td.body, fields, mod);
|
|
return fields;
|
|
}
|
|
|
|
#endif // DSLUISCHEMA_H
|