1
0
Fork 0

Initial work on hex_viewer

This commit is contained in:
Duncan Ogilvie 2025-04-22 10:07:59 +02:00
parent 5c3c8087db
commit ede73f1663
39 changed files with 3527 additions and 97 deletions

119
src/cross/CMakeLists.txt generated
View File

@ -21,9 +21,14 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
# Create a configure-time dependency on cmake.toml to improve IDE support
configure_file(cmake.toml cmake.toml COPYONLY)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS cmake.toml)
endif()
# Options
option(LIBPL_ENABLE_CLI "" OFF)
option(LIBPL_ENABLE_TESTS "" OFF)
option(LIBWOLV_ENABLE_TESTS "" OFF)
project(cross)
include("widgets/Qt.cmake")
@ -50,27 +55,27 @@ set(CMAKE_FOLDER ${CMKR_CMAKE_FOLDER})
# Target: minidump
set(minidump_SOURCES
cmake.toml
"minidump/FileParser.cpp"
"minidump/GotoDialog.cpp"
"minidump/MainWindow.cpp"
"minidump/MiniDisassembly.cpp"
"minidump/MiniHexDump.cpp"
"minidump/MiniMemoryMap.cpp"
"minidump/Navigation.cpp"
"minidump/REToolSync.cpp"
"minidump/main.cpp"
"minidump/FileParser.h"
"minidump/GotoDialog.cpp"
"minidump/GotoDialog.h"
"minidump/MainWindow.cpp"
"minidump/MainWindow.h"
"minidump/MainWindow.ui"
"minidump/MiniDisassembly.cpp"
"minidump/MiniDisassembly.h"
"minidump/MiniHexDump.cpp"
"minidump/MiniHexDump.h"
"minidump/MiniMemoryMap.cpp"
"minidump/MiniMemoryMap.h"
"minidump/Navigation.cpp"
"minidump/Navigation.h"
"minidump/REToolSync.cpp"
"minidump/REToolSync.h"
"minidump/main.cpp"
"minidump/udmp-parser.h"
"minidump/udmp-utils.h"
"minidump/MainWindow.ui"
cmake.toml
)
qt_executable(minidump ${minidump_SOURCES})
@ -93,22 +98,22 @@ endif()
# Target: remote_table
set(remote_table_SOURCES
cmake.toml
"remote_table/JsonRpcClient.cpp"
"remote_table/MainWindow.cpp"
"remote_table/OverlayFrame.cpp"
"remote_table/RemoteTable.cpp"
"remote_table/TableServer.cpp"
"remote_table/main.cpp"
"remote_table/JsonRpcClient.h"
"remote_table/MainWindow.cpp"
"remote_table/MainWindow.h"
"remote_table/MainWindow.ui"
"remote_table/OverlayFrame.cpp"
"remote_table/OverlayFrame.h"
"remote_table/OverlayFrame.ui"
"remote_table/RemoteTable.cpp"
"remote_table/RemoteTable.h"
"remote_table/TableRpcData.h"
"remote_table/TableServer.cpp"
"remote_table/TableServer.h"
"remote_table/MainWindow.ui"
"remote_table/OverlayFrame.ui"
"remote_table/json.hpp"
cmake.toml
"remote_table/main.cpp"
)
qt_executable(remote_table ${remote_table_SOURCES})
@ -129,14 +134,14 @@ endif()
# Target: struct_viewer
set(struct_viewer_SOURCES
"struct_viewer/MainWindow.cpp"
"struct_viewer/StructWidget.cpp"
"struct_viewer/main.cpp"
"struct_viewer/MainWindow.h"
"struct_viewer/StructWidget.h"
"struct_viewer/MainWindow.ui"
"struct_viewer/StructWidget.ui"
cmake.toml
"struct_viewer/MainWindow.cpp"
"struct_viewer/MainWindow.h"
"struct_viewer/MainWindow.ui"
"struct_viewer/StructWidget.cpp"
"struct_viewer/StructWidget.h"
"struct_viewer/StructWidget.ui"
"struct_viewer/main.cpp"
)
qt_executable(struct_viewer ${struct_viewer_SOURCES})
@ -156,3 +161,65 @@ if(NOT CMKR_VS_STARTUP_PROJECT)
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT struct_viewer)
endif()
# Target: hex_viewer
set(hex_viewer_SOURCES
cmake.toml
"hex_viewer/CodeEditor.cpp"
"hex_viewer/CodeEditor.h"
"hex_viewer/DataExplorerDialog.cpp"
"hex_viewer/DataExplorerDialog.h"
"hex_viewer/DataExplorerDialog.ui"
"hex_viewer/File.cpp"
"hex_viewer/File.h"
"hex_viewer/GotoDialog.cpp"
"hex_viewer/GotoDialog.h"
"hex_viewer/MainWindow.cpp"
"hex_viewer/MainWindow.h"
"hex_viewer/MainWindow.ui"
"hex_viewer/MiniHexDump.cpp"
"hex_viewer/MiniHexDump.h"
"hex_viewer/Navigation.cpp"
"hex_viewer/Navigation.h"
"hex_viewer/PatternHighlighter.cpp"
"hex_viewer/PatternHighlighter.h"
"hex_viewer/PatternLanguage.cpp"
"hex_viewer/PatternLanguage.h"
"hex_viewer/RichTextItemDelegate.cpp"
"hex_viewer/RichTextItemDelegate.h"
"hex_viewer/StructWidget.cpp"
"hex_viewer/StructWidget.h"
"hex_viewer/StructWidget.ui"
"hex_viewer/Styled.h"
"hex_viewer/main.cpp"
)
qt_executable(hex_viewer ${hex_viewer_SOURCES})
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${hex_viewer_SOURCES})
target_compile_features(hex_viewer PRIVATE
cxx_std_20
)
target_include_directories(hex_viewer PRIVATE
hex_viewer
)
if(NOT TARGET libpl)
message(FATAL_ERROR "Target \"libpl\" referenced by \"hex_viewer\" does not exist!")
endif()
if(NOT TARGET libpl-gen)
message(FATAL_ERROR "Target \"libpl-gen\" referenced by \"hex_viewer\" does not exist!")
endif()
target_link_libraries(hex_viewer PRIVATE
x64dbg::widgets
libpl
libpl-gen
fmt::fmt-header-only
)
get_directory_property(CMKR_VS_STARTUP_PROJECT DIRECTORY ${PROJECT_SOURCE_DIR} DEFINITION VS_STARTUP_PROJECT)
if(NOT CMKR_VS_STARTUP_PROJECT)
set_property(DIRECTORY ${PROJECT_SOURCE_DIR} PROPERTY VS_STARTUP_PROJECT hex_viewer)
endif()

View File

@ -1,57 +1,80 @@
# Reference: https://build-cpp.github.io/cmkr/cmake-toml
[cmake]
version = "3.19"
[project]
name = "cross"
include-after = ["widgets/Qt.cmake"]
[subdir.widgets]
[subdir.vendor]
[template.qt_executable]
type = "executable"
add-function = "qt_executable"
pass-sources = true
compile-features = ["cxx_std_20"]
[target.minidump]
type = "qt_executable"
sources = [
"minidump/*.cpp",
"minidump/*.h",
"minidump/*.ui",
"minidump/*.qrc",
]
link-libraries = [
"x64dbg::widgets",
"cpp-httplib",
"linux-pe",
]
[target.remote_table]
type = "qt_executable"
sources = [
"remote_table/*.cpp",
"remote_table/*.h",
"remote_table/*.ui",
"remote_table/*.qrc",
"remote_table/json.hpp",
]
link-libraries = [
"x64dbg::widgets",
]
[target.struct_viewer]
type = "qt_executable"
sources = [
"struct_viewer/*.cpp",
"struct_viewer/*.h",
"struct_viewer/*.ui",
"struct_viewer/*.qrc",
]
link-libraries = [
"x64dbg::widgets",
"btparser",
]
# Reference: https://build-cpp.github.io/cmkr/cmake-toml
[cmake]
version = "3.19"
[project]
name = "cross"
include-after = ["widgets/Qt.cmake"]
[options]
LIBPL_ENABLE_CLI = false
LIBPL_ENABLE_TESTS = false
LIBWOLV_ENABLE_TESTS = false
[subdir.widgets]
[subdir.vendor]
[template.qt_executable]
type = "executable"
add-function = "qt_executable"
pass-sources = true
compile-features = ["cxx_std_20"]
[target.minidump]
type = "qt_executable"
sources = [
"minidump/*.cpp",
"minidump/*.h",
"minidump/*.ui",
"minidump/*.qrc",
]
link-libraries = [
"x64dbg::widgets",
"cpp-httplib",
"linux-pe",
]
[target.remote_table]
type = "qt_executable"
sources = [
"remote_table/*.cpp",
"remote_table/*.h",
"remote_table/*.ui",
"remote_table/*.qrc",
"remote_table/json.hpp",
]
link-libraries = [
"x64dbg::widgets",
]
[target.struct_viewer]
type = "qt_executable"
sources = [
"struct_viewer/*.cpp",
"struct_viewer/*.h",
"struct_viewer/*.ui",
"struct_viewer/*.qrc",
]
link-libraries = [
"x64dbg::widgets",
"btparser",
]
[target.hex_viewer]
type = "qt_executable"
sources = [
"hex_viewer/*.cpp",
"hex_viewer/*.h",
"hex_viewer/*.ui",
"hex_viewer/*.qrc",
]
link-libraries = [
"x64dbg::widgets",
"::libpl",
"::libpl-gen",
"fmt::fmt-header-only",
]
private-include-directories = [
"hex_viewer",
]

View File

@ -2,7 +2,7 @@ include_guard()
# Change these defaults to point to your infrastructure if desired
set(CMKR_REPO "https://github.com/build-cpp/cmkr" CACHE STRING "cmkr git repository" FORCE)
set(CMKR_TAG "v0.2.23" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
set(CMKR_TAG "v0.2.44" CACHE STRING "cmkr git tag (this needs to be available forever)" FORCE)
set(CMKR_COMMIT_HASH "" CACHE STRING "cmkr git commit hash (optional)" FORCE)
# To bootstrap/generate a cmkr project: cmake -P cmkr.cmake

View File

@ -0,0 +1,288 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "CodeEditor.h"
#include <QPainter>
#include <QTextBlock>
#include <QRegularExpression>
#include <QDebug>
#include <QAction>
CodeEditor::CodeEditor(QWidget* parent)
: QPlainTextEdit(parent)
{
setWordWrapMode(QTextOption::NoWrap);
lineNumberArea = new LineNumberArea(this);
connect(this, &CodeEditor::blockCountChanged, this, &CodeEditor::updateLineNumberAreaWidth);
connect(this, &CodeEditor::updateRequest, this, &CodeEditor::updateLineNumberArea);
connect(this, &CodeEditor::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine);
updateLineNumberAreaWidth(0);
highlightCurrentLine();
}
int CodeEditor::lineNumberAreaWidth()
{
int digits = 1;
int max = qMax(1, blockCount());
while (max >= 10)
{
max /= 10;
++digits;
}
return 3 + measureFontWidth("7") * digits;
}
void CodeEditor::setErrorLine(int line)
{
mErrorLine = line;
highlightCurrentLine();
}
void CodeEditor::setTokenHighlights(const QString& token, const QList<int>& lines)
{
mHighlightToken = token;
mHighlightLines = lines;
highlightCurrentLine(); // TODO: this will now be done twice
}
void CodeEditor::setHackedReadonly(bool readonly)
{
mHackedReadonly = readonly;
}
int CodeEditor::measureFontWidth(const QString& str)
{
auto metrics = fontMetrics();
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
return metrics.horizontalAdvance(str);
#else
return metrics.width(str);
#endif
}
void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */)
{
setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}
void CodeEditor::updateLineNumberArea(const QRect& rect, int dy)
{
if (dy)
lineNumberArea->scroll(0, dy);
else
lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
if (rect.contains(viewport()->rect()))
updateLineNumberAreaWidth(0);
}
void CodeEditor::resizeEvent(QResizeEvent* e)
{
QPlainTextEdit::resizeEvent(e);
QRect cr = contentsRect();
lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
}
void CodeEditor::changeEvent(QEvent* event)
{
if(event->type() == QEvent::StyleChange)
{
highlightCurrentLine();
}
else if(event->type() == QEvent::FontChange)
{
setTabStopDistance(measureFontWidth(" "));
}
QPlainTextEdit::changeEvent(event);
}
void CodeEditor::keyPressEvent(QKeyEvent* event)
{
// Shift+Enter does something sus
if((event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) && event->modifiers() == Qt::ShiftModifier)
{
return;
}
if(mHackedReadonly)
{
// https://doc.qt.io/qt-6/qplaintextedit.html#read-only-key-bindings
switch(event->key())
{
case Qt::Key_Copy:
case Qt::Key_Left:
case Qt::Key_Right:
case Qt::Key_Up:
case Qt::Key_Down:
case Qt::Key_PageUp:
case Qt::Key_PageDown:
case Qt::Key_Home:
case Qt::Key_End:
case Qt::Key_Shift:
case Qt::Key_Alt:
case Qt::Key_Control:
case Qt::Key_Meta:
break;
case Qt::Key_A:
// Select all
if(event->modifiers() & (Qt::ControlModifier | Qt::MetaModifier))
break;
case Qt::Key_C:
// Copy
if (event->modifiers() & (Qt::ControlModifier | Qt::MetaModifier))
break;
default:
{
QKeySequence seq(event->modifiers() | event->key());
Q_FOREACH(auto action, actions())
{
auto shortcut = action->shortcut();
if(!shortcut.isEmpty() && shortcut == seq)
{
action->trigger();
event->accept();
break;
}
}
return;
}
}
}
QPlainTextEdit::keyPressEvent(event);
}
void CodeEditor::highlightCurrentLine()
{
QList<QTextEdit::ExtraSelection> extraSelections;
if (!mHackedReadonly /* isReadOnly() */)
{
QTextEdit::ExtraSelection selection;
selection.format.setBackground(selectedLineHighlightColor());
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = textCursor();
selection.cursor.clearSelection();
extraSelections.append(selection);
}
if (mErrorLine != -1)
{
QTextEdit::ExtraSelection selection;
selection.format.setBackground(errorLineHighlightColor());
selection.format.setProperty(QTextFormat::FullWidthSelection, true);
selection.cursor = textCursor();
selection.cursor.clearSelection();
selection.cursor.setPosition(document()->findBlockByLineNumber(mErrorLine - 1).position());
extraSelections.append(selection);
}
if(!mHighlightToken.isEmpty())
{
QTextCursor cursor = textCursor();
if(mHighlightLines.empty())
{
// Global selection
auto plainText = toPlainText();
QStringMatcher matcher(mHighlightToken);
for(auto from = 0; ;)
{
auto index = matcher.indexIn(plainText, from);
if(index == -1)
break;
QTextEdit::ExtraSelection currentWord;
currentWord.format.setFontUnderline(true);
cursor.setPosition(index, QTextCursor::MoveAnchor);
cursor.setPosition(index + mHighlightToken.length(), QTextCursor::KeepAnchor);
currentWord.cursor = cursor;
extraSelections.append(currentWord);
from = index + mHighlightToken.length();
}
}
}
setExtraSelections(extraSelections);
}
void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent* event)
{
QPainter painter(lineNumberArea);
painter.setFont(font());
painter.fillRect(event->rect(), lineNumberBackgroundColor());
QTextBlock block = firstVisibleBlock();
int blockNumber = block.blockNumber();
int top = qRound(blockBoundingGeometry(block).translated(contentOffset()).top());
int bottom = top + qRound(blockBoundingRect(block).height());
while (block.isValid() && top <= event->rect().bottom())
{
if (block.isVisible() && bottom >= event->rect().top())
{
QString number = QString::number(blockNumber + 1);
painter.setPen(lineNumberColor());
painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(), Qt::AlignRight, number);
}
block = block.next();
top = bottom;
bottom = top + qRound(blockBoundingRect(block).height());
++blockNumber;
}
}

