Initial work on hex_viewer
This commit is contained in:
parent
5c3c8087db
commit
ede73f1663
|
@ -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()
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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(" "));
|
||||
html.replace(QString("\r\n"), QString("<br/>\n"));
|
||||
html.replace(QChar('\n'), QString("<br/>\n"));
|
||||
|
||||
static QRegularExpression addressRegExp(R"((<source>:)(\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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
};
|
|
@ -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: "Courier New", "Courier", 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>
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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
|
|
@ -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>&File</string>
|
||||
</property>
|
||||
<addaction name="action_Load_file"/>
|
||||
</widget>
|
||||
<addaction name="menu_File"/>
|
||||
</widget>
|
||||
<action name="action_Load_file">
|
||||
<property name="text">
|
||||
<string>&Load file</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
|
@ -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())));
|
||||
});
|
||||
}
|
|
@ -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();
|
||||
};
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
};
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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("\\*/"));
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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());
|
||||
}
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
};
|
|
@ -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>
|
|
@ -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); }
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
3.11
|
|
@ -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())
|
|
@ -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",
|
||||
]
|
|
@ -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 },
|
||||
]
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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())
|
||||
|
|
Loading…
Reference in New Issue