#include "interpreter.h" #include "compression.h" #include "utils.h" #include "logmanager.h" #include #include Interpreter::Interpreter(Module mod) : m_mod(std::move(mod)) {} static QDataStream::ByteOrder toQtOrder(ByteOrder b) { return (b == ByteOrder::LE) ? QDataStream::LittleEndian : QDataStream::BigEndian; } QVariantMap Interpreter::runType(const QString& typeName, QIODevice* dev, const QString &filePath) const { QDataStream ds(dev); return runType(typeName, ds, filePath); } QVariantMap Interpreter::runType(const QString& typeName, QDataStream& stream, const QString& filePath) const { if (!m_mod.types.contains(typeName)) { throw std::runtime_error(("Unknown type: " + typeName).toStdString()); } const qint64 startPos = stream.device() ? stream.device()->pos() : -1; LogManager::instance().addEntry(QString("[PARSE] Starting type '%1' at pos 0x%2") .arg(typeName) .arg(startPos, 0, 16)); Runtime rt; rt.in = &stream; rt.module = &m_mod; const TypeDef& td = m_mod.types[typeName]; rt.order = td.order; applyByteOrder(rt); if (!filePath.isEmpty()) { seedFileVars(rt, filePath); } execBlock(rt, td.body); rt.vars["_type"] = typeName; const qint64 endPos = stream.device() ? stream.device()->pos() : -1; LogManager::instance().addEntry(QString("[PARSE] Finished type '%1' at pos 0x%2 (consumed %3 bytes)") .arg(typeName) .arg(endPos, 0, 16) .arg(endPos - startPos)); return rt.vars; } void Interpreter::seedFileVars(Runtime& rt, const QString& filePath) { rt.filePath = filePath; QFileInfo fi(filePath); rt.vars["_path"] = filePath; rt.vars["_name"] = fi.fileName(); // "mp_test_load.ff" rt.vars["_basename"] = fi.completeBaseName(); // "mp_test_load" (without extension) rt.vars["_ext"] = fi.suffix().toLower(); // "ff", "xpak", etc. } bool Interpreter::checkCriteria(const QString &typeName, QIODevice *dev, const QString filePath) const { if (!m_mod.types.contains(typeName)) { LogManager::instance().addEntry(QString("[CRITERIA] Type '%1' not found").arg(typeName)); return false; } const TypeDef& td = m_mod.types[typeName]; if (td.criteria.isEmpty()) { LogManager::instance().addEntry(QString("[CRITERIA] Type '%1' has no criteria, returning true").arg(typeName)); return true; } LogManager::instance().addEntry(QString("[CRITERIA] Checking type '%1' for file '%2'").arg(typeName).arg(filePath)); // Save/restore device position const qint64 savedPos = dev->pos(); auto restore = [&]() { dev->seek(savedPos); }; try { QDataStream ds(dev); Runtime rt; rt.in = &ds; rt.module = &m_mod; rt.order = td.order; applyByteOrder(rt); if (!filePath.isEmpty()) { seedFileVars(rt, filePath); } execBlock(rt, td.criteria); restore(); LogManager::instance().addEntry(QString("[CRITERIA] Type '%1' PASSED criteria").arg(typeName)); return true; } catch (const std::exception& e) { restore(); LogManager::instance().addEntry(QString("[CRITERIA] Type '%1' FAILED: %2").arg(typeName).arg(e.what())); return false; } catch (...) { restore(); LogManager::instance().addEntry(QString("[CRITERIA] Type '%1' FAILED (unknown exception)").arg(typeName)); return false; } } QVariantMap Interpreter::runTypeInternal(const QString &typeName, QDataStream &stream, const QString &filePath, std::optional inheritedOrder) const { if (!m_mod.types.contains(typeName)) { throw std::runtime_error(("Unknown type: " + typeName).toStdString()); } Runtime rt; rt.in = &stream; rt.module = &m_mod; const TypeDef& td = m_mod.types[typeName]; // IMPORTANT: inherit if the typedef doesn't specify byteorder if (td.hasExplicitByteOrder) rt.order = td.order; else if (inheritedOrder.has_value()) rt.order = *inheritedOrder; else rt.order = td.order; // root fallback (keeps old behavior) applyByteOrder(rt); if (!filePath.isEmpty()) seedFileVars(rt, filePath); execBlock(rt, td.body); rt.vars["_type"] = typeName; return rt.vars; } void Interpreter::applyByteOrder(Runtime& rt) const { if (!rt.in) return; rt.in->setByteOrder(toQtOrder(rt.order)); } qint64 Interpreter::toInt(const QVariant& v) const { if (v.canConvert()) return v.toLongLong(); throw std::runtime_error("Expected integer value"); } bool Interpreter::toBool(const QVariant& v) const { if (v.typeId() == QMetaType::Bool) return v.toBool(); if (v.canConvert()) return v.toLongLong() != 0; if (v.typeId() == QMetaType::QString) return !v.toString().isEmpty(); if (v.canConvert()) return !v.toByteArray().isEmpty(); return v.isValid(); } QVariant Interpreter::readScalar(Runtime& rt, ScalarType t) const { QDataStream& in = *rt.in; switch (t) { case ScalarType::U8: { quint8 v; in >> v; return v; } case ScalarType::I8: { qint8 v; in >> v; return v; } case ScalarType::U16: { quint16 v; in >> v; return v; } case ScalarType::I16: { qint16 v; in >> v; return v; } case ScalarType::U32: { quint32 v; in >> v; return v; } case ScalarType::I32: { qint32 v; in >> v; return v; } case ScalarType::U64: { quint64 v; in >> v; return v; } case ScalarType::I64: { qint64 v; in >> v; return v; } case ScalarType::Bool: { quint8 v; in >> v; return (v != 0); } } return {}; } QByteArray Interpreter::readBytes(Runtime& rt, qint64 n) const { if (n < 0) throw std::runtime_error("read(n): n must be >= 0"); QIODevice* dev = rt.in->device(); if (!dev) throw std::runtime_error("No device in stream"); QByteArray buf; buf.resize(int(n)); const int got = rt.in->readRawData(buf.data(), int(n)); if (got != int(n)) { buf.resize(qMax(0, got)); throw std::runtime_error("Unexpected EOF while reading bytes"); } return buf; } QByteArray Interpreter::readEOF(Runtime& rt) const { QIODevice* dev = rt.in->device(); if (!dev) throw std::runtime_error("No device in stream"); const qint64 remaining = dev->size() - dev->pos(); if (remaining < 0) return {}; return readBytes(rt, remaining); } QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const { // Builtins: // pos() -> int // size() -> int // read(n) where n is int or EOF -> bytes // zlib(bytes) -> bytes // parse("typeName", bytes) -> map if (c.fn == "pos") { if (!c.args.isEmpty()) throw std::runtime_error("pos() takes no args"); return rt.in->device()->pos(); } if (c.fn == "size") { if (!c.args.isEmpty()) throw std::runtime_error("size() takes no args"); return rt.in->device()->size(); } if (c.fn == "read") { if (c.args.size() != 1) throw std::runtime_error("read(x) takes 1 arg"); QVariant arg = evalExpr(rt, *c.args[0]); // special: EOF token was represented as Var("EOF") if (arg.typeId() == QMetaType::QString && arg.toString() == "EOF") { return readEOF(rt); } return readBytes(rt, toInt(arg)); } if (c.fn == "zlib") { if (c.args.size() != 1) throw std::runtime_error("zlib(bytes) takes 1 arg"); QByteArray in = evalExpr(rt, *c.args[0]).toByteArray(); const QByteArray decompressed = Compression::DecompressZLIB(in); Utils::ExportData(rt.filePath.split('/').last() + ".zone", decompressed); if (!rt.filePath.isEmpty()) { QFileInfo fi(rt.filePath); const QString stem = fi.completeBaseName(); // "mp_test_load" rt.vars["_zone_name"] = stem + ".zone"; // "mp_test_load.zone" } return decompressed; } if (c.fn == "parse") { if (c.args.size() != 2) throw std::runtime_error("parse(typeName, bytes) takes 2 args"); const QString typeName = evalExpr(rt, *c.args[0]).toString(); const QByteArray bytes = evalExpr(rt, *c.args[1]).toByteArray(); QDataStream nested(bytes); QVariantMap obj = runTypeInternal(typeName, nested, rt.filePath, rt.order); if (rt.vars.contains("_zone_name") && !obj.contains("_zone_name")) { obj["_zone_name"] = rt.vars["_zone_name"]; } obj["_type"] = typeName; return obj; } if (c.fn == "parse_here") { if (c.args.size() != 1) throw std::runtime_error("parse_here(typeName) takes 1 arg"); const QString typeName = evalExpr(rt, *c.args[0]).toString(); if (!rt.module->types.contains(typeName)) { throw std::runtime_error(("Unknown type (parse_here): " + typeName).toStdString()); } const qint64 startPos = rt.in->device() ? rt.in->device()->pos() : -1; LogManager::instance().addEntry(QString("[PARSE_HERE] Parsing '%1' at pos 0x%2") .arg(typeName) .arg(startPos, 0, 16)); // IMPORTANT: parse using the SAME stream (advance) // We must temporarily apply that type's byteorder. const TypeDef& td = rt.module->types[typeName]; // Save current byteorder const auto oldOrder = rt.order; // Decide child order ByteOrder childOrder = oldOrder; if (td.hasExplicitByteOrder) childOrder = td.order; // Create child runtime sharing stream Runtime childRt; childRt.in = rt.in; childRt.module = rt.module; childRt.order = childOrder; applyByteOrder(childRt); execBlock(childRt, td.body); QVariantMap child = childRt.vars; // Restore parent order rt.order = oldOrder; applyByteOrder(rt); child["_type"] = typeName; const qint64 endPos = rt.in->device() ? rt.in->device()->pos() : -1; LogManager::instance().addEntry(QString("[PARSE_HERE] Finished '%1' at pos 0x%2 (consumed %3 bytes)") .arg(typeName) .arg(endPos, 0, 16) .arg(endPos - startPos)); return child; } if (c.fn == "push") { if (c.args.size() != 2) throw std::runtime_error("push(name, value) takes 2 args"); const QString listName = evalExpr(rt, *c.args[0]).toString(); QVariant v = evalExpr(rt, *c.args[1]); if (v.typeId() != QMetaType::QVariantMap) throw std::runtime_error("push expects a map as 2nd arg"); QVariantList lst = rt.vars.value(listName).toList(); // empty if missing/not a list const int newIndex = lst.size(); lst.push_back(v); rt.vars[listName] = lst; const QVariantMap itemMap = v.toMap(); const QString itemType = itemMap.value("_type").toString(); const QString itemName = itemMap.value("_name").toString(); LogManager::instance().addEntry(QString("[PUSH] Added item #%1 to '%2': type='%3', name='%4'") .arg(newIndex) .arg(listName) .arg(itemType) .arg(itemName)); return rt.vars[listName]; } if (c.fn == "get") { if (c.args.size() != 2) throw std::runtime_error("get(container, key) takes 2 args"); QVariant container = evalExpr(rt, *c.args[0]); QVariant key = evalExpr(rt, *c.args[1]); // Array indexing: get(array, index) if (container.typeId() == QMetaType::QVariantList) { const QVariantList list = container.toList(); const qint64 index = toInt(key); if (index < 0 || index >= list.size()) { throw std::runtime_error(QString("get: array index %1 out of bounds (size=%2)") .arg(index).arg(list.size()).toStdString()); } return list[int(index)]; } // Object field access: get(object, "fieldName") if (container.typeId() == QMetaType::QVariantMap) { const QVariantMap map = container.toMap(); const QString fieldName = key.toString(); if (!map.contains(fieldName)) { throw std::runtime_error(QString("get: field '%1' not found in object") .arg(fieldName).toStdString()); } return map.value(fieldName); } throw std::runtime_error("get: first argument must be an array or object"); } if (c.fn == "set") { if (c.args.size() != 3) throw std::runtime_error("set(varName, key, value) takes 3 args"); const QString varName = evalExpr(rt, *c.args[0]).toString(); QVariant key = evalExpr(rt, *c.args[1]); QVariant value = evalExpr(rt, *c.args[2]); if (!rt.vars.contains(varName)) { throw std::runtime_error(QString("set: variable '%1' not found").arg(varName).toStdString()); } QVariant container = rt.vars[varName]; // Array indexing: set("arrayVar", index, value) if (container.typeId() == QMetaType::QVariantList) { QVariantList list = container.toList(); const qint64 index = toInt(key); if (index < 0 || index >= list.size()) { throw std::runtime_error(QString("set: array index %1 out of bounds (size=%2)") .arg(index).arg(list.size()).toStdString()); } list[int(index)] = value; rt.vars[varName] = list; return value; } // Object field access: set("objectVar", "fieldName", value) if (container.typeId() == QMetaType::QVariantMap) { QVariantMap map = container.toMap(); const QString fieldName = key.toString(); map[fieldName] = value; rt.vars[varName] = map; return value; } throw std::runtime_error("set: variable must be an array or object"); } auto readAt = [&](qint64 offset, int nbytes)->QByteArray { QIODevice* dev = rt.in->device(); if (!dev) throw std::runtime_error("No device"); if (offset < 0) throw std::runtime_error("readAt: offset must be >= 0"); if (nbytes < 0) throw std::runtime_error("readAt: nbytes must be >= 0"); const qint64 saved = dev->pos(); if (!dev->seek(offset)) throw std::runtime_error("seek failed"); QByteArray buf(nbytes, Qt::Uninitialized); const int got = rt.in->readRawData(buf.data(), nbytes); dev->seek(saved); if (got != nbytes) throw std::runtime_error("readAt short read"); return buf; }; // u8at(offset) if (c.fn == "u8at") { if (c.args.size() != 1) throw std::runtime_error("u8at(off) takes 1 arg"); qint64 off = toInt(evalExpr(rt, *c.args[0])); QByteArray b = readAt(off, 1); return quint8(b[0]); } if (c.fn == "u16at") { if (c.args.size() != 1) throw std::runtime_error("u16at(off) takes 1 arg"); qint64 off = toInt(evalExpr(rt, *c.args[0])); QByteArray b = readAt(off, 2); const quint8 b0 = quint8(b[0]), b1 = quint8(b[1]); if (rt.order == ByteOrder::LE) return quint16(b0 | (b1 << 8)); return quint16((b0 << 8) | b1); } if (c.fn == "u32at") { if (c.args.size() != 1) throw std::runtime_error("u32at(off) takes 1 arg"); qint64 off = toInt(evalExpr(rt, *c.args[0])); QByteArray b = readAt(off, 4); quint32 v = 0; if (rt.order == ByteOrder::LE) { v = quint32(quint8(b[0])) | (quint32(quint8(b[1])) << 8) | (quint32(quint8(b[2])) << 16) | (quint32(quint8(b[3])) << 24); } else { v = (quint32(quint8(b[0])) << 24) | (quint32(quint8(b[1])) << 16) | (quint32(quint8(b[2])) << 8) | quint32(quint8(b[3])); } return v; } if (c.fn == "u64at") { if (c.args.size() != 1) throw std::runtime_error("u64at(off) takes 1 arg"); qint64 off = toInt(evalExpr(rt, *c.args[0])); QByteArray b = readAt(off, 8); quint64 v = 0; if (rt.order == ByteOrder::LE) { for (int i = 0; i < 8; ++i) v |= (quint64(quint8(b[i])) << (8 * i)); } else { for (int i = 0; i < 8; ++i) v = (v << 8) | quint8(b[i]); } return v; } // optional helper for magic checks if (c.fn == "bytesat") { if (c.args.size() != 2) throw std::runtime_error("bytesat(off,len) takes 2 args"); qint64 off = toInt(evalExpr(rt, *c.args[0])); int len = int(toInt(evalExpr(rt, *c.args[1]))); return readAt(off, len); } if (c.fn == "ascii") { if (c.args.size() != 1) throw std::runtime_error("ascii(bytes) takes 1 arg"); const QByteArray b = evalExpr(rt, *c.args[0]).toByteArray(); return QString::fromLatin1(b); // strict 1:1 byte->char (good for magic tags) } if (c.fn == "cstring") { if (c.args.size() != 0) throw std::runtime_error("cstring() takes 0 args"); // Read null-terminated string (C-style string) QByteArray result; char ch; while (true) { if (rt.in->readRawData(&ch, 1) != 1) { throw std::runtime_error("cstring(): unexpected EOF"); } if (ch == '\0') break; result.append(ch); } return QString::fromUtf8(result); } if (c.fn == "set_global") { if (c.args.size() != 2) throw std::runtime_error("set_global(name, value) takes 2 args"); const QString name = evalExpr(rt, *c.args[0]).toString(); const QVariant value = evalExpr(rt, *c.args[1]); rt.module->globals[name] = value; return value; } if (c.fn == "write_file") { if (c.args.size() != 2) throw std::runtime_error("write_file(filename, data) takes 2 args"); const QString filename = evalExpr(rt, *c.args[0]).toString(); const QByteArray data = evalExpr(rt, *c.args[1]).toByteArray(); // Use Utils::ExportData which writes to exports/ directory if (!Utils::ExportData(filename, data)) { throw std::runtime_error(("Failed to export file: " + filename).toStdString()); } LogManager::instance().addEntry(QString("[EXPORT] Exported %1 bytes to exports/%2") .arg(data.size()) .arg(filename)); return data.size(); // Return number of bytes written } throw std::runtime_error(("Unknown function: " + c.fn).toStdString()); } QVariant Interpreter::evalPipe(Runtime& rt, const Expr::Pipe& p) const { QVariant v = evalExpr(rt, *p.base); for (const auto& stage : p.stages) { if (stage.fn == "zlib") { if (!v.canConvert()) throw std::runtime_error("zlib stage expects bytes"); const QByteArray in = v.toByteArray(); const QByteArray decompressed = Compression::DecompressZLIB(in); // Match evalCall("zlib") behavior if (!rt.filePath.isEmpty()) { QFileInfo fi(rt.filePath); const QString stem = fi.completeBaseName(); // "mp_test_load" rt.vars["_zone_name"] = stem + ".zone"; // "mp_test_load.zone" } v = decompressed; continue; } if (stage.fn == "parse") { // stage args[0] is a String expr holding type name (we created it that way in parser) if (stage.args.size() != 1) throw std::runtime_error("parse stage requires type name"); const QString typeName = stage.args[0]->node.index() == 1 ? std::get(stage.args[0]->node).v : evalExpr(rt, *stage.args[0]).toString(); if (!v.canConvert()) throw std::runtime_error("parse stage expects bytes"); const QByteArray bytes = v.toByteArray(); QDataStream nested(bytes); QVariantMap obj = runTypeInternal(typeName, nested, rt.filePath, rt.order); // propagate zone label just like evalCall("parse") if (rt.vars.contains("_zone_name") && !obj.contains("_zone_name")) { obj["_zone_name"] = rt.vars["_zone_name"]; } obj["_type"] = typeName; v = obj; continue; } // Allow calling a stage as fn(current) for future extensions // e.g. stage "zlib" already handled above throw std::runtime_error(("Unknown pipeline stage: " + stage.fn).toStdString()); } return v; } QVariant Interpreter::evalExpr(Runtime& rt, const Expr& e) const { if (std::holds_alternative(e.node)) { return std::get(e.node).v; } if (std::holds_alternative(e.node)) { return std::get(e.node).v; } if (std::holds_alternative(e.node)) { const QString name = std::get(e.node).name; // EOF is a sentinel used only inside read(...) if (name == "EOF") return QString("EOF"); // Check local vars first, then globals if (rt.vars.contains(name)) { return rt.vars.value(name); } if (rt.module && rt.module->globals.contains(name)) { return rt.module->globals.value(name); } throw std::runtime_error(("Unknown variable: " + name).toStdString()); } if (std::holds_alternative(e.node)) { const auto& u = std::get(e.node); QVariant rhs = evalExpr(rt, *u.rhs); if (u.op == "!") return !toBool(rhs); if (u.op == "-") return -toInt(rhs); if (u.op == "+") return +toInt(rhs); throw std::runtime_error(("Unknown unary op: " + u.op).toStdString()); } if (std::holds_alternative(e.node)) { const auto& b = std::get(e.node); QVariant lv = evalExpr(rt, *b.lhs); QVariant rv = evalExpr(rt, *b.rhs); const QString& op = b.op; // comparisons/logical if (op == "==") return lv == rv; if (op == "!=") return lv != rv; if (op == "<") return toInt(lv) < toInt(rv); if (op == "<=") return toInt(lv) <= toInt(rv); if (op == ">") return toInt(lv) > toInt(rv); if (op == ">=") return toInt(lv) >= toInt(rv); if (op == "&&") return toBool(lv) && toBool(rv); if (op == "||") return toBool(lv) || toBool(rv); // Handle string concatenation for + if (op == "+") { // If either operand is a string, do string concatenation if (lv.typeId() == QMetaType::QString || rv.typeId() == QMetaType::QString) { return lv.toString() + rv.toString(); } // Otherwise do integer addition return toInt(lv) + toInt(rv); } // arithmetic/bitwise const qint64 a = toInt(lv); const qint64 c = toInt(rv); if (op == "-") return a - c; if (op == "*") return a * c; if (op == "/") return c == 0 ? 0 : (a / c); if (op == "%") return c == 0 ? 0 : (a % c); if (op == "<<") return a << c; if (op == ">>") return a >> c; if (op == "&") return a & c; if (op == "^") return a ^ c; if (op == "|") return a | c; throw std::runtime_error(("Unknown binary op: " + op).toStdString()); } if (std::holds_alternative(e.node)) { return evalCall(rt, std::get(e.node)); } if (std::holds_alternative(e.node)) { return evalPipe(rt, std::get(e.node)); } throw std::runtime_error("Unknown expression node"); } void Interpreter::execBlock(Runtime& rt, const QVector& body) const { for (const auto& s : body) execStmt(rt, *s); } void Interpreter::execStmt(Runtime& rt, const Stmt& s) const { if (std::holds_alternative(s.node)) { const auto& rs = std::get(s.node); QVariant v = readScalar(rt, rs.type); rt.vars[rs.name] = v; return; } if (std::holds_alternative(s.node)) { const auto& sk = std::get(s.node); const qint64 n = toInt(evalExpr(rt, *sk.count)); if (n < 0) throw std::runtime_error("skip n must be >= 0"); const int skipped = rt.in->skipRawData(int(n)); if (skipped != int(n)) throw std::runtime_error("Unexpected EOF during skip"); return; } if (std::holds_alternative(s.node)) { const auto& al = std::get(s.node); const qint64 n = toInt(evalExpr(rt, *al.n)); if (n <= 0) throw std::runtime_error("align(n): n must be > 0"); QIODevice* dev = rt.in->device(); const qint64 pos = dev->pos(); const qint64 mod = pos % n; if (mod != 0) { const qint64 pad = n - mod; const int skipped = rt.in->skipRawData(int(pad)); if (skipped != int(pad)) throw std::runtime_error("Unexpected EOF during align"); } return; } if (std::holds_alternative(s.node)) { const auto& se = std::get(s.node); const qint64 p = toInt(evalExpr(rt, *se.pos)); if (p < 0) throw std::runtime_error("seek(pos): position must be >= 0"); QIODevice* dev = rt.in->device(); if (!dev->seek(p)) throw std::runtime_error("seek(pos) failed"); return; } if (std::holds_alternative(s.node)) { const auto& as = std::get(s.node); QVariant v = evalExpr(rt, *as.value); rt.vars[as.name] = v; // Log assignments of key fields for debugging if (as.name == "_name" || as.name == "asset_type" || as.name == "_type") { LogManager::instance().addEntry(QString("[ASSIGN] %1 = '%2'") .arg(as.name) .arg(v.toString())); } // Store hidden flag if set if (as.ui.hidden) { rt.vars["_hidden"] = true; } return; } if (std::holds_alternative(s.node)) { const auto& iff = std::get(s.node); const bool cond = toBool(evalExpr(rt, *iff.cond)); execBlock(rt, cond ? iff.thenBody : iff.elseBody); return; } if (std::holds_alternative(s.node)) { const auto& wh = std::get(s.node); while (toBool(evalExpr(rt, *wh.cond))) { execBlock(rt, wh.body); } return; } if (std::holds_alternative(s.node)) { const auto& rp = std::get(s.node); qint64 count = toInt(evalExpr(rt, *rp.count)); if (count < 0) count = 0; const qint64 startPos = rt.in->device() ? rt.in->device()->pos() : -1; LogManager::instance().addEntry(QString("[REPEAT] Starting repeat loop: count=%1, pos=0x%2") .arg(count) .arg(startPos, 0, 16)); // provide _i for repeat index const QVariant old = rt.vars.value("_i"); const bool hadOld = rt.vars.contains("_i"); for (qint64 i = 0; i < count; i++) { const qint64 iterPos = rt.in->device() ? rt.in->device()->pos() : -1; LogManager::instance().addEntry(QString("[REPEAT] Iteration %1/%2 at pos 0x%3") .arg(i) .arg(count) .arg(iterPos, 0, 16)); rt.vars["_i"] = i; execBlock(rt, rp.body); } const qint64 endPos = rt.in->device() ? rt.in->device()->pos() : -1; LogManager::instance().addEntry(QString("[REPEAT] Finished repeat loop at pos 0x%1") .arg(endPos, 0, 16)); if (hadOld) rt.vars["_i"] = old; else rt.vars.remove("_i"); return; } if (std::holds_alternative(s.node)) { const auto& fr = std::get(s.node); qint64 start = toInt(evalExpr(rt, *fr.start)); qint64 end = toInt(evalExpr(rt, *fr.end)); if (end < start) end = start; const QVariant old = rt.vars.value(fr.var); const bool hadOld = rt.vars.contains(fr.var); for (qint64 i = start; i < end; i++) { rt.vars[fr.var] = i; execBlock(rt, fr.body); } if (hadOld) rt.vars[fr.var] = old; else rt.vars.remove(fr.var); return; } if (std::holds_alternative(s.node)) { const auto& rq = std::get(s.node); const bool ok = toBool(evalExpr(rt, *rq.cond)); if (!ok) throw std::runtime_error("criteria require failed"); return; } if (std::holds_alternative(s.node)) { const auto& bo = std::get(s.node); rt.order = bo.order; applyByteOrder(rt); return; } if (std::holds_alternative(s.node)) { const auto& cs = std::get(s.node); evalExpr(rt, *cs.call); // Execute function, discard result return; } throw std::runtime_error("Unknown statement node"); }