View File

@ -0,0 +1,140 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#pragma once
#include <QPlainTextEdit>
#include "Styled.h"
QT_BEGIN_NAMESPACE
class QPaintEvent;
class QResizeEvent;
class QSize;
class QWidget;
QT_END_NAMESPACE
class LineNumberArea;
class CodeEditor : public QPlainTextEdit, Styled<CodeEditor>
{
Q_OBJECT
public:
CSS_COLOR(selectedLineHighlightColor, "#DFDBBE");
CSS_COLOR(errorLineHighlightColor, "#FF9999");
CSS_COLOR(lineNumberColor, "#8799C4");
CSS_COLOR(lineNumberBackgroundColor, "#fefff7");
CSS_COLOR(keywordColor, "#800000");
CSS_COLOR(instructionColor, "#333F47");
CSS_COLOR(globalVariableColor, "#1BA7B3");
CSS_COLOR(localVariableColor, "#E90B55");
CSS_COLOR(constantColor, "#A34784");
CSS_COLOR(integerTypeColor, "#1BA7B3");
CSS_COLOR(commentColor, "#8799C4");
CSS_COLOR(metadataColor, "#8799C4");
CSS_COLOR(functionColor, "#006793");
CSS_COLOR(stringColor, "#008000");
CSS_COLOR(operatorColor, "#C2492E");
public:
CodeEditor(QWidget* parent = nullptr);
void lineNumberAreaPaintEvent(QPaintEvent* event);
int lineNumberAreaWidth();
void setErrorLine(int line);
void setTokenHighlights(const QString& token, const QList<int>& lines);
void setHackedReadonly(bool readonly);
int measureFontWidth(const QString& str);
protected:
void resizeEvent(QResizeEvent* event) override;
void changeEvent(QEvent* event) override;
void keyPressEvent(QKeyEvent* event) override;
private slots:
void updateLineNumberAreaWidth(int newBlockCount);
void highlightCurrentLine();
void updateLineNumberArea(const QRect& rect, int dy);
private:
QWidget* lineNumberArea;
int mErrorLine = -1;
QString mHighlightToken;
QList<int> mHighlightLines;
bool mHackedReadonly = false;
};
class LineNumberArea : public QWidget
{
public:
LineNumberArea(CodeEditor* editor)
: QWidget(editor)
, codeEditor(editor)
{
}
QSize sizeHint() const override
{
return QSize(codeEditor->lineNumberAreaWidth(), 0);
}
protected:
void paintEvent(QPaintEvent* event) override
{
codeEditor->lineNumberAreaPaintEvent(event);
}
private:
CodeEditor* codeEditor;
};

View File

