feat: disasm popup, symbol separation, context menu improvements, RVA fixes

- Add Fadec x86 disassembler with hover popup for FuncPtr/void Pointer nodes
- Separate pointer symbol from address: // prefix, green comment coloring,
  independent hover/click zones (address triggers popup, symbol is passive)
- Fix RVA margin and inline local offset for pointer-expanded vtable children
  using ptrBase field threaded through composition
- Expand multi-select context menu with quick-convert, duplicate, copy address
- Remove Edit Value from hex node context menu
- Fix heatmap flickering on hex nodes (remove per-byte alternation)
- Fix popup repositioning when moving mouse between lines
- Truncate disasm popup to 6 lines with ... indicator
- Add BUILD_UI_TESTS option to skip widget tests on headless CI
- Add test_disasm with 35 test cases for disassembly and hex dump
- Add KUSER_SHARED_DATA example .rcx file
This commit is contained in:
IChooseYou
2026-02-18 07:10:13 -07:00
parent 91633169a0
commit 444ba34fa3
36 changed files with 17291 additions and 110 deletions

76
src/disasm.cpp Normal file
View File

@@ -0,0 +1,76 @@
#include "disasm.h"
extern "C" {
#include <fadec.h>
}
namespace rcx {
QString disassemble(const QByteArray& bytes, uint64_t baseAddr, int bitness, int maxBytes) {
if (bytes.isEmpty() || (bitness != 32 && bitness != 64))
return {};
int len = qMin((int)bytes.size(), maxBytes);
const auto* buf = reinterpret_cast<const uint8_t*>(bytes.constData());
QString result;
int off = 0;
while (off < len) {
FdInstr instr;
int ret = fd_decode(buf + off, len - off, bitness, baseAddr + off, &instr);
if (ret < 0)
break;
char fmtBuf[128];
fd_format(&instr, fmtBuf, sizeof(fmtBuf));
if (!result.isEmpty())
result += QLatin1Char('\n');
result += QStringLiteral("%1 %2")
.arg(baseAddr + off, bitness == 64 ? 16 : 8, 16, QLatin1Char('0'))
.arg(QString::fromLatin1(fmtBuf));
off += ret;
}
return result;
}
QString hexDump(const QByteArray& bytes, uint64_t baseAddr, int maxBytes) {
if (bytes.isEmpty())
return {};
int len = qMin((int)bytes.size(), maxBytes);
QString result;
for (int off = 0; off < len; off += 16) {
int lineLen = qMin(16, len - off);
if (!result.isEmpty())
result += QLatin1Char('\n');
// Address
bool wide = (baseAddr + len > 0xFFFFFFFFULL);
result += QStringLiteral("%1 ").arg(baseAddr + off, wide ? 16 : 8, 16, QLatin1Char('0'));
// Hex bytes
for (int i = 0; i < 16; i++) {
if (i < lineLen) {
uint8_t b = static_cast<uint8_t>(bytes[off + i]);
result += QStringLiteral("%1 ").arg(b, 2, 16, QLatin1Char('0'));
} else {
result += QStringLiteral(" ");
}
if (i == 7) result += QLatin1Char(' ');
}
// ASCII
result += QLatin1Char(' ');
for (int i = 0; i < lineLen; i++) {
char c = bytes[off + i];
result += (c >= 0x20 && c < 0x7f) ? QLatin1Char(c) : QLatin1Char('.');
}
}
return result;
}
} // namespace rcx