mirror of
https://github.com/x64dbg/DataExplorer.git
synced 2026-11-10 13:31:26 -10:00
501 lines
16 KiB
C++
501 lines
16 KiB
C++
#define PATTERN_BUILDING
|
|
|
|
#include "PatternLanguage.h"
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <fmt/format.h>
|
|
#include <fmt/ranges.h>
|
|
|
|
#include <wolv/utils/string.hpp>
|
|
|
|
#include <pl/pattern_language.hpp>
|
|
#include <pl/pattern_visitor.hpp>
|
|
#include <pl/patterns/pattern.hpp>
|
|
#include <pl/patterns/pattern_array_dynamic.hpp>
|
|
#include <pl/patterns/pattern_array_static.hpp>
|
|
#include <pl/patterns/pattern_bitfield.hpp>
|
|
#include <pl/patterns/pattern_boolean.hpp>
|
|
#include <pl/patterns/pattern_character.hpp>
|
|
#include <pl/patterns/pattern_enum.hpp>
|
|
#include <pl/patterns/pattern_float.hpp>
|
|
#include <pl/patterns/pattern_padding.hpp>
|
|
#include <pl/patterns/pattern_pointer.hpp>
|
|
#include <pl/patterns/pattern_signed.hpp>
|
|
#include <pl/patterns/pattern_string.hpp>
|
|
#include <pl/patterns/pattern_struct.hpp>
|
|
#include <pl/patterns/pattern_union.hpp>
|
|
#include <pl/patterns/pattern_unsigned.hpp>
|
|
#include <pl/patterns/pattern_wide_character.hpp>
|
|
#include <pl/patterns/pattern_wide_string.hpp>
|
|
#include <pl/patterns/pattern_error.hpp>
|
|
|
|
using namespace pl;
|
|
|
|
class ApiPatternVisitor : public PatternVisitor
|
|
{
|
|
public:
|
|
explicit ApiPatternVisitor(const PatternRunArgs *visitor)
|
|
: m_args(visitor)
|
|
{
|
|
}
|
|
|
|
void visit(ptrn::PatternBitfieldField &pattern) override
|
|
{
|
|
formatValue(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternBoolean &pattern) override
|
|
{
|
|
formatValue(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternFloat &pattern) override
|
|
{
|
|
formatValue(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternSigned &pattern) override
|
|
{
|
|
formatValue(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternUnsigned &pattern) override
|
|
{
|
|
formatValue(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternCharacter &pattern) override
|
|
{
|
|
formatString(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternEnum &pattern) override
|
|
{
|
|
formatString(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternString &pattern) override
|
|
{
|
|
formatString(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternWideCharacter &pattern) override
|
|
{
|
|
formatString(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternWideString &pattern) override
|
|
{
|
|
formatString(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::Pattern &pattern) override
|
|
{
|
|
formatString(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternPadding &pattern) override
|
|
{
|
|
// TODO: add padding support?
|
|
wolv::util::unused(pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternStruct &pattern) override
|
|
{
|
|
formatObject(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternUnion &pattern) override
|
|
{
|
|
formatObject(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternBitfield &pattern) override
|
|
{
|
|
formatObject(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternArrayDynamic &pattern) override
|
|
{
|
|
formatArray(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternArrayStatic &pattern) override
|
|
{
|
|
formatArray(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternBitfieldArray &pattern) override
|
|
{
|
|
formatArray(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternError &pattern) override
|
|
{
|
|
formatString(&pattern);
|
|
}
|
|
|
|
void visit(ptrn::PatternPointer &pattern) override
|
|
{
|
|
formatPointer(&pattern);
|
|
}
|
|
|
|
void treePush(TreeNode node)
|
|
{
|
|
m_treeStack.push_back(node);
|
|
}
|
|
|
|
void treePop()
|
|
{
|
|
m_treeStack.pop_back();
|
|
}
|
|
|
|
TreeNode treeTop()
|
|
{
|
|
return m_treeStack.empty() ? m_args->root : m_treeStack.back();
|
|
}
|
|
|
|
private:
|
|
static std::string debugPattern(const char *title, const ptrn::Pattern *pattern)
|
|
{
|
|
std::string p;
|
|
p += fmt::format("{}(\n", title);
|
|
p += fmt::format(" offset = {:#x}\n", pattern->getOffset());
|
|
p += fmt::format(" size = {:#x}\n", pattern->getSize());
|
|
p += fmt::format(" variableName = {}\n", pattern->getVariableName()); // name of the variable (or var[array_idx])
|
|
p += fmt::format(" displayName = {}\n",
|
|
pattern->getDisplayName()); // [[name("Whatever")]] (falls back to variable name)
|
|
p += fmt::format(" formattedName = {}\n",
|
|
pattern->getFormattedName()); // formatted type name (falls back to type name)
|
|
p += fmt::format(" typeName = {}\n", pattern->getTypeName()); // name of the type (without struct or array size)
|
|
p += fmt::format(" line = {}\n", pattern->getLine()); // source line
|
|
p += fmt::format(" visibility = {}\n", (int) pattern->getVisibility());
|
|
//p += format(" endian = {}\n", (int) pattern->getEndian());
|
|
//p += format(" section = {}\n", pattern->getSection());
|
|
p += fmt::format(" comment = {}\n", pattern->getComment());
|
|
p += fmt::format(" color = {:#x}\n", pattern->getColor());
|
|
p += fmt::format(" sealed = {}\n", pattern->isSealed());
|
|
p += fmt::format(")");
|
|
return p;
|
|
}
|
|
|
|
TreeNode apiVisit(const char *reason, ptrn::Pattern *pattern, const std::string &value)
|
|
{
|
|
// TODO: array elements,
|
|
auto formattedTypeName = pattern->getFormattedName();
|
|
auto displayName = pattern->getDisplayName();
|
|
auto comment = pattern->getComment();
|
|
VisitInfo info = {
|
|
.type_name = formattedTypeName.c_str(),
|
|
.variable_name = displayName.c_str(),
|
|
.offset = pattern->getOffset(),
|
|
.size = pattern->getSize(),
|
|
.line = pattern->getLine(),
|
|
.color = pattern->getColor(),
|
|
.value = value.c_str(),
|
|
.comment = comment.c_str(),
|
|
.reason = reason,
|
|
.big_endian = pattern->getEndian() == std::endian::big,
|
|
};
|
|
return m_args->visit(m_args->userdata, treeTop(), &info);
|
|
}
|
|
|
|
static std::string readFormatter(ptrn::Pattern *pattern)
|
|
{
|
|
if (const auto &functionName = pattern->getReadFormatterFunction(); !functionName.empty())
|
|
{
|
|
return pattern->toString();
|
|
}
|
|
else
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
void formatValue(ptrn::Pattern *pattern)
|
|
{
|
|
if (pattern->getVisibility() == ptrn::Visibility::Hidden) return;
|
|
if (pattern->getVisibility() == ptrn::Visibility::TreeHidden) return;
|
|
|
|
auto value = readFormatter(pattern);
|
|
if (value.empty())
|
|
{
|
|
// TODO: sealed support?
|
|
|
|
value = std::visit(wolv::util::overloaded{
|
|
// TODO: print full-width
|
|
[&](pl::integral auto value) -> std::string
|
|
{ return fmt::format("0x{:0{}X}\t{}", value, pattern->getSize() * 2, value); },
|
|
[&](std::floating_point auto value) -> std::string
|
|
{ return fmt::format("{}", value); },
|
|
[&](const std::string &value) -> std::string
|
|
{ return fmt::format("\"{}\"", value); },
|
|
[&](bool value) -> std::string
|
|
{ return value ? "true" : "false"; },
|
|
[&](char value) -> std::string
|
|
{ return fmt::format("'{}'", value); },
|
|
[&](const std::shared_ptr<ptrn::Pattern> &value) -> std::string
|
|
{ return fmt::format("\"{}\"", value->toString()); },
|
|
}, pattern->getValue());
|
|
}
|
|
|
|
apiVisit("formatValue", pattern, value);
|
|
}
|
|
|
|
void formatString(ptrn::Pattern *pattern)
|
|
{
|
|
if (pattern->getVisibility() == ptrn::Visibility::Hidden) return;
|
|
if (pattern->getVisibility() == ptrn::Visibility::TreeHidden) return;
|
|
|
|
apiVisit("formatString", pattern, pattern->toString());
|
|
}
|
|
|
|
template<typename T>
|
|
void formatObject(T *pattern)
|
|
{
|
|
if (pattern->getVisibility() == ptrn::Visibility::Hidden) return;
|
|
if (pattern->getVisibility() == ptrn::Visibility::TreeHidden) return;
|
|
|
|
auto objectNode = apiVisit("formatObject", pattern, readFormatter(pattern));
|
|
if (!pattern->isSealed())
|
|
{
|
|
treePush(objectNode);
|
|
pattern->forEachEntry(0, pattern->getEntryCount(), [&](u64, auto member)
|
|
{
|
|
member->accept(*this);
|
|
});
|
|
treePop();
|
|
}
|
|
}
|
|
|
|
template<typename T>
|
|
void formatArray(T *pattern)
|
|
{
|
|
if (pattern->getVisibility() == ptrn::Visibility::Hidden) return;
|
|
if (pattern->getVisibility() == ptrn::Visibility::TreeHidden) return;
|
|
|
|
auto arrayNode = apiVisit("formatArray", pattern, readFormatter(pattern));
|
|
if (!pattern->isSealed())
|
|
{
|
|
treePush(arrayNode);
|
|
pattern->forEachEntry(0, pattern->getEntryCount(), [&](u64, auto member)
|
|
{
|
|
member->accept(*this);
|
|
});
|
|
treePop();
|
|
}
|
|
}
|
|
|
|
void formatPointer(ptrn::PatternPointer *pattern)
|
|
{
|
|
if (pattern->getVisibility() == ptrn::Visibility::Hidden) return;
|
|
if (pattern->getVisibility() == ptrn::Visibility::TreeHidden) return;
|
|
|
|
auto pointerNode = apiVisit("formatPointer", pattern, readFormatter(pattern));
|
|
if (!pattern->isSealed())
|
|
{
|
|
treePush(pointerNode);
|
|
pattern->getPointedAtPattern()->accept(*this);
|
|
treePop();
|
|
}
|
|
}
|
|
|
|
private:
|
|
const PatternRunArgs *m_args = nullptr;
|
|
std::vector<TreeNode> m_treeStack;
|
|
};
|
|
|
|
PatternStatus PatternRun(const PatternRunArgs *args)
|
|
{
|
|
// Create and configure Pattern Language runtime
|
|
PatternLanguage runtime;
|
|
|
|
auto logCallback = [args](auto level, const auto &message)
|
|
{
|
|
std::string stripped = message;
|
|
while (!stripped.empty() && std::isspace(stripped.back()))
|
|
{
|
|
stripped.pop_back();
|
|
}
|
|
if (args->log_handler != nullptr)
|
|
{
|
|
args->log_handler(args->userdata, (LogLevel) (int) level, stripped.c_str());
|
|
}
|
|
else
|
|
{
|
|
switch (level)
|
|
{
|
|
using
|
|
enum core::LogConsole::Level;
|
|
|
|
case Debug:
|
|
fmt::print("[DEBUG] {}\n", stripped);
|
|
break;
|
|
case Info:
|
|
fmt::print("[INFO] {}\n", stripped);
|
|
break;
|
|
case Warning:
|
|
fmt::print("[WARN] {}\n", stripped);
|
|
break;
|
|
case Error:
|
|
fmt::print("[ERROR] {}\n", stripped);
|
|
break;
|
|
}
|
|
}
|
|
};
|
|
runtime.setLogCallback(logCallback);
|
|
|
|
std::vector<std::fs::path> includePaths(args->includes_count);
|
|
for (size_t i = 0; i < includePaths.size(); i++)
|
|
{
|
|
#ifdef _WIN32
|
|
const auto setIncludePath = [&](const auto &includePath)
|
|
{
|
|
using IncludePathType = std::remove_cvref_t<decltype(includePath)>;
|
|
if constexpr (requires(const IncludePathType &path) { path.has_value(); })
|
|
{
|
|
if (includePath.has_value())
|
|
{
|
|
includePaths[i] = *includePath;
|
|
}
|
|
else
|
|
{
|
|
includePaths[i] = args->includes_data[i];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
includePaths[i] = includePath;
|
|
}
|
|
};
|
|
setIncludePath(wolv::util::utf8ToWstring(args->includes_data[i]));
|
|
#else
|
|
includePaths[i] = args->includes_data[i];
|
|
#endif
|
|
}
|
|
runtime.setIncludePaths(includePaths);
|
|
|
|
for (size_t i = 0; i < args->defines_count; i++)
|
|
{
|
|
runtime.addDefine(args->defines_data[i]);
|
|
}
|
|
|
|
auto dangerCallback = [args]()
|
|
{
|
|
return args->allow_dangerous_functions;
|
|
};
|
|
runtime.setDangerousFunctionCallHandler(dangerCallback);
|
|
auto dataCallback = [args](u64 address, void *buffer, size_t size)
|
|
{
|
|
if (!args->data_source(args->userdata, address, buffer, size))
|
|
{
|
|
core::err::E0011.throwError(fmt::format("Failed to read address 0x{:X}[0x{:X}].", address, size));
|
|
}
|
|
};
|
|
runtime.setDataSource(args->base, args->size, dataCallback);
|
|
|
|
// Execute pattern file
|
|
auto filename = args->filename;
|
|
if (filename == nullptr)
|
|
{
|
|
filename = api::Source::DefaultSource;
|
|
}
|
|
if (const auto exitCode = runtime.executeString(args->source, filename); exitCode != 0)
|
|
{
|
|
auto compileErrors = runtime.getCompileErrors();
|
|
if (!compileErrors.empty())
|
|
{
|
|
if (args->compile_error == nullptr)
|
|
{
|
|
fmt::print("Compilation failed\n");
|
|
}
|
|
|
|
for (const auto &error: compileErrors)
|
|
{
|
|
auto pretty = error.format();
|
|
if (args->compile_error == nullptr)
|
|
{
|
|
fmt::print("{}\n", pretty);
|
|
}
|
|
else
|
|
{
|
|
const auto &location = error.getLocation();
|
|
CompileError cerror = {
|
|
.location = {
|
|
.file = location.source->source.c_str(),
|
|
.line = location.line,
|
|
.column = location.column,
|
|
.length = location.length,
|
|
},
|
|
.message = error.getMessage().c_str(),
|
|
.description = error.getMessage().c_str(),
|
|
.pretty = pretty.c_str(),
|
|
};
|
|
args->compile_error(args->userdata, &cerror);
|
|
}
|
|
}
|
|
return PatternCompileError;
|
|
}
|
|
else
|
|
{
|
|
std::string pretty;
|
|
EvalError cerror = {
|
|
.location = {
|
|
.file = filename,
|
|
.line = 0,
|
|
.column = 0,
|
|
.length = 0,
|
|
},
|
|
.pretty = nullptr,
|
|
};
|
|
|
|
if (const auto &error = runtime.getEvalError(); error.has_value())
|
|
{
|
|
cerror.location.line = error->line;
|
|
cerror.location.column = error->column;
|
|
pretty = error->message;
|
|
}
|
|
else
|
|
{
|
|
pretty = fmt::format("Pattern execution failed with exit code {} without an evaluation error.", exitCode);
|
|
}
|
|
|
|
cerror.pretty = pretty.c_str();
|
|
if (args->eval_error == nullptr)
|
|
{
|
|
fmt::print("Pattern Error: {}:{} -> {}\n", cerror.location.line, cerror.location.column, pretty);
|
|
}
|
|
else
|
|
{
|
|
args->eval_error(args->userdata, &cerror);
|
|
}
|
|
|
|
return PatternEvalError;
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
ApiPatternVisitor visitor(args);
|
|
for (const auto &pattern: runtime.getPatterns())
|
|
{
|
|
pattern->accept(visitor);
|
|
}
|
|
return PatternSuccess;
|
|
}
|
|
catch (const std::exception &e)
|
|
{
|
|
if (args->log_handler != nullptr)
|
|
{
|
|
args->log_handler(args->userdata, LogLevelError, e.what());
|
|
}
|
|
else
|
|
{
|
|
fmt::print("[ERROR] {}\n", e.what());
|
|
}
|
|
return PatternEvalError;
|
|
}
|
|
}
|