@ -0,0 +1,287 @@
#include "DataExplorerDialog.h"
#include "ui_DataExplorerDialog.h"
#include <QMessageBox>
#include <QDir>
#include <QTextBlock>
#include <QSettings>
#include "PatternHighlighter.h"
#include "Bridge.h"
#include <unordered_map>
struct PatternVisitor
{
int mNextId = 100;
uint64_t mCurrentBase = 0;
std::vector<char> mStringPool;
std::map<int, std::pair<size_t, size_t>> mNames;
bool valueCallback(const TYPEDESCRIPTOR* type, char* dest, size_t* destCount)
{
auto itr = mNames.find(type->id);
if(itr == mNames.end())
return false;
const auto& str = itr->second;
if(*destCount <= str.second)
{
*destCount = str.second + 1;
return false;
}
strcpy_s(dest, *destCount, mStringPool.data() + str.first);
return true;
}
TreeNode visit(TreeNode parent, const VisitInfo& info)
{
if(parent == nullptr)
{
mCurrentBase = info.offset;
}
auto id = mNextId++;
{
auto valueLen = strlen(info.value);
auto valueIndex = mStringPool.size();
mStringPool.resize(mStringPool.size() + valueLen + 1);
memcpy(mStringPool.data() + valueIndex, info.value, valueLen + 1);
mNames[id] = {valueIndex, valueLen};
}
std::string name = info.type_name;
if(*info.variable_name)
{
name += " ";
name += info.variable_name;
}
TYPEDESCRIPTOR td = {};
td.expanded = true;
td.reverse = info.big_endian;
td.name = name.c_str();
td.addr = mCurrentBase;
td.id = id;
td.sizeBits = info.size * 8;
// TODO: detect primitive types and exclude them from the toString callback
td.offset = info.offset - mCurrentBase;
td.callback = [](const TYPEDESCRIPTOR *type, char* dest, size_t* destCount)
{
return ((PatternVisitor*)type->userdata)->valueCallback(type, dest, destCount);
};
td.userdata = this;
auto node = nullptr; // TODO
//auto node = GuiTypeAddNode(parent, &td);
#ifdef _DEBUG
_plugin_logprintf("[%p->%p] %s %s |%s| (address: 0x%llX, size: 0x%llX, line: %d)\n",
parent,
node,
info.type_name,
info.variable_name,
info.value,
info.offset,
info.size,
info.line
);
#endif
return node;
}
void clear()
{
mCurrentBase = 0;
mStringPool.clear();
mNames.clear();
}
};
DataExplorerDialog::DataExplorerDialog(QWidget* parent) : QDialog(parent), ui(new Ui::DataExplorerDialog)
{
ui->setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setFixedSize(size());
ui->codeEdit->setFont(QFont("Courier New", 8));
auto action = new QAction(ui->codeEdit);
action->setShortcut(QKeySequence("Ctrl+R"));
ui->codeEdit->addAction(action);
connect(action, &QAction::triggered, ui->buttonParse, &QPushButton::click);
mVisitor = new PatternVisitor;
mHighlighter = new PatternHighlighter(ui->codeEdit, ui->codeEdit->document());
// Restore settings
{
QSettings settings("DataExplorer");
auto cursor = ui->codeEdit->textCursor();
auto savedPosition = settings.value("cursor", cursor.position()).toInt();
auto savedCode = settings.value("code", ui->codeEdit->toPlainText()).toString();
ui->codeEdit->setPlainText(savedCode);
cursor.setPosition(savedPosition);
ui->codeEdit->setTextCursor(cursor);
restoreGeometry(settings.value("geometry").toByteArray());
if(settings.value("visible").toBool())
show();
}
}
DataExplorerDialog::~DataExplorerDialog()
{
delete ui;
delete mVisitor;
delete mHighlighter;
}
void DataExplorerDialog::closeEvent(QCloseEvent* event)
{
QSettings settings("DataExplorer");
settings.setValue("code", ui->codeEdit->toPlainText());
settings.setValue("cursor", ui->codeEdit->textCursor().position());
settings.setValue("geometry", saveGeometry());
settings.setValue("visible", isVisible());
QDialog::closeEvent(event);
}
void DataExplorerDialog::changeEvent(QEvent* event)
{
if (event->type() == QEvent::StyleChange)
{
duint dark = 0;
BridgeSettingGetUint("Colors", "DarkTitleBar", &dark);
//_plugin_logprintf("[changeEvent] dark=%d\n", dark != 0);
if (mHighlighter)
{
ensurePolished();
mHighlighter->refreshColors(ui->codeEdit);
mHighlighter->setDocument(nullptr);
mHighlighter->setDocument(ui->codeEdit->document());
}
}
QDialog::changeEvent(event);
}
void DataExplorerDialog::on_buttonParse_pressed()
{
// TODO: change this?
QString includeDir = QApplication::applicationDirPath();
includeDir += "\\includes";
auto includeUtf8 = includeDir.toUtf8();
const char* includes[1] = {includeUtf8.constData()};
auto source = ui->codeEdit->toPlainText().toUtf8();
PatternRunArgs args = {};
args.userdata = this;
args.root = nullptr;
args.source = source.constData();
args.filename = "<source>";
args.base = 0x10000;
#ifdef _WIN64
args.size = 0x7FFFFFFFFFFFFFFF;
#else
args.size = 0x7FFFFFFF;
#endif // _WIN64
args.includes_count = std::size(includes);
args.includes_data = includes;
args.log_handler = [](void* userdata, LogLevel level, const char* message)
{
((DataExplorerDialog*)userdata)->logHandler(level, message);
};
args.compile_error = [](void* userdata, const CompileError* error)
{
((DataExplorerDialog*)userdata)->compileError(*error);
};
args.eval_error = [](void *userdata, const EvalError* error)
{
((DataExplorerDialog*)userdata)->evalError(*error);
};
args.data_source = [](void*, uint64_t address, void* buffer, size_t size)
{
// TODO: page cache?
return DbgMemRead(address, buffer, size);
};
args.visit = [](void *userdata, TreeNode parent, const VisitInfo *info) -> TreeNode
{
return ((DataExplorerDialog*)userdata)->mVisitor->visit(parent, *info);
};
// TODO: support removing type nodes from the API?
// TODO: callback on remove of type node
//GuiTypeClear();
mVisitor->clear();
ui->logEdit->clear();
ui->codeEdit->setErrorLine(-1);
auto status = PatternRun(&args);
//GuiUpdateTypeWidget();
if(status == PatternSuccess)
{
logHandler(LogLevelInfo, "Open the struct widget to see the results!\n");
}
}
void DataExplorerDialog::logHandler(LogLevel level, const char* message)
{
Q_UNUSED(level);
auto html = QString(message);
html += "\n";
html.replace("\t", " ");
html = html.toHtmlEscaped();
html.replace(QChar(' '), QString("&nbsp;"));
html.replace(QString("\r\n"), QString("<br/>\n"));
html.replace(QChar('\n'), QString("<br/>\n"));
static QRegularExpression addressRegExp(R"((&lt;source&gt;:)(\d+:\d+))");
html.replace(addressRegExp, "<a href=\"navigate://localhost/#\\2\">\\1\\2</a>");
QTextCursor cursor(ui->logEdit->document());
cursor.movePosition(QTextCursor::End);
cursor.beginEditBlock();
cursor.insertBlock();
// hack to not insert too many newlines: https://lists.qt-project.org/pipermail/qt-interest-old/2011-July/034725.html
cursor.deletePreviousChar();
cursor.insertHtml(html);
cursor.endEditBlock();
}
void DataExplorerDialog::compileError(const CompileError& error)
{
Q_UNUSED(error);
// TODO: highlight error lines
}
void DataExplorerDialog::evalError(const EvalError& error)
{
auto errorLine = error.location.line;
auto errorColumn = error.location.column;
if(errorLine > 0)
{
ui->codeEdit->setErrorLine(errorLine);
auto cursor = ui->codeEdit->textCursor();
cursor.clearSelection();
cursor.setPosition(ui->codeEdit->document()->findBlockByLineNumber(errorLine - 1).position() + errorColumn - 1);
ui->codeEdit->setTextCursor(cursor);
}
}
void DataExplorerDialog::on_logEdit_anchorClicked(const QUrl &url)
{
if(url.scheme() == "navigate")
{
auto fragment = url.fragment(QUrl::FullyDecoded);
QStringList split = fragment.split(':');
auto line = split[0].toInt();
auto column = split[1].toInt();
auto cursor = ui->codeEdit->textCursor();
cursor.clearSelection();
cursor.setPosition(ui->codeEdit->document()->findBlockByLineNumber(line - 1).position() + column - 1);
ui->codeEdit->setTextCursor(cursor);
ui->codeEdit->setFocus();
}
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <QDialog>
#include "PatternLanguage.h"
namespace Ui
{
class DataExplorerDialog;
}
class DataExplorerDialog : public QDialog
{
Q_OBJECT
public:
explicit DataExplorerDialog(QWidget* parent);
~DataExplorerDialog();
protected:
void closeEvent(QCloseEvent* event) override;
void changeEvent(QEvent* event) override;
private slots:
void on_buttonParse_pressed();
void on_logEdit_anchorClicked(const QUrl &url);
private:
void logHandler(LogLevel level, const char *message);
void compileError(const CompileError& error);
void evalError(const EvalError& error);
Ui::DataExplorerDialog *ui;
struct PatternVisitor* mVisitor = nullptr;
class PatternHighlighter* mHighlighter = nullptr;
};

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DataExplorerDialog</class>
<widget class="QDialog" name="DataExplorerDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>732</width>
<height>621</height>
</rect>
</property>
<property name="windowTitle">
<string>DataExplorer</string>
</property>
<property name="windowIcon">
<iconset resource="resource.qrc">
<normaloff>:/images/icon.png</normaloff>:/images/icon.png</iconset>
</property>
<widget class="CodeEditor" name="codeEdit">
<property name="geometry">
<rect>
<x>10</x>
<y>10</y>
<width>711</width>
<height>401</height>
</rect>
</property>
<property name="font">
<font>
<family>Courier New</family>
<pointsize>8</pointsize>
<bold>true</bold>
</font>
</property>
<property name="styleSheet">
<string notr="true">CodeEditor {
font-family: &quot;Courier New&quot;, &quot;Courier&quot;, monospace;
font-size: 8pt;
color: #000000;
background-color: #fefff7;
}</string>
</property>
<property name="plainText">
<string>struct Foo {
u32 x;
u8 y;
};
struct Cat {
u16 meow;
};
struct Bar {
Foo foo;
u64 z;
};
struct Test {
u8 a;
u8 b;
Cat c;
Foo foo;
u8 arr[10];
Cat c2[2];
};
Test t @ 0x2;</string>
</property>
</widget>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>10</x>
<y>410</y>
<width>31</width>
<height>16</height>
</rect>
</property>
<property name="text">
<string>Log:</string>
</property>
</widget>
<widget class="QTextBrowser" name="logEdit">
<property name="geometry">
<rect>
<x>10</x>
<y>430</y>
<width>711</width>
<height>181</height>
</rect>
</property>
<property name="font">
<font>
<family>Courier New</family>
</font>
</property>
<property name="openLinks">
<bool>false</bool>
</property>
</widget>
<widget class="QPushButton" name="buttonParse">
<property name="geometry">
<rect>
<x>690</x>
<y>380</y>
<width>31</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<family>Segoe UI Emoji</family>
<pointsize>12</pointsize>
<bold>true</bold>
</font>
</property>
<property name="toolTip">
<string>Run Pattern</string>
</property>
<property name="styleSheet">
<string notr="true">QPushButton {
color: green;
}</string>
</property>
<property name="text">
<string>▶️</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
</widget>
</widget>
<customwidgets>
<customwidget>
<class>CodeEditor</class>
<extends>QPlainTextEdit</extends>
<header>CodeEditor.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="resource.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,55 @@
#include "File.h"
File::File(duint virtualBase, const QString &fileName)
: mVirtualBase(virtualBase)
{
mFile = new QFile(fileName, this);
if(!mFile->open(QIODevice::ReadOnly))
{
throw std::runtime_error("Failed to open file: " + fileName.toUtf8().toStdString());
}
mSize = mFile->size();
}
File::~File()
{
if(mFile != nullptr)
{
mFile->close();
delete mFile;
mFile = nullptr;
}
}
bool File::read(duint addr, void *dest, duint size)
{
if(addr < mVirtualBase || addr + size > mVirtualBase + mSize) {
return false;
}
auto offset = addr - mVirtualBase;
mFile->seek(offset);
return mFile->read((char*)dest, size) == size;
}
bool File::getRange(duint addr, duint &base, duint &size)
{
if(!isValidPtr(addr))
{
return false;
}
base = mVirtualBase;
size = mSize;
return true;
}
bool File::isCodePtr(duint addr)
{
return false;
}
bool File::isValidPtr(duint addr)
{
return addr >= mVirtualBase && addr < mVirtualBase + mSize;
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <QObject>
#include <QFile>
#include "Bridge.h"
// TODO: implement as mapped file
class File : public QObject, public MemoryProvider
{
Q_OBJECT
public:
explicit File(duint virtualBase, const QString& fileName);
~File();
protected:
bool read(duint addr, void* dest, duint size) override;
bool getRange(duint addr, duint & base, duint & size) override;
bool isCodePtr(duint addr) override;
bool isValidPtr(duint addr) override;
private:
duint mVirtualBase = 0;
duint mSize = 0;
QFile* mFile = nullptr;
};

View File

@ -0,0 +1,30 @@
#include "GotoDialog.h"
#include "Bridge.h"
GotoDialog::GotoDialog(QWidget* parent, bool checkAddress)
: QInputDialog(parent)
, mCheckAddress(checkAddress)
{
setInputMode(InputMode::TextInput);
setLabelText(tr("Address:"));
setWindowTitle(tr("Goto"));
}
duint GotoDialog::address() const
{
return mAddress;
}
void GotoDialog::done(int r)
{
if(r == Accepted)
{
auto ok = false;
mAddress = textValue().toLongLong(&ok, 16);
if(!ok)
return;
if(mCheckAddress && !DbgMemIsValidReadPtr(mAddress))
return;
}
QInputDialog::done(r);
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <QInputDialog>
#include "Types.h"
class GotoDialog : public QInputDialog
{
public:
GotoDialog(QWidget* parent = nullptr, bool checkAddress = true);
using QInputDialog::exec;
duint address() const;
protected:
void done(int r) override;
private:
bool mCheckAddress = true;
duint mAddress = 0;
};

View File

@ -0,0 +1,116 @@
#include "MainWindow.h"
#include "./ui_MainWindow.h"
#include <QDebug>
#include <QMessageBox>
#include <QVBoxLayout>
#include <QFileDialog>
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
setupNavigation();
setupWidgets();
// Load the dump provided on the command line
auto args = qApp->arguments();
if(args.length() > 1)
{
loadFile(args.at(1));
}
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::loadFile(const QString & path)
{
DbgSetMemoryProvider(nullptr);
if(mFile != nullptr) {
delete mFile;
mFile = nullptr;
}
duint virtualBase = 0;
try
{
mFile = new File(virtualBase, path);
}
catch(const std::exception& x)
{
QMessageBox::critical(this, tr("Error"), x.what());
}
DbgSetMemoryProvider(mFile);
// Reload the views
mHexDump->loadFile(mFile);
emit mNavigation->gotoDump(virtualBase);
}
void MainWindow::setupNavigation()
{
mNavigation = new Navigation(this);
connect(mNavigation, &Navigation::focusWindow, [this](Navigation::Window window)
{
switch(window)
{
case Navigation::Dump:
ui->tabWidget->setCurrentWidget(mHexDump);
break;
default:
qDebug() << "Unknown window: " << window;
break;
}
});
connect(mNavigation, &Navigation::gotoAddress, [this](Navigation::Window window, duint address)
{
switch(window)
{
case Navigation::Dump:
qDebug() << "Dump at: " << address;
mHexDump->printDumpAt(address);
break;
default:
qDebug() << "Unknown window: " << window;
break;
}
});
}
struct DefaultArchitecture : Architecture {
bool disasm64() const override {
return true;
}
bool addr64() const override {
return true;
}
} gArchitecture;
Architecture* GlobalArchitecture()
{
return &gArchitecture;
}
void MainWindow::setupWidgets()
{
mHexDump = new MiniHexDump(mNavigation, GlobalArchitecture(), this);
ui->tabWidget->addTab(mHexDump, "Dump");
}
void MainWindow::on_action_Load_file_triggered()
{
auto fileName = QFileDialog::getOpenFileName(this, "Load file", QString(), "All files (*)");
if(!fileName.isEmpty())
{
loadFile(fileName);
}
}

View File

@ -0,0 +1,38 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <memory>
#include <QMainWindow>
#include "MiniHexDump.h"
#include "Navigation.h"
#include "File.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget* parent = nullptr);
~MainWindow();
private slots:
void on_action_Load_file_triggered();
private:
void loadFile(const QString & path);
void setupNavigation();
void setupWidgets();
private:
Ui::MainWindow* ui = nullptr;
Navigation* mNavigation = nullptr;
MiniHexDump* mHexDump = nullptr;
File* mFile = nullptr;
};
#endif // MAINWINDOW_H

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1200</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>Minidump</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>-1</number>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1200</width>
<height>24</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="action_Load_file"/>
</widget>
<addaction name="menu_File"/>
</widget>
<action name="action_Load_file">
<property name="text">
<string>&amp;Load file</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,67 @@
#include "MiniHexDump.h"
#include "GotoDialog.h"
#include "Bridge.h"
MiniHexDump::MiniHexDump(Navigation* navigation, Architecture* architecture, QWidget* parent)
: HexDump(architecture, parent)
, mNavigation(navigation)
{
mUnderliningEnabled = false;
hexAsciiSlot();
setupMenu();
}
void MiniHexDump::loadFile(File* file)
{
mFile = file;
mMemPage->setAttributes(0, 0);
setRowCount(0);
reloadData();
}
void MiniHexDump::hexAsciiSlot()
{
int charwidth = getCharWidth();
ColumnDescriptor wColDesc;
DataDescriptor dDesc;
wColDesc.isData = true; //hex byte
wColDesc.itemCount = 16;
wColDesc.separator = mAsciiSeparator ? mAsciiSeparator : 4;
dDesc.itemSize = Byte;
dDesc.byteMode = HexByte;
wColDesc.data = dDesc;
appendResetDescriptor(8 + charwidth * 47, tr("Hex"), false, wColDesc);
wColDesc.isData = true; //ascii byte
wColDesc.itemCount = 16;
wColDesc.separator = 0;
dDesc.itemSize = Byte;
dDesc.byteMode = AsciiByte;
wColDesc.data = dDesc;
appendDescriptor(8 + charwidth * 16, tr("ASCII"), false, wColDesc);
wColDesc.isData = false; //empty column
wColDesc.itemCount = 0;
wColDesc.separator = 0;
dDesc.itemSize = Byte;
dDesc.byteMode = AsciiByte;
wColDesc.data = dDesc;
appendDescriptor(0, "", false, wColDesc);
reloadData();
}
void MiniHexDump::setupMenu()
{
addMenuAction(TableAction(TR("Goto Address"), "G"), [this]()
{
GotoDialog gotoDialog(this);
if(gotoDialog.exec() == QDialog::Accepted)
emit mNavigation->gotoAddress(Navigation::Dump, gotoDialog.address());
});
addMenuAction(TR("Copy Address"), [this]()
{
Bridge::CopyToClipboard(ToPtrString(rvaToVa(getInitialSelection())));
});
}

View File

@ -0,0 +1,25 @@
#pragma once
#include <BasicView/HexDump.h>
#include "File.h"
#include "MagicMenu.h"
#include "Navigation.h"
class MiniHexDump : public HexDump, public MagicMenu<MiniHexDump>
{
Q_OBJECT
public:
explicit MiniHexDump(Navigation* navigation, Architecture* architecture, QWidget* parent = nullptr);
void loadFile(File* file);
private slots:
void hexAsciiSlot();
private:
File* mFile = nullptr;
int mAsciiSeparator = 0;
Navigation* mNavigation = nullptr;
void setupMenu();
};

View File

@ -0,0 +1,27 @@
#include "Navigation.h"
Navigation::Navigation(QObject* parent)
: QObject(parent)
{
}
void Navigation::gotoDump(duint address)
{
gotoFocus(Window::Dump, address);
}
void Navigation::gotoDisassembly(duint address)
{
gotoFocus(Window::Disassembly, address);
}
void Navigation::gotoMemoryMap(duint address)
{
gotoFocus(Window::MemoryMap, address);
}
void Navigation::gotoFocus(Window window, duint address)
{
emit gotoAddress(window, address);
emit focusWindow(window);
}

View File

@ -0,0 +1,29 @@
#pragma once
#include <QObject>
#include "Types.h"
class Navigation : public QObject
{
Q_OBJECT
public:
explicit Navigation(QObject* parent = nullptr);
enum Window
{
Dump,
Disassembly,
MemoryMap,
};
void gotoDump(duint address);
void gotoDisassembly(duint address);
void gotoMemoryMap(duint address);
signals:
void gotoAddress(Navigation::Window window, duint address);
void focusWindow(Navigation::Window window);
private:
void gotoFocus(Window window, duint address);
};

View File

@ -0,0 +1,201 @@
#include "PatternHighlighter.h"
PatternHighlighter::PatternHighlighter(const CodeEditor* style, QTextDocument* parent)
: QSyntaxHighlighter(parent)
{
refreshColors(style);
}
static const char* keywords[] = {
"struct",
"union",
"enum",
"bitfield",
"float",
"double",
"char",
"char16",
"bool",
"str",
"auto",
"namespace",
"using",
"fn",
"if",
"else",
"match",
"break",
"continue",
"try",
"catch",
"return",
"in",
"out",
"static"
};
static const char* instructions[] = {
"addressof",
"sizeof",
"import",
};
struct Format
{
Format(QTextCharFormat& format)
: format(format)
{
}
Format&& self()
{
return std::move(*this);
}
Format&& italic()
{
format.setFontItalic(true);
return self();
}
Format&& bold()
{
format.setFontWeight(QFont::Bold);
return self();
}
Format&& color(const QColor& color)
{
format.setForeground(color);
return self();
}
Format&& color(const QColorWrapper& color)
{
format.setForeground(color());
return self();
}
private:
QTextCharFormat& format;
};
void PatternHighlighter::refreshColors(const CodeEditor* style)
{
highlightingRules.clear();
auto addRule = [this](const char* regex) {
highlightingRules.append({ QRegularExpression(regex), QTextCharFormat() });
if (!highlightingRules.back().pattern.isValid())
{
qFatal("Invalid regular expression");
}
return Format(highlightingRules.back().format);
};
// Rules added later can override previous rules (since they are applied in order)
// operators
addRule(R"regex([+\-=^|&%*/<>]+)regex")
.color(style->operatorColor);
addRule(R"regex([\$\?:@~!])regex")
.bold()
.color(style->operatorColor);
// constants: 12, 0x33
addRule(R"regex(\b\d+\b)regex")
.color(style->constantColor);
addRule(R"regex(\b0x[0-9A-Fa-f]+\b)regex")
.color(style->constantColor);
addRule(R"regex(\b(true|false)\b)regex")
.color(style->constantColor);
// Keywords
{
QTextCharFormat keywordFormat;
keywordFormat.setForeground(style->keywordColor());
for (const QString& pattern : keywords)
highlightingRules.append({ QRegularExpression(QString("\\b%1\\b").arg(pattern)), keywordFormat });
}
addRule(R"regex(|\[\[|\]\])regex")
.bold()
.color(style->keywordColor);
// Instructions
{
QTextCharFormat instructionFormat;
instructionFormat.setForeground(style->instructionColor());
instructionFormat.setFontWeight(QFont::Bold);
for (const QString& pattern : instructions)
{
auto escapedRegex = QRegularExpression::escape(pattern);
highlightingRules.append({ QRegularExpression(QString("\\b%1\\b").arg(escapedRegex)), instructionFormat });
}
}
// preprocessor definitions
addRule(R"regex(\w*#(pragma|define|include|ifdef|ifndef|endif|error).+$)regex")
.bold()
.color(style->instructionColor);
// function(
addRule(R"regex(\b[-a-zA-Z$._:][-a-zA-Z$._:0-9]*\w*(?=\())regex")
.bold()
.color(style->functionColor);
// string literal
addRule(R"regex("(?:\\.|[^"\\])*")regex")
.color(style->stringColor);
// i64 and other types
addRule(R"regex(\b[us]\d+\b)regex")
.color(style->integerTypeColor);
// Line comments
addRule(R"regex(//.*$)regex")
.color(style->commentColor);
// Block comments
addRule(R"regex(\/\*[\s\S]*?\*\/)regex")
.color(style->commentColor);
}
void PatternHighlighter::highlightBlock(const QString& text)
{
for(int i = 0; i < highlightingRules.size(); i++)
{
const HighlightingRule& rule = highlightingRules[i];
QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext())
{
QRegularExpressionMatch match = matchIterator.next();
setFormat(match.capturedStart(), match.capturedLength(), rule.format);
}
}
setCurrentBlockState(0);
int startIndex = 0;
if (previousBlockState() != 1)
startIndex = text.indexOf(commentStartExpression);
while (startIndex >= 0)
{
QRegularExpressionMatch match = commentEndExpression.match(text, startIndex);
int endIndex = match.capturedStart();
int commentLength = 0;
if (endIndex == -1)
{
setCurrentBlockState(1);
commentLength = text.length() - startIndex;
}
else
{
commentLength = endIndex - startIndex + match.capturedLength();
}
setFormat(startIndex, commentLength, multiLineCommentFormat);
startIndex = text.indexOf(commentStartExpression, startIndex + commentLength);
}
}

View File

@ -0,0 +1,35 @@
#pragma once
#include <QObject>
#include <QSyntaxHighlighter>
#include <QTextCharFormat>
#include <QRegularExpression>
#include "CodeEditor.h"
QT_BEGIN_NAMESPACE
class QTextDocument;
QT_END_NAMESPACE
class PatternHighlighter : public QSyntaxHighlighter
{
Q_OBJECT
public:
PatternHighlighter(const CodeEditor* style, QTextDocument* parent = 0);
void refreshColors(const CodeEditor* style);
protected:
void highlightBlock(const QString& text) override;
private:
struct HighlightingRule
{
QRegularExpression pattern;
QTextCharFormat format;
};
QVector<HighlightingRule> highlightingRules;
QTextCharFormat multiLineCommentFormat;
QRegularExpression commentStartExpression = QRegularExpression(QStringLiteral("/\\*"));
QRegularExpression commentEndExpression = QRegularExpression(QStringLiteral("\\*/"));
};

View File

@ -0,0 +1,459 @@
#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, const 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(const ptrn::Pattern *pattern)
{
if (const auto &functionName = pattern->getReadFormatterFunction(); !functionName.empty())
{
return pattern->toString();
}
else
{
return {};
}
}
void formatValue(const 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
[&](std::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(const 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
includePaths[i] = 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 (!runtime.executeString(args->source, filename))
{
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
{
auto error = runtime.getEvalError().value();
if (args->eval_error == nullptr)
{
fmt::print("Pattern Error: {}:{} -> {}\n", error.line, error.column, error.message);
}
else
{
EvalError cerror = {
.location = {
.file = filename,
.line = error.line,
.column = error.column,
.length = 0,
},
.pretty = error.message.c_str(),
};
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)
{
args->log_handler(args->userdata, LogLevelError, e.what());
return PatternEvalError;
}
}

View File

@ -0,0 +1,94 @@
#pragma once
#include <stddef.h>
#include <stdbool.h>
#include <stdint.h>
typedef enum
{
LogLevelDebug = 0,
LogLevelInfo = 1,
LogLevelWarning = 2,
LogLevelError = 3,
} LogLevel;
typedef struct
{
const char *file;
uint32_t line;
uint32_t column;
size_t length;
} SourceLocation;
typedef struct
{
SourceLocation location;
const char *message;
const char *description;
const char *pretty;
} CompileError;
typedef struct
{
SourceLocation location;
const char *pretty;
} EvalError;
typedef void *TreeNode;
typedef struct
{
const char *type_name;
const char *variable_name;
uint64_t offset;
uint64_t size;
uint32_t line;
uint32_t color;
const char *value;
const char *comment;
const char *reason;
bool big_endian;
} VisitInfo;
typedef struct
{
void *userdata;
TreeNode root;
const char *source;
const char *filename;
uint64_t base;
uint64_t size;
const char **defines_data;
size_t defines_count;
const char **includes_data;
size_t includes_count;
bool allow_dangerous_functions;
void (*log_handler)(void *userdata, LogLevel level, const char *message);
void (*compile_error)(void *userdata, const CompileError *error);
void (*eval_error)(void *userdata, const EvalError *error);
bool (*data_source)(void *userdata, uint64_t address, void *buffer, size_t size);
TreeNode (*visit)(void *userdata, TreeNode parent, const VisitInfo *info);
} PatternRunArgs;
typedef enum
{
PatternSuccess,
PatternCompileError,
PatternEvalError,
} PatternStatus;
#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus
PatternStatus PatternRun(const PatternRunArgs *args);
#ifdef __cplusplus
}
#endif // __cplusplus

View File

@ -0,0 +1,102 @@
#include "RichTextItemDelegate.h"
#include <QTextDocument>
#include <QPainter>
#include <QAbstractTextDocumentLayout>
#include <QApplication>
RichTextItemDelegate::RichTextItemDelegate(QColor* textColor, QObject* parent)
: QStyledItemDelegate(parent),
mTextColor(textColor)
{
}
void RichTextItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem & inOption, const QModelIndex & index) const
{
QStyleOptionViewItem option = inOption;
initStyleOption(&option, index);
if(option.text.isEmpty())
{
// This is nothing this function is supposed to handle
QStyledItemDelegate::paint(painter, inOption, index);
return;
}
QStyle* style = option.widget ? option.widget->style() : QApplication::style();
QTextOption textOption;
textOption.setWrapMode(option.features & QStyleOptionViewItem::WrapText ? QTextOption::WordWrap
: QTextOption::ManualWrap);
textOption.setTextDirection(option.direction);
QTextDocument doc;
doc.setDefaultTextOption(textOption);
doc.setHtml(QString("<font color=\"%1\">%2</font>").arg(mTextColor->name(), option.text));
doc.setDefaultFont(option.font);
doc.setDocumentMargin(0);
doc.setTextWidth(option.rect.width());
doc.adjustSize();
if(doc.size().width() > option.rect.width())
{
// Elide text
QTextCursor cursor(&doc);
cursor.movePosition(QTextCursor::End);
const QString elidedPostfix = "...";
QFontMetrics metric(option.font);
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
int postfixWidth = metric.horizontalAdvance(elidedPostfix);
#else
int postfixWidth = metric.width(elidedPostfix);
#endif
while(!doc.isEmpty() && doc.size().width() > option.rect.width() - postfixWidth)
{
cursor.deletePreviousChar();
doc.adjustSize();
}
cursor.insertText(elidedPostfix);
}
// Painting item without text (this takes care of painting e.g. the highlighted for selected
// or hovered over items in an ItemView)
option.text = QString();
style->drawControl(QStyle::CE_ItemViewItem, &option, painter, inOption.widget);
// Figure out where to render the text in order to follow the requested alignment
QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &option);
QSize documentSize(doc.size().width(), doc.size().height()); // Convert QSizeF to QSize
QRect layoutRect = QStyle::alignedRect(Qt::LayoutDirectionAuto, option.displayAlignment, documentSize, textRect);
painter->save();
// Translate the painter to the origin of the layout rectangle in order for the text to be
// rendered at the correct position
painter->translate(layoutRect.topLeft());
doc.drawContents(painter, textRect.translated(-textRect.topLeft()));
painter->restore();
}
QSize RichTextItemDelegate::sizeHint(const QStyleOptionViewItem & inOption, const QModelIndex & index) const
{
QStyleOptionViewItem option = inOption;
initStyleOption(&option, index);
if(option.text.isEmpty())
{
// This is nothing this function is supposed to handle
return QStyledItemDelegate::sizeHint(inOption, index);
}
QTextDocument doc;
doc.setHtml(option.text);
doc.setTextWidth(option.rect.width());
doc.setDefaultFont(option.font);
doc.setDocumentMargin(0);
return QSize(doc.idealWidth(), doc.size().height());
}

View File

@ -0,0 +1,20 @@
#pragma once
#include <QStyledItemDelegate>
// Based on: https://stackoverflow.com/a/66412883/1806760
class RichTextItemDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
explicit RichTextItemDelegate(QColor* textColor, QObject* parent = nullptr);
protected:
void paint(QPainter* painter, const QStyleOptionViewItem & option, const QModelIndex & index) const override;
QSize sizeHint(const QStyleOptionViewItem & option, const QModelIndex & index) const override;
private:
QColor* mTextColor = nullptr;
};

View File

@ -0,0 +1,476 @@
#include <QFileDialog>
#include <QTextDocumentFragment>
#include <QMessageBox>
#include "StructWidget.h"
#include "ui_StructWidget.h"
#include "Configuration.h"
#include "GotoDialog.h"
#include "StringUtil.h"
#include "MiscUtil.h"
#include "RichTextItemDelegate.h"
struct TypeDescriptor
{
TYPEDESCRIPTOR type = {};
QString name;
};
Q_DECLARE_METATYPE(TypeDescriptor)
StructWidget::StructWidget(QWidget* parent) :
QWidget(parent),
ui(new Ui::StructWidget)
{
ui->setupUi(this);
ui->treeWidget->setStyleSheet("QTreeWidget { background-color: #FFF8F0; alternate-background-color: #DCD9CF; }");
ui->treeWidget->setItemDelegate(new RichTextItemDelegate(&mTextColor, ui->treeWidget));
connect(Bridge::getBridge(), SIGNAL(typeAddNode(void*, const TYPEDESCRIPTOR*)), this, SLOT(typeAddNode(void*, const TYPEDESCRIPTOR*)));
connect(Bridge::getBridge(), SIGNAL(typeClear()), this, SLOT(typeClear()));
connect(Bridge::getBridge(), SIGNAL(typeUpdateWidget()), this, SLOT(typeUpdateWidget()));
connect(Bridge::getBridge(), SIGNAL(dbgStateChanged(DBGSTATE)), this, SLOT(dbgStateChangedSlot(DBGSTATE)));
connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(colorsUpdatedSlot()));
connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(fontsUpdatedSlot()));
connect(Config(), SIGNAL(shortcutsUpdated()), this, SLOT(shortcutsUpdatedSlot()));
colorsUpdatedSlot();
fontsUpdatedSlot();
setupColumns();
setupContextMenu();
}
StructWidget::~StructWidget()
{
delete ui;
}
void StructWidget::saveWindowSettings()
{
auto saveColumn = [this](int column)
{
auto settingName = QString("StructWidgetV2Column%1").arg(column);
BridgeSettingSetUint("Gui", settingName.toUtf8().constData(), ui->treeWidget->columnWidth(column));
};
auto columnCount = ui->treeWidget->columnCount();
for(int i = 0; i < columnCount; i++)
saveColumn(i);
}
void StructWidget::loadWindowSettings()
{
auto loadColumn = [this](int column)
{
auto settingName = QString("StructWidgetV2Column%1").arg(column);
duint width = 0;
if(BridgeSettingGetUint("Gui", settingName.toUtf8().constData(), &width))
ui->treeWidget->setColumnWidth(column, width);
};
auto columnCount = ui->treeWidget->columnCount();
for(int i = 0; i < columnCount; i++)
loadColumn(i);
}
void StructWidget::colorsUpdatedSlot()
{
mTextColor = ConfigColor("StructTextColor");
auto background = ConfigColor("StructBackgroundColor");
auto altBackground = ConfigColor("StructAlternateBackgroundColor");
auto style = QString("QTreeWidget { background-color: %1; alternate-background-color: %2; }").arg(background.name(), altBackground.name());
ui->treeWidget->setStyleSheet(style);
}
void StructWidget::fontsUpdatedSlot()
{
auto font = ConfigFont("AbstractTableView");
setFont(font);
ui->treeWidget->setFont(font);
ui->treeWidget->header()->setFont(font);
}
void StructWidget::typeAddNode(void* parent, const TYPEDESCRIPTOR* type)
{
// Disable updates until the next typeUpdateWidget()
ui->treeWidget->setUpdatesEnabled(false);
TypeDescriptor dtype;
if(type->magic == TYPEDESCRIPTOR_MAGIC)
{
dtype.type = *type;
}
else
{
dtype.type.expanded = type->expanded;
dtype.type.reverse = type->reverse;
dtype.type.magic = TYPEDESCRIPTOR_MAGIC;
dtype.type.name = type->name;
dtype.type.addr = type->addr;
dtype.type.offset = type->offset;
dtype.type.id = type->id;
dtype.type.sizeBits = type->sizeBits * 8;
dtype.type.callback = type->callback;
dtype.type.userdata = type->userdata;
dtype.type.bitOffset = 0;
}
dtype.name = highlightTypeName(dtype.type.name);
dtype.type.name = nullptr;
QStringList text;
auto columnCount = ui->treeWidget->columnCount();
for(int i = 0; i < columnCount; i++)
text.append(QString());
text[ColOffset] = "+0x" + ToHexString(dtype.type.offset);
text[ColField] = dtype.name;
if(dtype.type.offset == 0 && true)
text[ColAddress] = QString("<u>%1</u>").arg(ToPtrString(dtype.type.addr + dtype.type.offset));
else
text[ColAddress] = ToPtrString(dtype.type.addr + dtype.type.offset);
text[ColSize] = "0x" + ToHexString(dtype.type.sizeBits / 8);
text[ColValue] = ""; // NOTE: filled in later
QTreeWidgetItem* item = parent ? new QTreeWidgetItem((QTreeWidgetItem*)parent, text) : new QTreeWidgetItem(ui->treeWidget, text);
item->setExpanded(dtype.type.expanded);
QVariant var;
var.setValue(dtype);
item->setData(0, Qt::UserRole, var);
}
void StructWidget::typeClear()
{
ui->treeWidget->clear();
}
void StructWidget::typeUpdateWidget()
{
ui->treeWidget->setUpdatesEnabled(false);
for(QTreeWidgetItemIterator it(ui->treeWidget); *it; ++it)
{
QTreeWidgetItem* item = *it;
auto type = item->data(0, Qt::UserRole).value<TypeDescriptor>();
auto name = type.name.toUtf8();
type.type.name = name.constData();
auto addr = type.type.addr + type.type.offset;
item->setText(ColAddress, ToPtrString(addr));
QString valueStr;
if(type.type.callback) //use the provided callback
{
char value[128] = "";
size_t valueCount = std::size(value);
if(!type.type.callback(&type.type, value, &valueCount) && valueCount && valueCount != std::size(value))
{
auto dest = new char[valueCount];
if(type.type.callback(&type.type, dest, &valueCount))
valueStr = value;
else
valueStr = "???";
delete[] dest;
}
else
valueStr = value;
}
else if(!item->childCount() && type.type.sizeBits > 0 && type.type.sizeBits / 8 <= sizeof(uint64_t)) //attempt to display small, non-parent values
{
uint64_t data;
// todo fix mem read for bit offset
if(DbgMemRead(addr, &data, type.type.sizeBits / 8))
{
if(type.type.reverse)
std::reverse((char*)data, (char*)data + (type.type.sizeBits / 8));
valueStr = QString().asprintf("0x%llX, %llu", data, data);
}
else if(type.type.addr)
valueStr = "???";
}
item->setText(ColValue, valueStr);
}
ui->treeWidget->setUpdatesEnabled(true);
}
void StructWidget::dbgStateChangedSlot(DBGSTATE state)
{
if(state == stopped)
ui->treeWidget->clear();
}
void StructWidget::setupColumns()
{
auto charWidth = ui->treeWidget->fontMetrics().horizontalAdvance(' ');
ui->treeWidget->setColumnWidth(ColField, 4 + charWidth * 60);
ui->treeWidget->setColumnWidth(ColOffset, 6 + charWidth * 7);
ui->treeWidget->setColumnWidth(ColAddress, 6 + charWidth * sizeof(duint) * 2);
ui->treeWidget->setColumnWidth(ColSize, 4 + charWidth * 6);
// NOTE: Trick to display the expander icons in the second column
// Reference: https://stackoverflow.com/a/25887454/1806760
// ui->treeWidget->header()->moveSection(ColField, ColOffset);
}
#define hasSelection !!ui->treeWidget->selectedItems().count()
#define selectedItem ui->treeWidget->selectedItems()[0]
#define selectedType selectedItem->data(0, Qt::UserRole).value<TypeDescriptor>().type
void StructWidget::setupContextMenu()
{
auto makeAction = [this](const QIcon& icon, const QString& text, const char* slot)
{
auto action = new QAction(text, this);
action->setIcon(icon);
connect(action, SIGNAL(triggered(bool)), this, slot);
return action;
};
auto makeMenu = [this](const QIcon& icon, const QString& title)
{
auto menu = new QMenu(title, this);
menu->setIcon(icon);
return menu;
};
mMenuBuilder = new MenuBuilder(this);
mMenuBuilder->addAction(makeAction(DIcon("dump"), tr("&Follow address in Dump"), SLOT(followDumpSlot())), [this](QMenu*)
{
return hasSelection && DbgMemIsValidReadPtr(selectedType.addr + selectedType.offset);
});
mMenuBuilder->addAction(makeAction(DIcon("dump"), tr("Follow value in Dump"), SLOT(followValueDumpSlot())), [this](QMenu*)
{
return DbgMemIsValidReadPtr(selectedValue());
});
mMenuBuilder->addAction(makeAction(DIcon("processor-cpu"), tr("Follow value in Disassembler"), SLOT(followValueDisasmSlot())), [this](QMenu*)
{
return DbgMemIsValidReadPtr(selectedValue());
});
mMenuBuilder->addAction(makeAction(DIcon("structaddr"), tr("Change address"), SLOT(changeAddrSlot())), [this](QMenu*)
{
return hasSelection && !selectedItem->parent() && DbgIsDebugging();
});
mMenuBuilder->addAction(makeAction(DIcon("visitstruct"), tr("Display type"), SLOT(visitSlot())));
mMenuBuilder->addAction(makeAction(DIcon("database-import"), tr("Load JSON"), SLOT(loadJsonSlot())));
mMenuBuilder->addAction(makeAction(DIcon("source"), tr("Parse header"), SLOT(parseFileSlot())));
mMenuBuilder->addAction(makeAction(DIcon("removestruct"), tr("Remove"), SLOT(removeSlot())), [this](QMenu*)
{
return hasSelection && !selectedItem->parent();
});
mMenuBuilder->addAction(makeAction(DIcon("eraser"), tr("Clear"), SLOT(clearSlot())));
mMenuBuilder->addAction(makeAction(DIcon("sync"), tr("&Refresh"), SLOT(refreshSlot())));
auto copyMenu = new MenuBuilder(this);
auto columnCount = ui->treeWidget->columnCount();
auto headerItem = ui->treeWidget->headerItem();
for(int column = 0; column < columnCount; column++)
{
auto action = makeAction(QIcon(), headerItem->text(column), SLOT(copyColumnSlot()));
action->setObjectName(QString("%1").arg(column));
copyMenu->addAction(action, [this, column](QMenu*)
{
return hasSelection && !selectedItem->text(column).isEmpty();
});
}
mMenuBuilder->addMenu(makeMenu(DIcon("copy"), tr("&Copy")), copyMenu);
mMenuBuilder->loadFromConfig();
}
QString StructWidget::highlightTypeName(QString name) const
{
// TODO: this can be improved with colors
static auto re = []
{
const char* keywords[] =
{
"uint64_t",
"uint32_t",
"uint16_t",
"char16_t",
"unsigned",
"int64_t",
"int32_t",
"wchar_t",
"int16_t",
"uint8_t",
"double",
"size_t",
"uint64",
"uint32",
"ushort",
"uint16",
"signed",
"int8_t",
"const",
"float",
"duint",
"dsint",
"int64",
"int32",
"short",
"int16",
"ubyte",
"uchar",
"uint8",
"void",
"long",
"bool",
"byte",
"char",
"int8",
"ptr",
"int",
};
QString keywordRegex;
keywordRegex += "\\b(";
for(size_t i = 0; i < std::size(keywords); i++)
{
if(i > 0)
keywordRegex += '|';
keywordRegex += QRegularExpression::escape(keywords[i]);
}
keywordRegex += ")\\b";
return QRegularExpression(keywordRegex, QRegularExpression::CaseInsensitiveOption);
}();
name.replace(re, "<b>\\1</b>");
static QRegularExpression sre("^(struct|union|class|enum) ([a-zA-Z0-9_:$]+)");
name.replace(sre, "<u>\\1</u> <b>\\2</b>");
return name;
}
duint StructWidget::selectedValue() const
{
if(!hasSelection)
return 0;
QStringList split = selectedItem->text(ColValue).split(',');
if(split.length() < 1)
return 0;
return split[0].toULongLong(nullptr, 0);
}
void StructWidget::on_treeWidget_customContextMenuRequested(const QPoint & pos)
{
QMenu menu;
mMenuBuilder->build(&menu);
if(menu.actions().count())
menu.exec(ui->treeWidget->viewport()->mapToGlobal(pos));
}
void StructWidget::followDumpSlot()
{
if(!hasSelection)
return;
DbgCmdExec(QString("dump %1").arg(ToPtrString(selectedType.addr + selectedType.offset)));
}
void StructWidget::followValueDumpSlot()
{
if(!hasSelection)
return;
DbgCmdExec(QString("dump %1").arg(ToPtrString(selectedValue())));
}
void StructWidget::followValueDisasmSlot()
{
if(!hasSelection)
return;
DbgCmdExec(QString("disasm %1").arg(ToPtrString(selectedValue())));
}
void StructWidget::clearSlot()
{
ui->treeWidget->clear();
}
void StructWidget::removeSlot()
{
if(!hasSelection)
return;
delete selectedItem;
}
void StructWidget::visitSlot()
{
#if 0 // TODO
QStringList structs;
DbgFunctions()->EnumStructs([](const char* name, void* userdata)
{
((QStringList*)userdata)->append(name);
}, &structs);
if(structs.isEmpty())
{
SimpleErrorBox(this, tr("Error"), tr("No types loaded yet, parse a header first..."));
return;
}
QString selection;
if(!SimpleChoiceBox(this, tr("Type to display"), "", structs, selection, true, "", DIcon("struct"), 1) || selection.isEmpty())
return;
if(!mGotoDialog)
mGotoDialog = new GotoDialog(this);
duint addr = 0;
mGotoDialog->setWindowTitle(tr("Address to display %1 at").arg(selection));
if(DbgIsDebugging() && mGotoDialog->exec() == QDialog::Accepted)
addr = DbgValFromString(mGotoDialog->expressionText.toUtf8().constData());
DbgCmdExec(QString("VisitType %1, %2, 2").arg(selection, ToPtrString(addr)));
// TODO: show a proper error message on failure
#endif
}
void StructWidget::loadJsonSlot()
{
auto filename = QFileDialog::getOpenFileName(this, tr("Load JSON"), QString(), tr("JSON files (*.json);;All files (*.*)"));
if(!filename.length())
return;
filename = QDir::toNativeSeparators(filename);
DbgCmdExec(QString("LoadTypes \"%1\"").arg(filename));
}
void StructWidget::parseFileSlot()
{
auto filename = QFileDialog::getOpenFileName(this, tr("Parse header"), QString(), tr("Header files (*.h *.hpp);;All files (*.*)"));
if(!filename.length())
return;
filename = QDir::toNativeSeparators(filename);
DbgCmdExec(QString("ParseTypes \"%1\"").arg(filename));
}
static void changeTypeAddr(QTreeWidgetItem* item, duint addr)
{
auto changeAddr = item->data(0, Qt::UserRole).value<TypeDescriptor>().type.addr;
for(QTreeWidgetItemIterator it(item); *it; ++it)
{
QTreeWidgetItem* item = *it;
auto type = item->data(0, Qt::UserRole).value<TypeDescriptor>();
type.type.addr = type.type.addr == changeAddr ? addr : 0; //invalidate pointers (requires revisit)
QVariant var;
var.setValue(type);
item->setData(0, Qt::UserRole, var);
}
}
void StructWidget::changeAddrSlot()
{
if(!hasSelection || !DbgIsDebugging())
return;
if(!mGotoDialog)
mGotoDialog = new GotoDialog(this);
mGotoDialog->setWindowTitle(tr("Change address"));
if(mGotoDialog->exec() != QDialog::Accepted)
return;
changeTypeAddr(selectedItem, mGotoDialog->address());
refreshSlot();
}
void StructWidget::refreshSlot()
{
typeUpdateWidget();
}
void StructWidget::copyColumnSlot()
{
QAction* action = qobject_cast<QAction*>(sender());
if(action == nullptr || !hasSelection)
return;
auto column = action->objectName().toInt();
auto text = selectedItem->text(column);
text = QTextDocumentFragment::fromHtml(text).toPlainText();
if(!text.isEmpty())
Bridge::CopyToClipboard(text);
}

View File

@ -0,0 +1,68 @@
#pragma once
#include <QWidget>
#include "Bridge.h"
#include "MagicMenu.h"
class MenuBuilder;
class GotoDialog;
namespace Ui
{
class StructWidget;
}
class StructWidget : public QWidget, public MagicMenu<StructWidget>
{
Q_OBJECT
public:
explicit StructWidget(QWidget* parent = nullptr);
~StructWidget();
void saveWindowSettings();
void loadWindowSettings();
public slots:
void colorsUpdatedSlot();
void fontsUpdatedSlot();
void typeAddNode(void* parent, const TYPEDESCRIPTOR* type);
void typeClear();
void typeUpdateWidget();
void dbgStateChangedSlot(DBGSTATE state);
private:
Ui::StructWidget* ui;
MenuBuilder* mMenuBuilder;
GotoDialog* mGotoDialog = nullptr;
QColor mTextColor;
void setupColumns();
void setupContextMenu();
QString highlightTypeName(QString name) const;
duint selectedValue() const;
enum
{
ColField,
ColOffset,
ColAddress,
ColSize,
ColValue,
};
private slots:
void on_treeWidget_customContextMenuRequested(const QPoint & pos);
void followDumpSlot();
void followValueDumpSlot();
void followValueDisasmSlot();
void clearSlot();
void removeSlot();
void visitSlot();
void loadJsonSlot();
void parseFileSlot();
void changeAddrSlot();
void refreshSlot();
void copyColumnSlot();
};

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>StructWidget</class>
<widget class="QWidget" name="StructWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>826</width>
<height>300</height>
</rect>
</property>
<property name="contextMenuPolicy">
<enum>Qt::DefaultContextMenu</enum>
</property>
<property name="windowTitle">
<string>Struct</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QTreeWidget" name="treeWidget">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="indentation">
<number>15</number>
</property>
<attribute name="headerCascadingSectionResizes">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Field</string>
</property>
</column>
<column>
<property name="text">
<string>Offset</string>
</property>
</column>
<column>
<property name="text">
<string>Address</string>
</property>
</column>
<column>
<property name="text">
<string>Size</string>
</property>
</column>
<column>
<property name="text">
<string>Value</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,55 @@
#pragma once
#include <type_traits>
#include <QWidget>
#include <QColor>
template<class Widget>
class Styled
{
bool polished = false;
friend class QColorWrapper;
protected:
QWidget* widget() { return (Widget*)(this); }
};
class QColorWrapper
{
QWidget* widget;
QColor color;
public:
explicit QColorWrapper(QWidget* widget, QColor defaultColor = {})
: widget(widget)
, color(defaultColor)
{
}
QColor operator()() const
{
return get();
}
QColor get(bool ensurePolished = true) const
{
if (ensurePolished && widget)
{
widget->ensurePolished();
}
return color;
}
void set(QColor color)
{
this->color = color;
this->widget = nullptr;
}
};
#define CSS_COLOR(name, defaultColor) \
QColorWrapper name = QColorWrapper(Styled::widget(), defaultColor); \
Q_PROPERTY(QColor name READ get_##name WRITE set_##name) \
QColor get_##name() const { return name.get(false); } \
void set_##name(QColor color) { name.set(color); }

View File

@ -0,0 +1,17 @@
#include <QApplication>
#include "MainWindow.h"
#include "Configuration.h"
#if QT_VERSION < QT_VERSION_CHECK(5, 12, 0)
#error Your Qt version is likely too old, upgrade to 5.12 or higher
#endif // QT_VERSION
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
Configuration config;
MainWindow w;
w.show();
return a.exec();
}

View File

@ -0,0 +1 @@
3.11

View File

View File

@ -0,0 +1,73 @@
import asyncio
import websockets
import json
import time
from typing import TypedDict
rpc_methods = {}
def jsonrpc(func):
rpc_methods[func.__name__] = func
return func
class TableParams(TypedDict):
lines: int
offset: int
class TableResult(TypedDict):
rows: list[list[str]]
@jsonrpc
async def table(params: TableParams) -> TableResult:
lines = params.get("lines", 0)
offset = params.get("offset", 0)
rows = []
for line in range(lines):
rows.append([
f"address: {line + offset}",
f"data: {line + offset}"
])
start = time.time()
while (time.time() - start) * 1000 < 500:
await asyncio.sleep(0.01)
return {"rows": rows}
async def handler(websocket):
async for message in websocket:
try:
request = json.loads(message)
if request.get("jsonrpc") != "2.0":
print(f"[server] Invalid magic: {request.get('jsonrpc')}")
continue
method = request.get("method")
req_id = request.get("id")
params = request.get("params", {})
response = {
"jsonrpc": "2.0",
"id": req_id
}
if method in rpc_methods:
result = await rpc_methods[method](params)
response["result"] = result
else:
print(f"[server] unknown method: {method}")
continue
await websocket.send(json.dumps(response))
except Exception as e:
print(f"[server] JSON error: {e}")
async def main():
print("[server] Listening on ws://127.0.0.1:42069")
async with websockets.serve(handler, "127.0.0.1", 42069):
await asyncio.Future()
if __name__ == "__main__":
asyncio.run(main())

View File

@ -0,0 +1,15 @@
[project]
name = "remote-server"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"capstone>=5.0.6",
"icicle-emu>=0.0.10",
"lief>=0.16.4",
"pefile>=2024.8.26",
"pyelftools>=0.32",
"pyside6>=6.9.0",
"websockets>=15.0.1",
]

