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>
299 lines
9.4 KiB
C++
299 lines
9.4 KiB
C++
#include "reverseeval.h"
|
|
|
|
InverseChain ReverseEvaluator::analyzeExpression(const Expr& expr, const QString& targetField) {
|
|
Q_UNUSED(targetField);
|
|
InverseChain chain;
|
|
chain.canInvert = analyzeRecursive(expr, chain);
|
|
|
|
if (!chain.canInvert && chain.reason.isEmpty()) {
|
|
chain.reason = "Expression structure not supported for inversion";
|
|
}
|
|
|
|
return chain;
|
|
}
|
|
|
|
bool ReverseEvaluator::analyzeRecursive(const Expr& expr, InverseChain& chain) {
|
|
// Case 1: Simple variable reference - this is the source field
|
|
if (isVariable(expr)) {
|
|
chain.sourceField = getVariableName(expr);
|
|
return true;
|
|
}
|
|
|
|
// Case 2: Literal value - not invertible (no source to modify)
|
|
if (isConstant(expr)) {
|
|
chain.reason = "Expression is a constant with no source field";
|
|
return false;
|
|
}
|
|
|
|
// Case 3: Unary operations
|
|
if (std::holds_alternative<Expr::Unary>(expr.node)) {
|
|
const auto& unary = std::get<Expr::Unary>(expr.node);
|
|
|
|
if (unary.op == "-") {
|
|
// -x → apply negate operation
|
|
if (!analyzeRecursive(*unary.rhs, chain)) {
|
|
return false;
|
|
}
|
|
chain.operations.append({InvertOp::Negate, 0});
|
|
return true;
|
|
}
|
|
else if (unary.op == "~") {
|
|
// ~x → bitwise not is self-inverse, but not in InvertOp
|
|
chain.reason = "Bitwise NOT inversion not implemented";
|
|
return false;
|
|
}
|
|
else if (unary.op == "!") {
|
|
// !x → not invertible (boolean conversion loses information)
|
|
chain.reason = "Logical NOT is not invertible (boolean conversion)";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Case 4: Binary operations
|
|
if (std::holds_alternative<Expr::Binary>(expr.node)) {
|
|
const auto& binary = std::get<Expr::Binary>(expr.node);
|
|
|
|
bool leftIsVar = !isConstant(*binary.lhs);
|
|
bool rightIsVar = !isConstant(*binary.rhs);
|
|
|
|
// If both sides are non-constant, we can't invert (multiple sources)
|
|
if (leftIsVar && rightIsVar) {
|
|
chain.reason = "Expression has multiple variable sources";
|
|
return false;
|
|
}
|
|
|
|
// If both sides are constant, nothing to invert
|
|
if (!leftIsVar && !rightIsVar) {
|
|
chain.reason = "Expression has no variable sources";
|
|
return false;
|
|
}
|
|
|
|
// Get the constant value
|
|
qint64 constVal;
|
|
const Expr* varExpr;
|
|
bool constOnRight;
|
|
|
|
if (leftIsVar) {
|
|
constVal = getConstantValue(*binary.rhs);
|
|
varExpr = binary.lhs.get();
|
|
constOnRight = true;
|
|
} else {
|
|
constVal = getConstantValue(*binary.lhs);
|
|
varExpr = binary.rhs.get();
|
|
constOnRight = false;
|
|
}
|
|
|
|
// First, recurse into the variable side
|
|
if (!analyzeRecursive(*varExpr, chain)) {
|
|
return false;
|
|
}
|
|
|
|
// Now add the inverse operation based on the binary operator
|
|
const QString& op = binary.op;
|
|
|
|
if (op == "+") {
|
|
// x = y + c → y = x - c
|
|
// x = c + y → y = x - c (same inverse)
|
|
chain.operations.append({InvertOp::Sub, constVal});
|
|
return true;
|
|
}
|
|
else if (op == "-") {
|
|
if (constOnRight) {
|
|
// x = y - c → y = x + c
|
|
chain.operations.append({InvertOp::Add, constVal});
|
|
} else {
|
|
// x = c - y → y = c - x
|
|
chain.operations.append({InvertOp::SubFrom, constVal});
|
|
}
|
|
return true;
|
|
}
|
|
else if (op == "*") {
|
|
// x = y * c → y = x / c
|
|
if (constVal == 0) {
|
|
chain.reason = "Cannot invert multiplication by zero";
|
|
return false;
|
|
}
|
|
chain.operations.append({InvertOp::Div, constVal});
|
|
return true;
|
|
}
|
|
else if (op == "/") {
|
|
if (constOnRight) {
|
|
// x = y / c → y = x * c
|
|
chain.operations.append({InvertOp::Mul, constVal});
|
|
} else {
|
|
// x = c / y → y = c / x
|
|
chain.reason = "Division with constant on left is not supported";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else if (op == "%") {
|
|
chain.reason = "Modulo operation is not invertible (infinite solutions)";
|
|
return false;
|
|
}
|
|
else if (op == "<<") {
|
|
if (constOnRight) {
|
|
// x = y << c → y = x >> c
|
|
chain.operations.append({InvertOp::Shr, constVal});
|
|
} else {
|
|
chain.reason = "Left shift with constant on left is not supported";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else if (op == ">>") {
|
|
if (constOnRight) {
|
|
// x = y >> c → y = x << c
|
|
chain.operations.append({InvertOp::Shl, constVal});
|
|
} else {
|
|
chain.reason = "Right shift with constant on left is not supported";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
else if (op == "&") {
|
|
chain.reason = "Bitwise AND is not invertible (bits masked out are lost)";
|
|
return false;
|
|
}
|
|
else if (op == "|") {
|
|
chain.reason = "Bitwise OR is not invertible (cannot determine original bits)";
|
|
return false;
|
|
}
|
|
else if (op == "^") {
|
|
// x = y ^ c → y = x ^ c (XOR is its own inverse!)
|
|
chain.operations.append({InvertOp::BitXor, constVal});
|
|
return true;
|
|
}
|
|
else if (op == "==" || op == "!=" || op == "<" || op == "<=" ||
|
|
op == ">" || op == ">=" || op == "&&" || op == "||") {
|
|
chain.reason = "Comparison/logical operations are not invertible";
|
|
return false;
|
|
}
|
|
else {
|
|
chain.reason = QString("Unknown binary operator: %1").arg(op);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Case 5: Function calls - generally not invertible
|
|
if (std::holds_alternative<Expr::Call>(expr.node)) {
|
|
chain.reason = "Function calls are not invertible";
|
|
return false;
|
|
}
|
|
|
|
// Case 6: Member access (x.field)
|
|
if (std::holds_alternative<Expr::Member>(expr.node)) {
|
|
chain.reason = "Member access expressions are not invertible";
|
|
return false;
|
|
}
|
|
|
|
// Case 7: Pipe operations
|
|
if (std::holds_alternative<Expr::Pipe>(expr.node)) {
|
|
chain.reason = "Pipe expressions are not invertible";
|
|
return false;
|
|
}
|
|
|
|
chain.reason = "Unknown expression type";
|
|
return false;
|
|
}
|
|
|
|
qint64 ReverseEvaluator::computeInverse(const InverseChain& chain, qint64 editedValue) const {
|
|
if (!chain.canInvert) {
|
|
return editedValue; // Can't invert, return as-is
|
|
}
|
|
|
|
qint64 result = editedValue;
|
|
|
|
// Apply operations in order (they were added in the order they need to be applied)
|
|
for (const auto& op_pair : chain.operations) {
|
|
result = applyInverseOp(result, op_pair.first, op_pair.second);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
QVariant ReverseEvaluator::computeInverse(const InverseChain& chain, const QVariant& editedValue) const {
|
|
return QVariant::fromValue(computeInverse(chain, editedValue.toLongLong()));
|
|
}
|
|
|
|
qint64 ReverseEvaluator::applyInverseOp(qint64 value, InvertOp op, qint64 operand) const {
|
|
switch (op) {
|
|
case InvertOp::Identity:
|
|
return value;
|
|
|
|
case InvertOp::Add:
|
|
return value + operand;
|
|
|
|
case InvertOp::Sub:
|
|
return value - operand;
|
|
|
|
case InvertOp::SubFrom:
|
|
// For x = c - y, inverse is y = c - x
|
|
return operand - value;
|
|
|
|
case InvertOp::Mul:
|
|
return value * operand;
|
|
|
|
case InvertOp::Div:
|
|
if (operand == 0) return value; // Avoid division by zero
|
|
return value / operand;
|
|
|
|
case InvertOp::Shl:
|
|
return value << operand;
|
|
|
|
case InvertOp::Shr:
|
|
return value >> operand;
|
|
|
|
case InvertOp::Negate:
|
|
return -value;
|
|
|
|
case InvertOp::BitAnd:
|
|
case InvertOp::BitOr:
|
|
case InvertOp::Modulo:
|
|
// These are non-invertible, should never be called
|
|
return value;
|
|
|
|
case InvertOp::BitXor:
|
|
// XOR is self-inverse
|
|
return value ^ operand;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
bool ReverseEvaluator::isInvertible(const Expr& expr) const {
|
|
InverseChain chain;
|
|
return const_cast<ReverseEvaluator*>(this)->analyzeRecursive(expr, chain);
|
|
}
|
|
|
|
QString ReverseEvaluator::whyNotInvertible(const Expr& expr) const {
|
|
InverseChain chain;
|
|
if (const_cast<ReverseEvaluator*>(this)->analyzeRecursive(expr, chain)) {
|
|
return QString(); // It is invertible
|
|
}
|
|
return chain.reason;
|
|
}
|
|
|
|
bool ReverseEvaluator::isConstant(const Expr& expr) const {
|
|
return std::holds_alternative<Expr::Int>(expr.node) ||
|
|
std::holds_alternative<Expr::String>(expr.node);
|
|
}
|
|
|
|
qint64 ReverseEvaluator::getConstantValue(const Expr& expr) const {
|
|
if (std::holds_alternative<Expr::Int>(expr.node)) {
|
|
return std::get<Expr::Int>(expr.node).v;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool ReverseEvaluator::isVariable(const Expr& expr) const {
|
|
return std::holds_alternative<Expr::Var>(expr.node);
|
|
}
|
|
|
|
QString ReverseEvaluator::getVariableName(const Expr& expr) const {
|
|
if (std::holds_alternative<Expr::Var>(expr.node)) {
|
|
return std::get<Expr::Var>(expr.node).name;
|
|
}
|
|
return QString();
|
|
}
|