View File

@ -0,0 +1,205 @@
version = 1
revision = 1
requires-python = ">=3.11"
[[package]]
name = "capstone"
version = "5.0.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d5/b0/1f126035a4cbc6f488b97e4bd57a46a28b6ba29ca8b938cbda840601a18a/capstone-5.0.6.tar.gz", hash = "sha256:b11a87d67751b006b9b44428d59c99512e6d6c89cf7dff8cdd92d9065628b5a0", size = 2945704 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cd/9a/d9c11e090fa03dfc61a03a57ba44c6a07370f4ac5814f2a5804bfd40ee8b/capstone-5.0.6-py3-none-macosx_10_9_universal2.whl", hash = "sha256:0bca16e1c3ca1b928df6103b3889dcb6df7b05392d75a0b7af3508798148b899", size = 2177082 },
{ url = "https://files.pythonhosted.org/packages/66/28/72a0be2325e6ee459f27cdcd835d3eee6fed5136321b5f7be41b41dc8656/capstone-5.0.6-py3-none-macosx_10_9_x86_64.whl", hash = "sha256:539191906a34ad1c573ec12b787b2caf154ea41175db6ded9def00aea8151099", size = 1180215 },
{ url = "https://files.pythonhosted.org/packages/54/93/7b8fb02661d47a2822d5b640df804ef310417144af02e6db3446f174c4b5/capstone-5.0.6-py3-none-macosx_11_0_arm64.whl", hash = "sha256:e0b87b283905e4fc43635ca04cf26f4a5d9e8375852e5464d38938f3a28c207a", size = 1192757 },
{ url = "https://files.pythonhosted.org/packages/ba/a2/d1bdb7260ade8165182979ea16098ef3a37c01316140511a611e549dbfe3/capstone-5.0.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa7892f0c89455078c18f07d2d309fb07baa53061b8f9a63db1ea00d41a46726", size = 1458413 },
{ url = "https://files.pythonhosted.org/packages/78/7f/ec0687bbe8f6b128f1d41d90ec7cedfd1aaaa4ecb1ae8d334acc7dad8013/capstone-5.0.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0358855773100bb91ae6681fabce7299df83156945ba943f6211061a592c54a6", size = 1481605 },
{ url = "https://files.pythonhosted.org/packages/fc/1d/77bb0f79e1dacdfdcc0679c747d9ca24cc621095e09bdb665e7dd0c580ae/capstone-5.0.6-py3-none-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:667d6466dab1522fa5e9659be5cf1aca83e4fc3da7d15d0e5e6047f71fb46c4a", size = 1480730 },
{ url = "https://files.pythonhosted.org/packages/72/63/07437972f68d0b2ba13e1705a6994404c9c961afbadc342c5b6fcf1de652/capstone-5.0.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:45c0e25500dd8d283d3b70f2e10cebfec93ab8bdaf6af9a763a0a999b4705891", size = 1457992 },
{ url = "https://files.pythonhosted.org/packages/0c/53/f371e86493a2ae659b5a493c3cc23122974e83a1f53d3a5638d7bb7ac371/capstone-5.0.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:22f1f2f118f8fa1d1c5c90bca90e75864d55e16349b3c03aaea0e86b4a45d2a9", size = 1484184 },
{ url = "https://files.pythonhosted.org/packages/df/c3/8b842ae32949c3570581164619c2f69001c6d8da566dc2e490372032b0d6/capstone-5.0.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:bc23cf634f51d0e53bdd04ea53ccfff7fc9060dfe58dff1e1b260ce40e5538ff", size = 1485357 },
{ url = "https://files.pythonhosted.org/packages/da/72/ff7894c2fb5716d9a3ce9c27ba34b29d991a11d8442d2ef0fcdc5564ba7e/capstone-5.0.6-py3-none-win_amd64.whl", hash = "sha256:761c3deae00b22ac697081cdae1383bb90659dd0d79387a09cf5bdbb22b17064", size = 1271345 },
]
[[package]]
name = "icicle-emu"
version = "0.0.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/4e/de/6e175464b29336d7014e8dce1b42566b147aba6a7526529839a2bc9deb0a/icicle_emu-0.0.10.tar.gz", hash = "sha256:19c26f5464c59e695d05caa3c9e0b93fdb14dcf4106c2890a1fd4c2bf43cdd5a", size = 1806163 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/da/7b/34f25638ef1933a343efa0cf4ef7c370926435011c515b87e4803c2c739a/icicle_emu-0.0.10-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a549e91c2fba6e0717e60467d61f65d7fb55a9f6e01757fcf2ded62665d9fe94", size = 5452945 },
{ url = "https://files.pythonhosted.org/packages/13/8d/2a846b5d70eeaa5a540a6ea906656c9c404fde20e04c55a5366a1df78083/icicle_emu-0.0.10-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:dd959a2bb194ba1e2e44b5117604fe5323b16a35347619ae4ef1e03f5b71368d", size = 3718153 },
{ url = "https://files.pythonhosted.org/packages/bd/47/282cb974bc947bd09dacc929805dca4eaeb93d59fdea8d82ed664e54e91e/icicle_emu-0.0.10-cp37-abi3-win_amd64.whl", hash = "sha256:e64066c5f896c74df36a2531f743c96ff6a154cb4defdad45b7795dc9a47686d", size = 3244949 },
]
[[package]]
name = "lief"
version = "0.16.4"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/c0/fe/5418787238d321118bdbc05f2c25fda11afc6367299bb35ffa072b0a534a/lief-0.16.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9ac39ce86488ab3dd8b45d15968b696ed2bc13e3471f6fe2d3783ad1c86adae3", size = 2645185 },
{ url = "https://files.pythonhosted.org/packages/24/af/83e497e25f13c789d1df82c492cba111ee9bfbdec7b697c8f812a0162764/lief-0.16.4-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:1cc3bdc8ecc400c398b31e15fdab9986d43e1388d9727fce0300dca0a5e65c17", size = 2735588 },
{ url = "https://files.pythonhosted.org/packages/8a/00/0d48504545e20a46d537a3e29b4e7662790e8c693e86e8b7530f1cf82d33/lief-0.16.4-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:621e0988efdf691e56fd6a8afe68cdeb4aea7cf935de3cbbcbec3455c6e57076", size = 3271863 },
{ url = "https://files.pythonhosted.org/packages/12/7f/53c6ba841ec45ddc92b1c4743baa7096c650aa645197191470ae80035d52/lief-0.16.4-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8b1f328d6e955cccb798353105abdb9983c251556927258e55b02e2aa60d5645", size = 2985505 },
{ url = "https://files.pythonhosted.org/packages/28/28/f9c377b58706fb718d07ba42e62dd5332320f3465475e2c525b872905f6a/lief-0.16.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d1d062baacc79781f1143a01d057d0e0a857b1f80489b330684c57b76ec12e7f", size = 3295834 },
{ url = "https://files.pythonhosted.org/packages/0f/3e/d7747f8dc7fcda21847e6d82b18c2177be17a5b25127b5ed87f5c266ced8/lief-0.16.4-cp311-cp311-win32.whl", hash = "sha256:7286e473544f85ba6c60b5afba0c3b9df3fd96d315d64a457c02cc7ce68b9711", size = 3044978 },
{ url = "https://files.pythonhosted.org/packages/39/9d/87abdaeeee4208d426bd436c75ebfc9efac22a60f67e38652a97c9cb7355/lief-0.16.4-cp311-cp311-win_amd64.whl", hash = "sha256:038b6d8910a4ff832ff6d77e439f55ec53f6c9426842ebe54bae9c26e3c36edb", size = 3172517 },
{ url = "https://files.pythonhosted.org/packages/97/ac/462765b19cfdf30ab7b77584766ea52db5c451501b9521f0d566019b78f6/lief-0.16.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55e48dcb17c2ba2fbee5dd6cf68315b5a417fcfb5cb778a4298b09cd6c3dae4a", size = 2642519 },
{ url = "https://files.pythonhosted.org/packages/30/72/a07660bf542eca17e5ea6747d10fba4aa73139e16f5c56ad4041e76b6343/lief-0.16.4-cp312-cp312-macosx_11_0_x86_64.whl", hash = "sha256:28ba25ebd22fc6bc81ae50108d5e7bc0ac3fd243dd606d12e8f96678bf82c1d0", size = 2743028 },
{ url = "https://files.pythonhosted.org/packages/54/31/3b59440766d9ea5ecac0cabc49fa3a7e4efb142b1fd47ee3ddcf62a1dc22/lief-0.16.4-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:9ada948c418de2a994084fa405c28e2b36b12c3c098e69b7f77b563f049a61b5", size = 3275999 },
{ url = "https://files.pythonhosted.org/packages/7a/35/8e330c50e1cf6c23a41d6e0ff3817d325a55ed4ff8bb06694472f00de69f/lief-0.16.4-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:457a703c2b15f3465c415485506ce7f121f884f0ca82b13a08777758cb346c80", size = 2992553 },
{ url = "https://files.pythonhosted.org/packages/30/f9/3a725c4d09d46a814883fd6e63cb9eccaaaa23e45372bb73c3ab1c0e34e9/lief-0.16.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8ba0b038eb7e8953d6c85347cba7cb353a79d122585e0797e3f1a75352ff6321", size = 3301839 },
{ url = "https://files.pythonhosted.org/packages/00/9c/1051c681702740d92bc125c74a5b74e20d873ee04686d9d6a44e028fe6bb/lief-0.16.4-cp312-cp312-win32.whl", hash = "sha256:06cd2432def66454785add6b8f14f2cf9f7ef4168cf2eb24367192953fb33206", size = 3053189 },
{ url = "https://files.pythonhosted.org/packages/39/6e/f4a2d1c0e47939d407d8fe894c78964249bd8efd172be75878054051b688/lief-0.16.4-cp312-cp312-win_amd64.whl", hash = "sha256:c6363bf971135a4c65e2fa151e456995bb6ecb38c00f1ed9f8d4c6a625111556", size = 3182188 },
{ url = "https://files.pythonhosted.org/packages/67/1a/e93b937628e7a48b6f12ff6196a2471c890df3d023b1d7d590873b881e9a/lief-0.16.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dec20dc5c6e146743ce99b2b883500326a7eedfbdd53f50be9db037a055cc840", size = 2641883 },
{ url = "https://files.pythonhosted.org/packages/2a/d5/4b7342c9d9bdfe46837446dbf0cb018832fe5a8794c0d9c9d57795a58d8e/lief-0.16.4-cp313-cp313-macosx_11_0_x86_64.whl", hash = "sha256:4aa2bc911936cdf0ff21109963ddf93eaf2366304fc5e87a2b084e2dbbfe1f52", size = 2742752 },
{ url = "https://files.pythonhosted.org/packages/93/51/5f1a7741227482703323c819144b3d396765ff540a5ce70b900cc87ec888/lief-0.16.4-cp313-cp313-manylinux2014_aarch64.whl", hash = "sha256:2ba064016a68598eaa0d0c322f7fae06af62baf6265c9ff8ec17c92aea157ce4", size = 3280516 },
{ url = "https://files.pythonhosted.org/packages/c9/67/e91dcb95552cb684dfbb72f097534945e527d79baa9528d7a3ef99ccf64f/lief-0.16.4-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:3b79275054b85af5857754c262b111aaf55455240acca52f02c82f9a6e526fd8", size = 2992545 },
{ url = "https://files.pythonhosted.org/packages/b4/fa/30ecfc19432456353612bd8e547c4a92b05d1a1fb145a1279e23b59e0278/lief-0.16.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd9a8ebd338a8147e6f8fc2115e5965a11b8c12f09b8afe91473d9905c6ee88f", size = 3301597 },
{ url = "https://files.pythonhosted.org/packages/3c/15/b1518db49c4d69232631133cd2b3b34a5691015c5e7394c5f38c0eba15c1/lief-0.16.4-cp313-cp313-win32.whl", hash = "sha256:9e103cc2bee8ff8d960ead8a094fc6aa9878b9ebeae0c1785d1e5bc2428ec4a4", size = 3053184 },
{ url = "https://files.pythonhosted.org/packages/ad/da/2b01fafc36aeb9a0c0949f5ffaba0a4562c6e77276fbd6b6cbf87387087c/lief-0.16.4-cp313-cp313-win_amd64.whl", hash = "sha256:775661661d0c4d33429f99e6cfd7f0e0c3781191be81c79eaa4a9f2c68929ebd", size = 3182245 },
]
[[package]]
name = "pefile"
version = "2024.8.26"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/03/4f/2750f7f6f025a1507cd3b7218691671eecfd0bbebebe8b39aa0fe1d360b8/pefile-2024.8.26.tar.gz", hash = "sha256:3ff6c5d8b43e8c37bb6e6dd5085658d658a7a0bdcd20b6a07b1fcfc1c4e9d632", size = 76008 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/54/16/12b82f791c7f50ddec566873d5bdd245baa1491bac11d15ffb98aecc8f8b/pefile-2024.8.26-py3-none-any.whl", hash = "sha256:76f8b485dcd3b1bb8166f1128d395fa3d87af26360c2358fb75b80019b957c6f", size = 74766 },
]
[[package]]
name = "pyelftools"
version = "0.32"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b9/ab/33968940b2deb3d92f5b146bc6d4009a5f95d1d06c148ea2f9ee965071af/pyelftools-0.32.tar.gz", hash = "sha256:6de90ee7b8263e740c8715a925382d4099b354f29ac48ea40d840cf7aa14ace5", size = 15047199 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/af/43/700932c4f0638c3421177144a2e86448c0d75dbaee2c7936bda3f9fd0878/pyelftools-0.32-py3-none-any.whl", hash = "sha256:013df952a006db5e138b1edf6d8a68ecc50630adbd0d83a2d41e7f846163d738", size = 188525 },
]
[[package]]
name = "pyside6"
version = "6.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyside6-addons" },
{ name = "pyside6-essentials" },
{ name = "shiboken6" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/46/74/0b465aa77644cfc3bfde912bb999b5a441d92c699272cab722335e92df3e/PySide6-6.9.0-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:b8f286a1bd143f3b2bdf08367b9362b13f469d26986c25700af9c4c68f79213e", size = 558001 },
{ url = "https://files.pythonhosted.org/packages/91/53/ce78d2c279a4ed7d4baf5089a5ebff45d675670a42daa5e0f8dbb9ced6ed/PySide6-6.9.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:09239d1b808f18efccd3803db874d683917efcdebfdf0e8dec449cf50e74e7aa", size = 558139 },
{ url = "https://files.pythonhosted.org/packages/4b/54/41d6ab0847c043f1fd96433a87ffd09a7cf17e11f5587e91e152777ec010/PySide6-6.9.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:1a176409dd0dd12b72d2c78b776e5051f569071ec52b7aaadd0a5b3333493c24", size = 558139 },
{ url = "https://files.pythonhosted.org/packages/63/03/55a632191beadd6bc59b04055961e2c3224a3475a906a63d1899a5ab493d/PySide6-6.9.0-cp39-abi3-win_amd64.whl", hash = "sha256:0103e5d161696db40d75bfbf4e4b7d4f3372903c1b400c4e3379377b62c50290", size = 564479 },
{ url = "https://files.pythonhosted.org/packages/e8/80/340523ecb17d2a168d7e37dfd8a7a0eebb81dcbec4870447f132f2a1a28e/PySide6-6.9.0-cp39-abi3-win_arm64.whl", hash = "sha256:846fbccf0b3501eb31cf0791a46e137615efba6ce540da2b426d79fa3e7762c4", size = 401752 },
]
[[package]]
name = "pyside6-addons"
version = "6.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pyside6-essentials" },
{ name = "shiboken6" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/e8/a4/211077b3f30342827b2c543f80a5f6bc483ff3af6be99766984618e68fb6/PySide6_Addons-6.9.0-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:98f9ad4b65820736e12d49c18db2e570eac63727407fbb59a62ac753e89dc201", size = 315606763 },
{ url = "https://files.pythonhosted.org/packages/58/c1/21224090a7ee7e9ce5699e5bf16b84d576b7587f0712ccb6862a8b28476c/PySide6_Addons-6.9.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:fc9dcd63a0ce7565f238cb11c44494435a50eb6cb72b8dbce3b709618989c3dc", size = 166252767 },
{ url = "https://files.pythonhosted.org/packages/85/c3/add4948cf15648db542531a5c292f9de946ee288243730be7607499936ec/PySide6_Addons-6.9.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:d8a650644e0b9d1e7a092f6bcd11f25a63706d12f77d442b6ace75d346ab5d30", size = 161938789 },
{ url = "https://files.pythonhosted.org/packages/77/c0/b1718f62d1fcc9bac4c410d4150d7e1214235e73cc18f39dc36ad49f093f/PySide6_Addons-6.9.0-cp39-abi3-win_amd64.whl", hash = "sha256:8cf54065b3d1b4698448fad825378a25c10ef52017d9dff48cead03200636d8d", size = 142994491 },
{ url = "https://files.pythonhosted.org/packages/29/aa/810ceb3d111fa6a0cc865520e05198dd0cad4855558c8c8309d4d3852854/PySide6_Addons-6.9.0-cp39-abi3-win_arm64.whl", hash = "sha256:260a56da59539f476c1635a3ff13591e10f1b04d92155c0617129bc53ca8b5f8", size = 26840861 },
]
[[package]]
name = "pyside6-essentials"
version = "6.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "shiboken6" },
]
wheels = [
{ url = "https://files.pythonhosted.org/packages/98/ac/a3c8097d6fdcf414d961bdc0d532381d0ee141e4c699f5e2b881a7c3613f/PySide6_Essentials-6.9.0-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:b18e3e01b507e8a57481fe19792eb373d5f10a23a50702ce540da1435e722f39", size = 131981893 },
{ url = "https://files.pythonhosted.org/packages/9e/fd/46b713827007162de9108b22d01702868e75f31585da7eca5a79e3435590/PySide6_Essentials-6.9.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:45eaf7f17688d1991f39680dbfd3c41674f3cbb78f278aa10fe0b5f2f31c1989", size = 94232483 },
{ url = "https://files.pythonhosted.org/packages/ff/f1/72e1d400017a658e271594c8bd9c447c623dfd4fb936f4e043a4f9a8c93b/PySide6_Essentials-6.9.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:69aedfad77119c5bec0005ca31d5620e9bac8ba5ae66c7389160530cfd698ed8", size = 92102516 },
{ url = "https://files.pythonhosted.org/packages/96/8a/bc710350c4cf6894968e39970eaa613b85a82eb1f230052de597e44a00ac/PySide6_Essentials-6.9.0-cp39-abi3-win_amd64.whl", hash = "sha256:94a0096d6bb1d3e5cef29ca4a5366d0f229d42480fbb17aa25ad85d72b1b7947", size = 72336994 },
{ url = "https://files.pythonhosted.org/packages/49/a4/703e379a0979985f681cf04b9af4129f5dde20141b3cc64fc2a39d006614/PySide6_Essentials-6.9.0-cp39-abi3-win_arm64.whl", hash = "sha256:d2dc45536f2269ad111991042e81257124f1cd1c9ed5ea778d7224fd65dc9e2b", size = 49449220 },
]
[[package]]
name = "remote-server"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "capstone" },
{ name = "icicle-emu" },
{ name = "lief" },
{ name = "pefile" },
{ name = "pyelftools" },
{ name = "pyside6" },
{ name = "websockets" },
]
[package.metadata]
requires-dist = [
{ name = "capstone", specifier = ">=5.0.6" },
{ name = "icicle-emu", specifier = ">=0.0.10" },
{ name = "lief", specifier = ">=0.16.4" },
{ name = "pefile", specifier = ">=2024.8.26" },
{ name = "pyelftools", specifier = ">=0.32" },
{ name = "pyside6", specifier = ">=6.9.0" },
{ name = "websockets", specifier = ">=15.0.1" },
]
[[package]]
name = "shiboken6"
version = "6.9.0"
source = { registry = "https://pypi.org/simple" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/be/85/97b36b045a233bcea9580e8c99d5c76d65cf9727dad8cb173527f6717471/shiboken6-6.9.0-cp39-abi3-macosx_12_0_universal2.whl", hash = "sha256:c4d8e3a5907154ac4789e52c77957db95bcf584238c244d7743cb39e9b66dd26", size = 407067 },
{ url = "https://files.pythonhosted.org/packages/45/d3/f6ddef22d4f2ac11c079157ad3714d9b1fb9324d9cd3b200f824923fe2ba/shiboken6-6.9.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3f585caae5b814a7e23308db0a077355a7dc20c34d58ca4c339ff7625e9a1936", size = 206509 },
{ url = "https://files.pythonhosted.org/packages/0d/59/6a91aad272fe89bf2293b7864fb6e926822c93a2f6192611528c6945196d/shiboken6-6.9.0-cp39-abi3-manylinux_2_39_aarch64.whl", hash = "sha256:b61579b90bf9c53ecc174085a69429166dfe57a0b8b894f933d1281af9df6568", size = 202809 },
{ url = "https://files.pythonhosted.org/packages/e2/6e/cf00d723ab141132fb6d35ba8faf109cbc0ee83412016343600abb423149/shiboken6-6.9.0-cp39-abi3-win_amd64.whl", hash = "sha256:121ea290ed1afa5ad6abf690b377612693436292b69c61b0f8e10b1f0850f935", size = 1153132 },
{ url = "https://files.pythonhosted.org/packages/b5/01/d59babab05786c99ebabdd152864ea3d4c500160979952c620eec68b1ff2/shiboken6-6.9.0-cp39-abi3-win_arm64.whl", hash = "sha256:24f53857458881b54798d7e35704611d07f6b6885bcdf80f13a4c8bb485b8df2", size = 1831261 },
]
[[package]]
name = "websockets"
version = "15.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/21/e6/26d09fab466b7ca9c7737474c52be4f76a40301b08362eb2dbc19dcc16c1/websockets-15.0.1.tar.gz", hash = "sha256:82544de02076bafba038ce055ee6412d68da13ab47f0c60cab827346de828dee", size = 177016 }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9f/32/18fcd5919c293a398db67443acd33fde142f283853076049824fc58e6f75/websockets-15.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:823c248b690b2fd9303ba00c4f66cd5e2d8c3ba4aa968b2779be9532a4dad431", size = 175423 },
{ url = "https://files.pythonhosted.org/packages/76/70/ba1ad96b07869275ef42e2ce21f07a5b0148936688c2baf7e4a1f60d5058/websockets-15.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678999709e68425ae2593acf2e3ebcbcf2e69885a5ee78f9eb80e6e371f1bf57", size = 173082 },
{ url = "https://files.pythonhosted.org/packages/86/f2/10b55821dd40eb696ce4704a87d57774696f9451108cff0d2824c97e0f97/websockets-15.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d50fd1ee42388dcfb2b3676132c78116490976f1300da28eb629272d5d93e905", size = 173330 },
{ url = "https://files.pythonhosted.org/packages/a5/90/1c37ae8b8a113d3daf1065222b6af61cc44102da95388ac0018fcb7d93d9/websockets-15.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d99e5546bf73dbad5bf3547174cd6cb8ba7273062a23808ffea025ecb1cf8562", size = 182878 },
{ url = "https://files.pythonhosted.org/packages/8e/8d/96e8e288b2a41dffafb78e8904ea7367ee4f891dafc2ab8d87e2124cb3d3/websockets-15.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:66dd88c918e3287efc22409d426c8f729688d89a0c587c88971a0faa2c2f3792", size = 181883 },
{ url = "https://files.pythonhosted.org/packages/93/1f/5d6dbf551766308f6f50f8baf8e9860be6182911e8106da7a7f73785f4c4/websockets-15.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8dd8327c795b3e3f219760fa603dcae1dcc148172290a8ab15158cf85a953413", size = 182252 },
{ url = "https://files.pythonhosted.org/packages/d4/78/2d4fed9123e6620cbf1706c0de8a1632e1a28e7774d94346d7de1bba2ca3/websockets-15.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8fdc51055e6ff4adeb88d58a11042ec9a5eae317a0a53d12c062c8a8865909e8", size = 182521 },
{ url = "https://files.pythonhosted.org/packages/e7/3b/66d4c1b444dd1a9823c4a81f50231b921bab54eee2f69e70319b4e21f1ca/websockets-15.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:693f0192126df6c2327cce3baa7c06f2a117575e32ab2308f7f8216c29d9e2e3", size = 181958 },
{ url = "https://files.pythonhosted.org/packages/08/ff/e9eed2ee5fed6f76fdd6032ca5cd38c57ca9661430bb3d5fb2872dc8703c/websockets-15.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:54479983bd5fb469c38f2f5c7e3a24f9a4e70594cd68cd1fa6b9340dadaff7cf", size = 181918 },
{ url = "https://files.pythonhosted.org/packages/d8/75/994634a49b7e12532be6a42103597b71098fd25900f7437d6055ed39930a/websockets-15.0.1-cp311-cp311-win32.whl", hash = "sha256:16b6c1b3e57799b9d38427dda63edcbe4926352c47cf88588c0be4ace18dac85", size = 176388 },
{ url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828 },
{ url = "https://files.pythonhosted.org/packages/51/6b/4545a0d843594f5d0771e86463606a3988b5a09ca5123136f8a76580dd63/websockets-15.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:3e90baa811a5d73f3ca0bcbf32064d663ed81318ab225ee4f427ad4e26e5aff3", size = 175437 },
{ url = "https://files.pythonhosted.org/packages/f4/71/809a0f5f6a06522af902e0f2ea2757f71ead94610010cf570ab5c98e99ed/websockets-15.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:592f1a9fe869c778694f0aa806ba0374e97648ab57936f092fd9d87f8bc03665", size = 173096 },
{ url = "https://files.pythonhosted.org/packages/3d/69/1a681dd6f02180916f116894181eab8b2e25b31e484c5d0eae637ec01f7c/websockets-15.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0701bc3cfcb9164d04a14b149fd74be7347a530ad3bbf15ab2c678a2cd3dd9a2", size = 173332 },
{ url = "https://files.pythonhosted.org/packages/a6/02/0073b3952f5bce97eafbb35757f8d0d54812b6174ed8dd952aa08429bcc3/websockets-15.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8b56bdcdb4505c8078cb6c7157d9811a85790f2f2b3632c7d1462ab5783d215", size = 183152 },
{ url = "https://files.pythonhosted.org/packages/74/45/c205c8480eafd114b428284840da0b1be9ffd0e4f87338dc95dc6ff961a1/websockets-15.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0af68c55afbd5f07986df82831c7bff04846928ea8d1fd7f30052638788bc9b5", size = 182096 },
{ url = "https://files.pythonhosted.org/packages/14/8f/aa61f528fba38578ec553c145857a181384c72b98156f858ca5c8e82d9d3/websockets-15.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64dee438fed052b52e4f98f76c5790513235efaa1ef7f3f2192c392cd7c91b65", size = 182523 },
{ url = "https://files.pythonhosted.org/packages/ec/6d/0267396610add5bc0d0d3e77f546d4cd287200804fe02323797de77dbce9/websockets-15.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d5f6b181bb38171a8ad1d6aa58a67a6aa9d4b38d0f8c5f496b9e42561dfc62fe", size = 182790 },
{ url = "https://files.pythonhosted.org/packages/02/05/c68c5adbf679cf610ae2f74a9b871ae84564462955d991178f95a1ddb7dd/websockets-15.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:5d54b09eba2bada6011aea5375542a157637b91029687eb4fdb2dab11059c1b4", size = 182165 },
{ url = "https://files.pythonhosted.org/packages/29/93/bb672df7b2f5faac89761cb5fa34f5cec45a4026c383a4b5761c6cea5c16/websockets-15.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3be571a8b5afed347da347bfcf27ba12b069d9d7f42cb8c7028b5e98bbb12597", size = 182160 },
{ url = "https://files.pythonhosted.org/packages/ff/83/de1f7709376dc3ca9b7eeb4b9a07b4526b14876b6d372a4dc62312bebee0/websockets-15.0.1-cp312-cp312-win32.whl", hash = "sha256:c338ffa0520bdb12fbc527265235639fb76e7bc7faafbb93f6ba80d9c06578a9", size = 176395 },
{ url = "https://files.pythonhosted.org/packages/7d/71/abf2ebc3bbfa40f391ce1428c7168fb20582d0ff57019b69ea20fa698043/websockets-15.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:fcd5cf9e305d7b8338754470cf69cf81f420459dbae8a3b40cee57417f4614a7", size = 176841 },
{ url = "https://files.pythonhosted.org/packages/cb/9f/51f0cf64471a9d2b4d0fc6c534f323b664e7095640c34562f5182e5a7195/websockets-15.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ee443ef070bb3b6ed74514f5efaa37a252af57c90eb33b956d35c8e9c10a1931", size = 175440 },
{ url = "https://files.pythonhosted.org/packages/8a/05/aa116ec9943c718905997412c5989f7ed671bc0188ee2ba89520e8765d7b/websockets-15.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5a939de6b7b4e18ca683218320fc67ea886038265fd1ed30173f5ce3f8e85675", size = 173098 },
{ url = "https://files.pythonhosted.org/packages/ff/0b/33cef55ff24f2d92924923c99926dcce78e7bd922d649467f0eda8368923/websockets-15.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:746ee8dba912cd6fc889a8147168991d50ed70447bf18bcda7039f7d2e3d9151", size = 173329 },
{ url = "https://files.pythonhosted.org/packages/31/1d/063b25dcc01faa8fada1469bdf769de3768b7044eac9d41f734fd7b6ad6d/websockets-15.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:595b6c3969023ecf9041b2936ac3827e4623bfa3ccf007575f04c5a6aa318c22", size = 183111 },
{ url = "https://files.pythonhosted.org/packages/93/53/9a87ee494a51bf63e4ec9241c1ccc4f7c2f45fff85d5bde2ff74fcb68b9e/websockets-15.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c714d2fc58b5ca3e285461a4cc0c9a66bd0e24c5da9911e30158286c9b5be7f", size = 182054 },
{ url = "https://files.pythonhosted.org/packages/ff/b2/83a6ddf56cdcbad4e3d841fcc55d6ba7d19aeb89c50f24dd7e859ec0805f/websockets-15.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f3c1e2ab208db911594ae5b4f79addeb3501604a165019dd221c0bdcabe4db8", size = 182496 },
{ url = "https://files.pythonhosted.org/packages/98/41/e7038944ed0abf34c45aa4635ba28136f06052e08fc2168520bb8b25149f/websockets-15.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:229cf1d3ca6c1804400b0a9790dc66528e08a6a1feec0d5040e8b9eb14422375", size = 182829 },
{ url = "https://files.pythonhosted.org/packages/e0/17/de15b6158680c7623c6ef0db361da965ab25d813ae54fcfeae2e5b9ef910/websockets-15.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:756c56e867a90fb00177d530dca4b097dd753cde348448a1012ed6c5131f8b7d", size = 182217 },
{ url = "https://files.pythonhosted.org/packages/33/2b/1f168cb6041853eef0362fb9554c3824367c5560cbdaad89ac40f8c2edfc/websockets-15.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:558d023b3df0bffe50a04e710bc87742de35060580a293c2a984299ed83bc4e4", size = 182195 },
{ url = "https://files.pythonhosted.org/packages/86/eb/20b6cdf273913d0ad05a6a14aed4b9a85591c18a987a3d47f20fa13dcc47/websockets-15.0.1-cp313-cp313-win32.whl", hash = "sha256:ba9e56e8ceeeedb2e080147ba85ffcd5cd0711b89576b83784d8605a7df455fa", size = 176393 },
{ url = "https://files.pythonhosted.org/packages/1b/6c/c65773d6cab416a64d191d6ee8a8b1c68a09970ea6909d16965d26bfed1e/websockets-15.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:e09473f095a819042ecb2ab9465aee615bd9c2028e4ef7d933600a8401c79561", size = 176837 },
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 },
]

18
src/cross/vendor/CMakeLists.txt generated vendored
View File

@ -3,9 +3,24 @@
# Create a configure-time dependency on cmake.toml to improve IDE support
if(CMKR_ROOT_PROJECT)
configure_file(cmake.toml cmake.toml COPYONLY)
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS cmake.toml)
endif()
include(FetchContent)
# Fix warnings about DOWNLOAD_EXTRACT_TIMESTAMP
if(POLICY CMP0135)
cmake_policy(SET CMP0135 NEW)
endif()
message(STATUS "Fetching PatternLanguage (0b850bfa62670bd4d6ddc5b81218233f193946b8)...")
FetchContent_Declare(PatternLanguage
GIT_REPOSITORY
"https://github.com/WerWolv/PatternLanguage"
GIT_TAG
0b850bfa62670bd4d6ddc5b81218233f193946b8
)
FetchContent_MakeAvailable(PatternLanguage)
# Subdirectory: btparser
set(CMKR_CMAKE_FOLDER ${CMAKE_FOLDER})
if(CMAKE_FOLDER)
@ -29,4 +44,3 @@ add_library(linux-pe INTERFACE)
target_include_directories(linux-pe INTERFACE
linux-pe
)

View File

@ -1,9 +1,13 @@
[target.cpp-httplib]
type = "interface"
include-directories = ["cpp-httplib"]
[target.linux-pe]
type = "interface"
include-directories = ["linux-pe"]
[subdir.btparser]
[target.cpp-httplib]
type = "interface"
include-directories = ["cpp-httplib"]
[target.linux-pe]
type = "interface"
include-directories = ["linux-pe"]
[subdir.btparser]
[fetch-content.PatternLanguage]
git = "https://github.com/WerWolv/PatternLanguage"
tag = "0b850bfa62670bd4d6ddc5b81218233f193946b8"

View File

@ -138,6 +138,27 @@ bool DbgSetEncodeType(duint addr, duint size, ENCODETYPE type);
void DbgDelEncodeTypeRange(duint start, duint end);
void DbgDelEncodeTypeSegment(duint start);
struct TYPEDESCRIPTOR;
typedef bool (*TYPETOSTRING)(const TYPEDESCRIPTOR* type, char* dest, size_t* destCount); //don't change destCount for final failure
#define TYPEDESCRIPTOR_MAGIC 0x1337
struct TYPEDESCRIPTOR
{
bool expanded; //is the type node expanded?
bool reverse; //big endian?
uint16_t magic; // compatiblity
const char* name; //type name (int b)
duint addr; //virtual address
duint offset; //offset to addr for the actual location in bytes
int id; //type id
int sizeBits; //sizeof(type) in bits
TYPETOSTRING callback; //convert to string
void* userdata; //user data
duint bitOffset; // bit offset from first bitfield
};
using GuiCallback = void(*)(void*);
void GuiExecuteOnGuiThreadEx(GuiCallback callback, void* data);

View File

@ -6,6 +6,8 @@
#include <QCoreApplication>
#include <QDebug>
class AbstractTableView;
// TODO: support QT_TR_NOOP in linguist
#define TR(str) TranslatedText(str)
@ -68,8 +70,11 @@ public:
base()->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(base(), &QWidget::customContextMenuRequested, [this](const QPoint & pos)
{
if(pos.y() < base()->getHeaderHeight())
return;
if constexpr(std::is_base_of_v<AbstractTableView, Base>)
{
if(pos.y() < base()->getHeaderHeight())
return;
}
auto wMenu = new QMenu(base());
mMenuBuilder->build(wMenu);
if(wMenu->actions().length())