diff options
67 files changed, 2280 insertions, 379 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 82b66be75..c8c8e3884 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,6 +27,7 @@ Follow the indentation/whitespace style shown below. Do not use tabs, use 4-spac ### Comments * For regular comments, use C++ style (`//`) comments, even for multi-line ones. * For doc-comments (Doxygen comments), use `/// ` if it's a single line, else use the `/**` `*/` style featured in the example. Start the text on the second line, not the first containing `/**`. +* For items that are both defined and declared in two separate files, put the doc-comment only next to the associated declaration. (In a header file, usually.) Otherwise, put it next to the implementation. Never duplicate doc-comments in both places. ```cpp namespace Example { diff --git a/src/citra/emu_window/emu_window_glfw.cpp b/src/citra/emu_window/emu_window_glfw.cpp index 697bf4693..982619126 100644 --- a/src/citra/emu_window/emu_window_glfw.cpp +++ b/src/citra/emu_window/emu_window_glfw.cpp @@ -76,9 +76,9 @@ EmuWindow_GLFW::EmuWindow_GLFW() { std::string window_title = Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc); m_render_window = glfwCreateWindow(VideoCore::kScreenTopWidth, (VideoCore::kScreenTopHeight + VideoCore::kScreenBottomHeight), - window_title.c_str(), NULL, NULL); + window_title.c_str(), nullptr, nullptr); - if (m_render_window == NULL) { + if (m_render_window == nullptr) { ERROR_LOG(GUI, "Failed to create GLFW window! Exiting..."); exit(1); } @@ -123,7 +123,7 @@ void EmuWindow_GLFW::MakeCurrent() { /// Releases (dunno if this is the "right" word) the GLFW context from the caller thread void EmuWindow_GLFW::DoneCurrent() { - glfwMakeContextCurrent(NULL); + glfwMakeContextCurrent(nullptr); } void EmuWindow_GLFW::ReloadSetKeymaps() { diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt index 98a48a69a..90e5c6aa6 100644 --- a/src/citra_qt/CMakeLists.txt +++ b/src/citra_qt/CMakeLists.txt @@ -8,9 +8,12 @@ set(SRCS debugger/callstack.cpp debugger/disassembler.cpp debugger/graphics.cpp + debugger/graphics_breakpoints.cpp debugger/graphics_cmdlists.cpp + debugger/graphics_framebuffer.cpp debugger/ramview.cpp debugger/registers.cpp + util/spinbox.cpp bootmanager.cpp hotkeys.cpp main.cpp @@ -23,9 +26,13 @@ set(HEADERS debugger/callstack.hxx debugger/disassembler.hxx debugger/graphics.hxx + debugger/graphics_breakpoints.hxx + debugger/graphics_breakpoints_p.hxx debugger/graphics_cmdlists.hxx + debugger/graphics_framebuffer.hxx debugger/ramview.hxx debugger/registers.hxx + util/spinbox.hxx bootmanager.hxx hotkeys.hxx main.hxx diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp index 9bf079919..b53206be6 100644 --- a/src/citra_qt/bootmanager.cpp +++ b/src/citra_qt/bootmanager.cpp @@ -14,6 +14,8 @@ #include "core/core.h" #include "core/settings.h" +#include "video_core/debug_utils/debug_utils.h" + #include "video_core/video_core.h" #include "citra_qt/version.h" @@ -65,14 +67,21 @@ void EmuThread::Stop() } stop_run = true; + // Release emu threads from any breakpoints, so that this doesn't hang forever. + Pica::g_debug_context->ClearBreakpoints(); + //core::g_state = core::SYS_DIE; - wait(500); + // TODO: Waiting here is just a bad workaround for retarded shutdown logic. + wait(1000); if (isRunning()) { WARN_LOG(MASTER_LOG, "EmuThread still running, terminating..."); quit(); - wait(1000); + + // TODO: Waiting 50 seconds can be necessary if the logging subsystem has a lot of spam + // queued... This should be fixed. + wait(50000); if (isRunning()) { WARN_LOG(MASTER_LOG, "EmuThread STILL running, something is wrong here..."); @@ -230,7 +239,7 @@ QByteArray GRenderWindow::saveGeometry() { // If we are a top-level widget, store the current geometry // otherwise, store the last backup - if (parent() == NULL) + if (parent() == nullptr) return ((QGLWidget*)this)->saveGeometry(); else return geometry; diff --git a/src/citra_qt/debugger/graphics_breakpoints.cpp b/src/citra_qt/debugger/graphics_breakpoints.cpp new file mode 100644 index 000000000..df5579e15 --- /dev/null +++ b/src/citra_qt/debugger/graphics_breakpoints.cpp @@ -0,0 +1,261 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <QMetaType> +#include <QPushButton> +#include <QTreeWidget> +#include <QVBoxLayout> +#include <QLabel> + +#include "graphics_breakpoints.hxx" +#include "graphics_breakpoints_p.hxx" + +BreakPointModel::BreakPointModel(std::shared_ptr<Pica::DebugContext> debug_context, QObject* parent) + : QAbstractListModel(parent), context_weak(debug_context), + at_breakpoint(debug_context->at_breakpoint), + active_breakpoint(debug_context->active_breakpoint) +{ + +} + +int BreakPointModel::columnCount(const QModelIndex& parent) const +{ + return 2; +} + +int BreakPointModel::rowCount(const QModelIndex& parent) const +{ + return static_cast<int>(Pica::DebugContext::Event::NumEvents); +} + +QVariant BreakPointModel::data(const QModelIndex& index, int role) const +{ + const auto event = static_cast<Pica::DebugContext::Event>(index.row()); + + switch (role) { + case Qt::DisplayRole: + { + switch (index.column()) { + case 0: + { + std::map<Pica::DebugContext::Event, QString> map; + map.insert({Pica::DebugContext::Event::CommandLoaded, tr("Pica command loaded")}); + map.insert({Pica::DebugContext::Event::CommandProcessed, tr("Pica command processed")}); + map.insert({Pica::DebugContext::Event::IncomingPrimitiveBatch, tr("Incoming primitive batch")}); + map.insert({Pica::DebugContext::Event::FinishedPrimitiveBatch, tr("Finished primitive batch")}); + + _dbg_assert_(GUI, map.size() == static_cast<size_t>(Pica::DebugContext::Event::NumEvents)); + + return map[event]; + } + + case 1: + return data(index, Role_IsEnabled).toBool() ? tr("Enabled") : tr("Disabled"); + + default: + break; + } + + break; + } + + case Qt::BackgroundRole: + { + if (at_breakpoint && index.row() == static_cast<int>(active_breakpoint)) { + return QBrush(QColor(0xE0, 0xE0, 0x10)); + } + break; + } + + case Role_IsEnabled: + { + auto context = context_weak.lock(); + return context && context->breakpoints[event].enabled; + } + + default: + break; + } + return QVariant(); +} + +QVariant BreakPointModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch(role) { + case Qt::DisplayRole: + { + if (section == 0) { + return tr("Event"); + } else if (section == 1) { + return tr("Status"); + } + + break; + } + } + + return QVariant(); +} + +bool BreakPointModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + const auto event = static_cast<Pica::DebugContext::Event>(index.row()); + + switch (role) { + case Role_IsEnabled: + { + auto context = context_weak.lock(); + if (!context) + return false; + + context->breakpoints[event].enabled = value.toBool(); + QModelIndex changed_index = createIndex(index.row(), 1); + emit dataChanged(changed_index, changed_index); + return true; + } + } + + return false; +} + + +void BreakPointModel::OnBreakPointHit(Pica::DebugContext::Event event) +{ + auto context = context_weak.lock(); + if (!context) + return; + + active_breakpoint = context->active_breakpoint; + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast<int>(event), 0), + createIndex(static_cast<int>(event), 1)); +} + +void BreakPointModel::OnResumed() +{ + auto context = context_weak.lock(); + if (!context) + return; + + at_breakpoint = context->at_breakpoint; + emit dataChanged(createIndex(static_cast<int>(active_breakpoint), 0), + createIndex(static_cast<int>(active_breakpoint), 1)); + active_breakpoint = context->active_breakpoint; +} + + +GraphicsBreakPointsWidget::GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context, + QWidget* parent) + : QDockWidget(tr("Pica Breakpoints"), parent), + Pica::DebugContext::BreakPointObserver(debug_context) +{ + setObjectName("PicaBreakPointsWidget"); + + status_text = new QLabel(tr("Emulation running")); + resume_button = new QPushButton(tr("Resume")); + resume_button->setEnabled(false); + + breakpoint_model = new BreakPointModel(debug_context, this); + breakpoint_list = new QTreeView; + breakpoint_list->setModel(breakpoint_model); + + toggle_breakpoint_button = new QPushButton(tr("Enable")); + toggle_breakpoint_button->setEnabled(false); + + qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event"); + + connect(resume_button, SIGNAL(clicked()), this, SLOT(OnResumeRequested())); + + connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), + this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), + Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), + breakpoint_model, SLOT(OnBreakPointHit(Pica::DebugContext::Event)), + Qt::BlockingQueuedConnection); + connect(this, SIGNAL(Resumed()), breakpoint_model, SLOT(OnResumed())); + + connect(this, SIGNAL(BreakPointsChanged(const QModelIndex&,const QModelIndex&)), + breakpoint_model, SIGNAL(dataChanged(const QModelIndex&,const QModelIndex&))); + + connect(breakpoint_list->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), + this, SLOT(OnBreakpointSelectionChanged(QModelIndex))); + + connect(toggle_breakpoint_button, SIGNAL(clicked()), this, SLOT(OnToggleBreakpointEnabled())); + + QWidget* main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(status_text); + sub_layout->addWidget(resume_button); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(breakpoint_list); + main_layout->addWidget(toggle_breakpoint_button); + main_widget->setLayout(main_layout); + + setWidget(main_widget); +} + +void GraphicsBreakPointsWidget::OnPicaBreakPointHit(Event event, void* data) +{ + // Process in GUI thread + emit BreakPointHit(event, data); +} + +void GraphicsBreakPointsWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) +{ + status_text->setText(tr("Emulation halted at breakpoint")); + resume_button->setEnabled(true); +} + +void GraphicsBreakPointsWidget::OnPicaResume() +{ + // Process in GUI thread + emit Resumed(); +} + +void GraphicsBreakPointsWidget::OnResumed() +{ + status_text->setText(tr("Emulation running")); + resume_button->setEnabled(false); +} + +void GraphicsBreakPointsWidget::OnResumeRequested() +{ + if (auto context = context_weak.lock()) + context->Resume(); +} + +void GraphicsBreakPointsWidget::OnBreakpointSelectionChanged(const QModelIndex& index) +{ + if (!index.isValid()) { + toggle_breakpoint_button->setEnabled(false); + return; + } + + toggle_breakpoint_button->setEnabled(true); + UpdateToggleBreakpointButton(index); +} + +void GraphicsBreakPointsWidget::OnToggleBreakpointEnabled() +{ + QModelIndex index = breakpoint_list->selectionModel()->currentIndex(); + bool new_state = !(breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool()); + + breakpoint_model->setData(index, new_state, + BreakPointModel::Role_IsEnabled); + UpdateToggleBreakpointButton(index); +} + +void GraphicsBreakPointsWidget::UpdateToggleBreakpointButton(const QModelIndex& index) +{ + if (true == breakpoint_model->data(index, BreakPointModel::Role_IsEnabled).toBool()) { + toggle_breakpoint_button->setText(tr("Disable")); + } else { + toggle_breakpoint_button->setText(tr("Enable")); + } +} diff --git a/src/citra_qt/debugger/graphics_breakpoints.hxx b/src/citra_qt/debugger/graphics_breakpoints.hxx new file mode 100644 index 000000000..2142c6fa1 --- /dev/null +++ b/src/citra_qt/debugger/graphics_breakpoints.hxx @@ -0,0 +1,53 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include <QAbstractListModel> +#include <QDockWidget> + +#include "video_core/debug_utils/debug_utils.h" + +class QLabel; +class QPushButton; +class QTreeView; + +class BreakPointModel; + +class GraphicsBreakPointsWidget : public QDockWidget, Pica::DebugContext::BreakPointObserver { + Q_OBJECT + + using Event = Pica::DebugContext::Event; + +public: + GraphicsBreakPointsWidget(std::shared_ptr<Pica::DebugContext> debug_context, + QWidget* parent = nullptr); + + void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; + void OnPicaResume() override; + +public slots: + void OnBreakPointHit(Pica::DebugContext::Event event, void* data); + void OnResumeRequested(); + void OnResumed(); + void OnBreakpointSelectionChanged(const QModelIndex&); + void OnToggleBreakpointEnabled(); + +signals: + void Resumed(); + void BreakPointHit(Pica::DebugContext::Event event, void* data); + void BreakPointsChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + +private: + void UpdateToggleBreakpointButton(const QModelIndex& index); + + QLabel* status_text; + QPushButton* resume_button; + QPushButton* toggle_breakpoint_button; + + BreakPointModel* breakpoint_model; + QTreeView* breakpoint_list; +}; diff --git a/src/citra_qt/debugger/graphics_breakpoints_p.hxx b/src/citra_qt/debugger/graphics_breakpoints_p.hxx new file mode 100644 index 000000000..bf5daf73d --- /dev/null +++ b/src/citra_qt/debugger/graphics_breakpoints_p.hxx @@ -0,0 +1,38 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include <QAbstractListModel> + +#include "video_core/debug_utils/debug_utils.h" + +class BreakPointModel : public QAbstractListModel { + Q_OBJECT + +public: + enum { + Role_IsEnabled = Qt::UserRole, + }; + + BreakPointModel(std::shared_ptr<Pica::DebugContext> context, QObject* parent); + + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole); + +public slots: + void OnBreakPointHit(Pica::DebugContext::Event event); + void OnResumed(); + +private: + std::weak_ptr<Pica::DebugContext> context_weak; + bool at_breakpoint; + Pica::DebugContext::Event active_breakpoint; +}; diff --git a/src/citra_qt/debugger/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics_cmdlists.cpp index 71dd166cd..7f97cf143 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.cpp +++ b/src/citra_qt/debugger/graphics_cmdlists.cpp @@ -2,30 +2,171 @@ // Licensed under GPLv2 // Refer to the license.txt file included. +#include <QLabel> #include <QListView> +#include <QMainWindow> #include <QPushButton> #include <QVBoxLayout> #include <QTreeView> +#include <QSpinBox> +#include <QComboBox> + +#include "video_core/pica.h" +#include "video_core/math.h" + +#include "video_core/debug_utils/debug_utils.h" #include "graphics_cmdlists.hxx" -GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) -{ +#include "util/spinbox.hxx" + +QImage LoadTexture(u8* src, const Pica::DebugUtils::TextureInfo& info) { + QImage decoded_image(info.width, info.height, QImage::Format_ARGB32); + for (int y = 0; y < info.height; ++y) { + for (int x = 0; x < info.width; ++x) { + Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(src, x, y, info); + decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a())); + } + } + + return decoded_image; +} + +class TextureInfoWidget : public QWidget { +public: + TextureInfoWidget(u8* src, const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr) : QWidget(parent) { + QLabel* image_widget = new QLabel; + QPixmap image_pixmap = QPixmap::fromImage(LoadTexture(src, info)); + image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation); + image_widget->setPixmap(image_pixmap); + + QVBoxLayout* layout = new QVBoxLayout; + layout->addWidget(image_widget); + setLayout(layout); + } +}; + +TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent) + : QDockWidget(tr("Texture 0x%1").arg(info.address, 8, 16, QLatin1Char('0'))), + info(info) { + + QWidget* main_widget = new QWidget; + + QLabel* image_widget = new QLabel; + + connect(this, SIGNAL(UpdatePixmap(const QPixmap&)), image_widget, SLOT(setPixmap(const QPixmap&))); + + CSpinBox* phys_address_spinbox = new CSpinBox; + phys_address_spinbox->SetBase(16); + phys_address_spinbox->SetRange(0, 0xFFFFFFFF); + phys_address_spinbox->SetPrefix("0x"); + phys_address_spinbox->SetValue(info.address); + connect(phys_address_spinbox, SIGNAL(ValueChanged(qint64)), this, SLOT(OnAddressChanged(qint64))); + + QComboBox* format_choice = new QComboBox; + format_choice->addItem(tr("RGBA8")); + format_choice->addItem(tr("RGB8")); + format_choice->addItem(tr("RGBA5551")); + format_choice->addItem(tr("RGB565")); + format_choice->addItem(tr("RGBA4")); + format_choice->setCurrentIndex(static_cast<int>(info.format)); + connect(format_choice, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFormatChanged(int))); + + QSpinBox* width_spinbox = new QSpinBox; + width_spinbox->setMaximum(65535); + width_spinbox->setValue(info.width); + connect(width_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnWidthChanged(int))); + + QSpinBox* height_spinbox = new QSpinBox; + height_spinbox->setMaximum(65535); + height_spinbox->setValue(info.height); + connect(height_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnHeightChanged(int))); + + QSpinBox* stride_spinbox = new QSpinBox; + stride_spinbox->setMaximum(65535 * 4); + stride_spinbox->setValue(info.stride); + connect(stride_spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnStrideChanged(int))); + + QVBoxLayout* main_layout = new QVBoxLayout; + main_layout->addWidget(image_widget); + + { + QHBoxLayout* sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Source Address:"))); + sub_layout->addWidget(phys_address_spinbox); + main_layout->addLayout(sub_layout); + } + + { + QHBoxLayout* sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Format"))); + sub_layout->addWidget(format_choice); + main_layout->addLayout(sub_layout); + } + + { + QHBoxLayout* sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Width:"))); + sub_layout->addWidget(width_spinbox); + sub_layout->addStretch(); + sub_layout->addWidget(new QLabel(tr("Height:"))); + sub_layout->addWidget(height_spinbox); + sub_layout->addStretch(); + sub_layout->addWidget(new QLabel(tr("Stride:"))); + sub_layout->addWidget(stride_spinbox); + main_layout->addLayout(sub_layout); + } + + main_widget->setLayout(main_layout); + + emit UpdatePixmap(ReloadPixmap()); + + setWidget(main_widget); +} + +void TextureInfoDockWidget::OnAddressChanged(qint64 value) { + info.address = value; + emit UpdatePixmap(ReloadPixmap()); +} + +void TextureInfoDockWidget::OnFormatChanged(int value) { + info.format = static_cast<Pica::Regs::TextureFormat>(value); + emit UpdatePixmap(ReloadPixmap()); +} + +void TextureInfoDockWidget::OnWidthChanged(int value) { + info.width = value; + emit UpdatePixmap(ReloadPixmap()); +} + +void TextureInfoDockWidget::OnHeightChanged(int value) { + info.height = value; + emit UpdatePixmap(ReloadPixmap()); +} +void TextureInfoDockWidget::OnStrideChanged(int value) { + info.stride = value; + emit UpdatePixmap(ReloadPixmap()); } -int GPUCommandListModel::rowCount(const QModelIndex& parent) const -{ +QPixmap TextureInfoDockWidget::ReloadPixmap() const { + u8* src = Memory::GetPointer(info.address); + return QPixmap::fromImage(LoadTexture(src, info)); +} + +GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) { + +} + +int GPUCommandListModel::rowCount(const QModelIndex& parent) const { return pica_trace.writes.size(); } -int GPUCommandListModel::columnCount(const QModelIndex& parent) const -{ +int GPUCommandListModel::columnCount(const QModelIndex& parent) const { return 2; } -QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const -{ +QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) return QVariant(); @@ -36,21 +177,39 @@ QVariant GPUCommandListModel::data(const QModelIndex& index, int role) const if (role == Qt::DisplayRole) { QString content; if (index.column() == 0) { - content = QString::fromLatin1(Pica::Regs::GetCommandName(cmd.cmd_id).c_str()); + QString content = QString::fromLatin1(Pica::Regs::GetCommandName(cmd.cmd_id).c_str()); content.append(" "); + return content; } else if (index.column() == 1) { - content.append(QString("%1 ").arg(cmd.hex, 8, 16, QLatin1Char('0'))); + QString content = QString("%1 ").arg(cmd.hex, 8, 16, QLatin1Char('0')); content.append(QString("%1 ").arg(val, 8, 16, QLatin1Char('0'))); + return content; } + } else if (role == CommandIdRole) { + return QVariant::fromValue<int>(cmd.cmd_id.Value()); + } - return QVariant(content); + return QVariant(); +} + +QVariant GPUCommandListModel::headerData(int section, Qt::Orientation orientation, int role) const { + switch(role) { + case Qt::DisplayRole: + { + if (section == 0) { + return tr("Command Name"); + } else if (section == 1) { + return tr("Data"); + } + + break; + } } return QVariant(); } -void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace) -{ +void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace) { beginResetModel(); pica_trace = trace; @@ -58,38 +217,82 @@ void GPUCommandListModel::OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& endResetModel(); } +#define COMMAND_IN_RANGE(cmd_id, reg_name) \ + (cmd_id >= PICA_REG_INDEX(reg_name) && \ + cmd_id < PICA_REG_INDEX(reg_name) + sizeof(decltype(Pica::registers.reg_name)) / 4) + +void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) { + const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt(); + if (COMMAND_IN_RANGE(command_id, texture0)) { + auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(Pica::registers.texture0, + Pica::registers.texture0_format); + + // TODO: Instead, emit a signal here to be caught by the main window widget. + auto main_window = static_cast<QMainWindow*>(parent()); + main_window->tabifyDockWidget(this, new TextureInfoDockWidget(info, main_window)); + } +} + +void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) { + QWidget* new_info_widget; + + const int command_id = list_widget->model()->data(index, GPUCommandListModel::CommandIdRole).toInt(); + if (COMMAND_IN_RANGE(command_id, texture0)) { + u8* src = Memory::GetPointer(Pica::registers.texture0.GetPhysicalAddress()); + auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(Pica::registers.texture0, + Pica::registers.texture0_format); + new_info_widget = new TextureInfoWidget(src, info); + } else { + new_info_widget = new QWidget; + } + + widget()->layout()->removeWidget(command_info_widget); + delete command_info_widget; + widget()->layout()->addWidget(new_info_widget); + command_info_widget = new_info_widget; +} +#undef COMMAND_IN_RANGE -GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent) -{ +GPUCommandListWidget::GPUCommandListWidget(QWidget* parent) : QDockWidget(tr("Pica Command List"), parent) { + setObjectName("Pica Command List"); GPUCommandListModel* model = new GPUCommandListModel(this); QWidget* main_widget = new QWidget; - QTreeView* list_widget = new QTreeView; + list_widget = new QTreeView; list_widget->setModel(model); list_widget->setFont(QFont("monospace")); list_widget->setRootIsDecorated(false); - QPushButton* toggle_tracing = new QPushButton(tr("Start Tracing")); + connect(list_widget->selectionModel(), SIGNAL(currentChanged(const QModelIndex&,const QModelIndex&)), + this, SLOT(SetCommandInfo(const QModelIndex&))); + connect(list_widget, SIGNAL(doubleClicked(const QModelIndex&)), + this, SLOT(OnCommandDoubleClicked(const QModelIndex&))); + + toggle_tracing = new QPushButton(tr("Start Tracing")); connect(toggle_tracing, SIGNAL(clicked()), this, SLOT(OnToggleTracing())); connect(this, SIGNAL(TracingFinished(const Pica::DebugUtils::PicaTrace&)), model, SLOT(OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace&))); + command_info_widget = new QWidget; + QVBoxLayout* main_layout = new QVBoxLayout; main_layout->addWidget(list_widget); main_layout->addWidget(toggle_tracing); + main_layout->addWidget(command_info_widget); main_widget->setLayout(main_layout); setWidget(main_widget); } -void GPUCommandListWidget::OnToggleTracing() -{ +void GPUCommandListWidget::OnToggleTracing() { if (!Pica::DebugUtils::IsPicaTracing()) { Pica::DebugUtils::StartPicaTracing(); + toggle_tracing->setText(tr("Finish Tracing")); } else { pica_trace = Pica::DebugUtils::FinishPicaTracing(); emit TracingFinished(*pica_trace); + toggle_tracing->setText(tr("Start Tracing")); } } diff --git a/src/citra_qt/debugger/graphics_cmdlists.hxx b/src/citra_qt/debugger/graphics_cmdlists.hxx index 1523e724f..a459bba64 100644 --- a/src/citra_qt/debugger/graphics_cmdlists.hxx +++ b/src/citra_qt/debugger/graphics_cmdlists.hxx @@ -10,16 +10,24 @@ #include "video_core/gpu_debugger.h" #include "video_core/debug_utils/debug_utils.h" +class QPushButton; +class QTreeView; + class GPUCommandListModel : public QAbstractListModel { Q_OBJECT public: + enum { + CommandIdRole = Qt::UserRole, + }; + GPUCommandListModel(QObject* parent); int columnCount(const QModelIndex& parent = QModelIndex()) const override; int rowCount(const QModelIndex& parent = QModelIndex()) const override; QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; public slots: void OnPicaTraceFinished(const Pica::DebugUtils::PicaTrace& trace); @@ -37,10 +45,39 @@ public: public slots: void OnToggleTracing(); + void OnCommandDoubleClicked(const QModelIndex&); + + void SetCommandInfo(const QModelIndex&); signals: void TracingFinished(const Pica::DebugUtils::PicaTrace&); private: std::unique_ptr<Pica::DebugUtils::PicaTrace> pica_trace; + + QTreeView* list_widget; + QWidget* command_info_widget; + QPushButton* toggle_tracing; +}; + +class TextureInfoDockWidget : public QDockWidget { + Q_OBJECT + +public: + TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent = nullptr); + +signals: + void UpdatePixmap(const QPixmap& pixmap); + +private slots: + void OnAddressChanged(qint64 value); + void OnFormatChanged(int value); + void OnWidthChanged(int value); + void OnHeightChanged(int value); + void OnStrideChanged(int value); + +private: + QPixmap ReloadPixmap() const; + + Pica::DebugUtils::TextureInfo info; }; diff --git a/src/citra_qt/debugger/graphics_framebuffer.cpp b/src/citra_qt/debugger/graphics_framebuffer.cpp new file mode 100644 index 000000000..ac47f298d --- /dev/null +++ b/src/citra_qt/debugger/graphics_framebuffer.cpp @@ -0,0 +1,282 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#include <QBoxLayout> +#include <QComboBox> +#include <QDebug> +#include <QLabel> +#include <QMetaType> +#include <QPushButton> +#include <QSpinBox> + +#include "video_core/pica.h" + +#include "graphics_framebuffer.hxx" + +#include "util/spinbox.hxx" + +BreakPointObserverDock::BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, + const QString& title, QWidget* parent) + : QDockWidget(title, parent), BreakPointObserver(debug_context) +{ + qRegisterMetaType<Pica::DebugContext::Event>("Pica::DebugContext::Event"); + + connect(this, SIGNAL(Resumed()), this, SLOT(OnResumed())); + + // NOTE: This signal is emitted from a non-GUI thread, but connect() takes + // care of delaying its handling to the GUI thread. + connect(this, SIGNAL(BreakPointHit(Pica::DebugContext::Event,void*)), + this, SLOT(OnBreakPointHit(Pica::DebugContext::Event,void*)), + Qt::BlockingQueuedConnection); +} + +void BreakPointObserverDock::OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) +{ + emit BreakPointHit(event, data); +} + +void BreakPointObserverDock::OnPicaResume() +{ + emit Resumed(); +} + + +GraphicsFramebufferWidget::GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, + QWidget* parent) + : BreakPointObserverDock(debug_context, tr("Pica Framebuffer"), parent), + framebuffer_source(Source::PicaTarget) +{ + setObjectName("PicaFramebuffer"); + + framebuffer_source_list = new QComboBox; + framebuffer_source_list->addItem(tr("Active Render Target")); + framebuffer_source_list->addItem(tr("Custom")); + framebuffer_source_list->setCurrentIndex(static_cast<int>(framebuffer_source)); + + framebuffer_address_control = new CSpinBox; + framebuffer_address_control->SetBase(16); + framebuffer_address_control->SetRange(0, 0xFFFFFFFF); + framebuffer_address_control->SetPrefix("0x"); + + framebuffer_width_control = new QSpinBox; + framebuffer_width_control->setMinimum(1); + framebuffer_width_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum + + framebuffer_height_control = new QSpinBox; + framebuffer_height_control->setMinimum(1); + framebuffer_height_control->setMaximum(std::numeric_limits<int>::max()); // TODO: Find actual maximum + + framebuffer_format_control = new QComboBox; + framebuffer_format_control->addItem(tr("RGBA8")); + framebuffer_format_control->addItem(tr("RGB8")); + framebuffer_format_control->addItem(tr("RGBA5551")); + framebuffer_format_control->addItem(tr("RGB565")); + framebuffer_format_control->addItem(tr("RGBA4")); + + // TODO: This QLabel should shrink the image to the available space rather than just expanding... + framebuffer_picture_label = new QLabel; + + auto enlarge_button = new QPushButton(tr("Enlarge")); + + // Connections + connect(this, SIGNAL(Update()), this, SLOT(OnUpdate())); + connect(framebuffer_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferSourceChanged(int))); + connect(framebuffer_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnFramebufferAddressChanged(qint64))); + connect(framebuffer_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferWidthChanged(int))); + connect(framebuffer_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnFramebufferHeightChanged(int))); + connect(framebuffer_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnFramebufferFormatChanged(int))); + + auto main_widget = new QWidget; + auto main_layout = new QVBoxLayout; + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Source:"))); + sub_layout->addWidget(framebuffer_source_list); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Virtual Address:"))); + sub_layout->addWidget(framebuffer_address_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Width:"))); + sub_layout->addWidget(framebuffer_width_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Height:"))); + sub_layout->addWidget(framebuffer_height_control); + main_layout->addLayout(sub_layout); + } + { + auto sub_layout = new QHBoxLayout; + sub_layout->addWidget(new QLabel(tr("Format:"))); + sub_layout->addWidget(framebuffer_format_control); + main_layout->addLayout(sub_layout); + } + main_layout->addWidget(framebuffer_picture_label); + main_layout->addWidget(enlarge_button); + main_widget->setLayout(main_layout); + setWidget(main_widget); + + // Load current data - TODO: Make sure this works when emulation is not running + emit Update(); + widget()->setEnabled(false); // TODO: Only enable if currently at breakpoint +} + +void GraphicsFramebufferWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) +{ + emit Update(); + widget()->setEnabled(true); +} + +void GraphicsFramebufferWidget::OnResumed() +{ + widget()->setEnabled(false); +} + +void GraphicsFramebufferWidget::OnFramebufferSourceChanged(int new_value) +{ + framebuffer_source = static_cast<Source>(new_value); + emit Update(); +} + +void GraphicsFramebufferWidget::OnFramebufferAddressChanged(qint64 new_value) +{ + if (framebuffer_address != new_value) { + framebuffer_address = static_cast<unsigned>(new_value); + + framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsFramebufferWidget::OnFramebufferWidthChanged(int new_value) +{ + if (framebuffer_width != new_value) { + framebuffer_width = new_value; + + framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value) +{ + if (framebuffer_height != new_value) { + framebuffer_height = new_value; + + framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsFramebufferWidget::OnFramebufferFormatChanged(int new_value) +{ + if (framebuffer_format != static_cast<Format>(new_value)) { + framebuffer_format = static_cast<Format>(new_value); + + framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom)); + emit Update(); + } +} + +void GraphicsFramebufferWidget::OnUpdate() +{ + QPixmap pixmap; + + switch (framebuffer_source) { + case Source::PicaTarget: + { + // TODO: Store a reference to the registers in the debug context instead of accessing them directly... + + auto framebuffer = Pica::registers.framebuffer; + using Framebuffer = decltype(framebuffer); + + framebuffer_address = framebuffer.GetColorBufferAddress(); + framebuffer_width = framebuffer.GetWidth(); + framebuffer_height = framebuffer.GetHeight(); + framebuffer_format = static_cast<Format>(framebuffer.color_format); + + break; + } + + case Source::Custom: + { + // Keep user-specified values + break; + } + + default: + qDebug() << "Unknown framebuffer source " << static_cast<int>(framebuffer_source); + break; + } + + // TODO: Implement a good way to visualize alpha components! + // TODO: Unify this decoding code with the texture decoder + switch (framebuffer_format) { + case Format::RGBA8: + { + QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); + u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); + for (int y = 0; y < framebuffer_height; ++y) { + for (int x = 0; x < framebuffer_width; ++x) { + u32 value = *(color_buffer + x + y * framebuffer_width); + + decoded_image.setPixel(x, y, qRgba((value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF, 255/*value >> 24*/)); + } + } + pixmap = QPixmap::fromImage(decoded_image); + break; + } + + case Format::RGB8: + { + QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); + u8* color_buffer = Memory::GetPointer(framebuffer_address); + for (int y = 0; y < framebuffer_height; ++y) { + for (int x = 0; x < framebuffer_width; ++x) { + u8* pixel_pointer = color_buffer + x * 3 + y * 3 * framebuffer_width; + + decoded_image.setPixel(x, y, qRgba(pixel_pointer[0], pixel_pointer[1], pixel_pointer[2], 255/*value >> 24*/)); + } + } + pixmap = QPixmap::fromImage(decoded_image); + break; + } + + case Format::RGBA5551: + { + QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32); + u32* color_buffer = (u32*)Memory::GetPointer(framebuffer_address); + for (int y = 0; y < framebuffer_height; ++y) { + for (int x = 0; x < framebuffer_width; ++x) { + u16 value = *(u16*)(((u8*)color_buffer) + x * 2 + y * framebuffer_width * 2); + u8 r = (value >> 11) & 0x1F; + u8 g = (value >> 6) & 0x1F; + u8 b = (value >> 1) & 0x1F; + u8 a = value & 1; + + decoded_image.setPixel(x, y, qRgba(r, g, b, 255/*a*/)); + } + } + pixmap = QPixmap::fromImage(decoded_image); + break; + } + + default: + qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format); + break; + } + + framebuffer_address_control->SetValue(framebuffer_address); + framebuffer_width_control->setValue(framebuffer_width); + framebuffer_height_control->setValue(framebuffer_height); + framebuffer_format_control->setCurrentIndex(static_cast<int>(framebuffer_format)); + framebuffer_picture_label->setPixmap(pixmap); +} diff --git a/src/citra_qt/debugger/graphics_framebuffer.hxx b/src/citra_qt/debugger/graphics_framebuffer.hxx new file mode 100644 index 000000000..1151ee7a1 --- /dev/null +++ b/src/citra_qt/debugger/graphics_framebuffer.hxx @@ -0,0 +1,92 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2 +// Refer to the license.txt file included. + +#pragma once + +#include <QDockWidget> + +#include "video_core/debug_utils/debug_utils.h" + +class QComboBox; +class QLabel; +class QSpinBox; + +class CSpinBox; + +// Utility class which forwards calls to OnPicaBreakPointHit and OnPicaResume to public slots. +// This is because the Pica breakpoint callbacks are called from a non-GUI thread, while +// the widget usually wants to perform reactions in the GUI thread. +class BreakPointObserverDock : public QDockWidget, Pica::DebugContext::BreakPointObserver { + Q_OBJECT + +public: + BreakPointObserverDock(std::shared_ptr<Pica::DebugContext> debug_context, const QString& title, + QWidget* parent = nullptr); + + void OnPicaBreakPointHit(Pica::DebugContext::Event event, void* data) override; + void OnPicaResume() override; + +private slots: + virtual void OnBreakPointHit(Pica::DebugContext::Event event, void* data) = 0; + virtual void OnResumed() = 0; + +signals: + void Resumed(); + void BreakPointHit(Pica::DebugContext::Event event, void* data); +}; + +class GraphicsFramebufferWidget : public BreakPointObserverDock { + Q_OBJECT + + using Event = Pica::DebugContext::Event; + + enum class Source { + PicaTarget = 0, + Custom = 1, + + // TODO: Add GPU framebuffer sources! + }; + + enum class Format { + RGBA8 = 0, + RGB8 = 1, + RGBA5551 = 2, + RGB565 = 3, + RGBA4 = 4, + }; + +public: + GraphicsFramebufferWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr); + +public slots: + void OnFramebufferSourceChanged(int new_value); + void OnFramebufferAddressChanged(qint64 new_value); + void OnFramebufferWidthChanged(int new_value); + void OnFramebufferHeightChanged(int new_value); + void OnFramebufferFormatChanged(int new_value); + void OnUpdate(); + +private slots: + void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override; + void OnResumed() override; + +signals: + void Update(); + +private: + + QComboBox* framebuffer_source_list; + CSpinBox* framebuffer_address_control; + QSpinBox* framebuffer_width_control; + QSpinBox* framebuffer_height_control; + QComboBox* framebuffer_format_control; + + QLabel* framebuffer_picture_label; + + Source framebuffer_source; + unsigned framebuffer_address; + unsigned framebuffer_width; + unsigned framebuffer_height; + Format framebuffer_format; +}; diff --git a/src/citra_qt/hotkeys.cpp b/src/citra_qt/hotkeys.cpp index bbaa4a8dc..5d0b52e4f 100644 --- a/src/citra_qt/hotkeys.cpp +++ b/src/citra_qt/hotkeys.cpp @@ -5,7 +5,7 @@ struct Hotkey { - Hotkey() : shortcut(NULL), context(Qt::WindowShortcut) {} + Hotkey() : shortcut(nullptr), context(Qt::WindowShortcut) {} QKeySequence keyseq; QShortcut* shortcut; @@ -81,7 +81,7 @@ QShortcut* GetHotkey(const QString& group, const QString& action, QWidget* widge Hotkey& hk = hotkey_groups[group][action]; if (!hk.shortcut) - hk.shortcut = new QShortcut(hk.keyseq, widget, NULL, NULL, hk.context); + hk.shortcut = new QShortcut(hk.keyseq, widget, nullptr, nullptr, hk.context); return hk.shortcut; } diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp index d5554d917..b4e3ad964 100644 --- a/src/citra_qt/main.cpp +++ b/src/citra_qt/main.cpp @@ -20,7 +20,9 @@ #include "debugger/callstack.hxx" #include "debugger/ramview.hxx" #include "debugger/graphics.hxx" +#include "debugger/graphics_breakpoints.hxx" #include "debugger/graphics_cmdlists.hxx" +#include "debugger/graphics_framebuffer.hxx" #include "core/settings.h" #include "core/system.h" @@ -36,6 +38,8 @@ GMainWindow::GMainWindow() { LogManager::Init(); + Pica::g_debug_context = Pica::DebugContext::Construct(); + Config config; if (!Settings::values.enable_log) @@ -67,12 +71,22 @@ GMainWindow::GMainWindow() addDockWidget(Qt::RightDockWidgetArea, graphicsCommandsWidget); graphicsCommandsWidget->hide(); + auto graphicsBreakpointsWidget = new GraphicsBreakPointsWidget(Pica::g_debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); + graphicsBreakpointsWidget->hide(); + + auto graphicsFramebufferWidget = new GraphicsFramebufferWidget(Pica::g_debug_context, this); + addDockWidget(Qt::RightDockWidgetArea, graphicsFramebufferWidget); + graphicsFramebufferWidget->hide(); + QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging")); debug_menu->addAction(disasmWidget->toggleViewAction()); debug_menu->addAction(registersWidget->toggleViewAction()); debug_menu->addAction(callstackWidget->toggleViewAction()); debug_menu->addAction(graphicsWidget->toggleViewAction()); debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); + debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); + debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction()); // Set default UI state // geometry: 55% of the window contents are in the upper screen half, 45% in the lower half @@ -131,8 +145,10 @@ GMainWindow::GMainWindow() GMainWindow::~GMainWindow() { // will get automatically deleted otherwise - if (render_window->parent() == NULL) + if (render_window->parent() == nullptr) delete render_window; + + Pica::g_debug_context.reset(); } void GMainWindow::BootGame(std::string filename) @@ -164,7 +180,7 @@ void GMainWindow::BootGame(std::string filename) void GMainWindow::OnMenuLoadFile() { - QString filename = QFileDialog::getOpenFileName(this, tr("Load file"), QString(), tr("3DS executable (*.elf *.axf *.bin *.cci *.cxi)")); + QString filename = QFileDialog::getOpenFileName(this, tr("Load file"), QString(), tr("3DS executable (*.3dsx *.elf *.axf *.bin *.cci *.cxi)")); if (filename.size()) BootGame(filename.toLatin1().data()); } @@ -213,14 +229,14 @@ void GMainWindow::OnOpenHotkeysDialog() void GMainWindow::ToggleWindowMode() { bool enable = ui.action_Popout_Window_Mode->isChecked(); - if (enable && render_window->parent() != NULL) + if (enable && render_window->parent() != nullptr) { ui.horizontalLayout->removeWidget(render_window); - render_window->setParent(NULL); + render_window->setParent(nullptr); render_window->setVisible(true); render_window->RestoreGeometry(); } - else if (!enable && render_window->parent() == NULL) + else if (!enable && render_window->parent() == nullptr) { render_window->BackupGeometry(); ui.horizontalLayout->addWidget(render_window); diff --git a/src/citra_qt/util/spinbox.cpp b/src/citra_qt/util/spinbox.cpp new file mode 100644 index 000000000..80dc67d7d --- /dev/null +++ b/src/citra_qt/util/spinbox.cpp @@ -0,0 +1,303 @@ +// Licensed under GPLv2+ +// Refer to the license.txt file included. + + +// Copyright 2014 Tony Wasserka +// All rights reserved. +// +// 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 owner 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. + +#include <QLineEdit> +#include <QRegExpValidator> + +#include "common/log.h" + +#include "spinbox.hxx" + +CSpinBox::CSpinBox(QWidget* parent) : QAbstractSpinBox(parent), base(10), min_value(-100), max_value(100), value(0), num_digits(0) +{ + // TODO: Might be nice to not immediately call the slot. + // Think of an address that is being replaced by a different one, in which case a lot + // invalid intermediate addresses would be read from during editing. + connect(lineEdit(), SIGNAL(textEdited(QString)), this, SLOT(OnEditingFinished())); + + UpdateText(); +} + +void CSpinBox::SetValue(qint64 val) +{ + auto old_value = value; + value = std::max(std::min(val, max_value), min_value); + + if (old_value != value) { + UpdateText(); + emit ValueChanged(value); + } +} + +void CSpinBox::SetRange(qint64 min, qint64 max) +{ + min_value = min; + max_value = max; + + SetValue(value); + UpdateText(); +} + +void CSpinBox::stepBy(int steps) +{ + auto new_value = value; + // Scale number of steps by the currently selected digit + // TODO: Move this code elsewhere and enable it. + // TODO: Support for num_digits==0, too + // TODO: Support base!=16, too + // TODO: Make the cursor not jump back to the end of the line... + /*if (base == 16 && num_digits > 0) { + int digit = num_digits - (lineEdit()->cursorPosition() - prefix.length()) - 1; + digit = std::max(0, std::min(digit, num_digits - 1)); + steps <<= digit * 4; + }*/ + + // Increment "new_value" by "steps", and perform annoying overflow checks, too. + if (steps < 0 && new_value + steps > new_value) { + new_value = std::numeric_limits<qint64>::min(); + } else if (steps > 0 && new_value + steps < new_value) { + new_value = std::numeric_limits<qint64>::max(); + } else { + new_value += steps; + } + + SetValue(new_value); + UpdateText(); +} + +QAbstractSpinBox::StepEnabled CSpinBox::stepEnabled() const +{ + StepEnabled ret = StepNone; + + if (value > min_value) + ret |= StepDownEnabled; + + if (value < max_value) + ret |= StepUpEnabled; + + return ret; +} + +void CSpinBox::SetBase(int base) +{ + this->base = base; + + UpdateText(); +} + +void CSpinBox::SetNumDigits(int num_digits) +{ + this->num_digits = num_digits; + + UpdateText(); +} + +void CSpinBox::SetPrefix(const QString& prefix) +{ + this->prefix = prefix; + + UpdateText(); +} + +void CSpinBox::SetSuffix(const QString& suffix) +{ + this->suffix = suffix; + + UpdateText(); +} + +static QString StringToInputMask(const QString& input) { + QString mask = input; + + // ... replace any special characters by their escaped counterparts ... + mask.replace("\\", "\\\\"); + mask.replace("A", "\\A"); + mask.replace("a", "\\a"); + mask.replace("N", "\\N"); + mask.replace("n", "\\n"); + mask.replace("X", "\\X"); + mask.replace("x", "\\x"); + mask.replace("9", "\\9"); + mask.replace("0", "\\0"); + mask.replace("D", "\\D"); + mask.replace("d", "\\d"); + mask.replace("#", "\\#"); + mask.replace("H", "\\H"); + mask.replace("h", "\\h"); + mask.replace("B", "\\B"); + mask.replace("b", "\\b"); + mask.replace(">", "\\>"); + mask.replace("<", "\\<"); + mask.replace("!", "\\!"); + + return mask; +} + +void CSpinBox::UpdateText() +{ + // If a fixed number of digits is used, we put the line edit in insertion mode by setting an + // input mask. + QString mask; + if (num_digits != 0) { + mask += StringToInputMask(prefix); + + // For base 10 and negative range, demand a single sign character + if (HasSign()) + mask += "X"; // identified as "-" or "+" in the validator + + // Uppercase digits greater than 9. + mask += ">"; + + // The greatest signed 64-bit number has 19 decimal digits. + // TODO: Could probably make this more generic with some logarithms. + // For reference, unsigned 64-bit can have up to 20 decimal digits. + int digits = (num_digits != 0) ? num_digits + : (base == 16) ? 16 + : (base == 10) ? 19 + : 0xFF; // fallback case... + + // Match num_digits digits + // Digits irrelevant to the chosen number base are filtered in the validator + mask += QString("H").repeated(std::max(num_digits, 1)); + + // Switch off case conversion + mask += "!"; + + mask += StringToInputMask(suffix); + } + lineEdit()->setInputMask(mask); + + // Set new text without changing the cursor position. This will cause the cursor to briefly + // appear at the end of the line and then to jump back to its original position. That's + // a bit ugly, but better than having setText() move the cursor permanently all the time. + int cursor_position = lineEdit()->cursorPosition(); + lineEdit()->setText(TextFromValue()); + lineEdit()->setCursorPosition(cursor_position); +} + +QString CSpinBox::TextFromValue() +{ + return prefix + + QString(HasSign() ? ((value < 0) ? "-" : "+") : "") + + QString("%1").arg(abs(value), num_digits, base, QLatin1Char('0')).toUpper() + + suffix; +} + +qint64 CSpinBox::ValueFromText() +{ + unsigned strpos = prefix.length(); + + QString num_string = text().mid(strpos, text().length() - strpos - suffix.length()); + return num_string.toLongLong(nullptr, base); +} + +bool CSpinBox::HasSign() const +{ + return base == 10 && min_value < 0; +} + +void CSpinBox::OnEditingFinished() +{ + // Only update for valid input + QString input = lineEdit()->text(); + int pos = 0; + if (QValidator::Acceptable == validate(input, pos)) + SetValue(ValueFromText()); +} + +QValidator::State CSpinBox::validate(QString& input, int& pos) const +{ + if (!prefix.isEmpty() && input.left(prefix.length()) != prefix) + return QValidator::Invalid; + + unsigned strpos = prefix.length(); + + // Empty "numbers" allowed as intermediate values + if (strpos >= input.length() - HasSign() - suffix.length()) + return QValidator::Intermediate; + + _dbg_assert_(GUI, base <= 10 || base == 16); + QString regexp; + + // Demand sign character for negative ranges + if (HasSign()) + regexp += "[+\\-]"; + + // Match digits corresponding to the chosen number base. + regexp += QString("[0-%1").arg(std::min(base, 9)); + if (base == 16) { + regexp += "a-fA-F"; + } + regexp += "]"; + + // Specify number of digits + if (num_digits > 0) { + regexp += QString("{%1}").arg(num_digits); + } else { + regexp += "+"; + } + + // Match string + QRegExp num_regexp(regexp); + int num_pos = strpos; + QString sub_input = input.mid(strpos, input.length() - strpos - suffix.length()); + + if (!num_regexp.exactMatch(sub_input) && num_regexp.matchedLength() == 0) + return QValidator::Invalid; + + sub_input = sub_input.left(num_regexp.matchedLength()); + bool ok; + qint64 val = sub_input.toLongLong(&ok, base); + + if (!ok) + return QValidator::Invalid; + + // Outside boundaries => don't accept + if (val < min_value || val > max_value) + return QValidator::Invalid; + + // Make sure we are actually at the end of this string... + strpos += num_regexp.matchedLength(); + + if (!suffix.isEmpty() && input.mid(strpos) != suffix) { + return QValidator::Invalid; + } else { + strpos += suffix.length(); + } + + if (strpos != input.length()) + return QValidator::Invalid; + + // At this point we can say for sure that the input is fine. Let's fix it up a bit though + input.replace(num_pos, sub_input.length(), sub_input.toUpper()); + + return QValidator::Acceptable; +} diff --git a/src/citra_qt/util/spinbox.hxx b/src/citra_qt/util/spinbox.hxx new file mode 100644 index 000000000..68f5b9894 --- /dev/null +++ b/src/citra_qt/util/spinbox.hxx @@ -0,0 +1,88 @@ +// Licensed under GPLv2+ +// Refer to the license.txt file included. + + +// Copyright 2014 Tony Wasserka +// All rights reserved. +// +// 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 owner 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. + + +#pragma once + +#include <QAbstractSpinBox> +#include <QtGlobal> + +class QVariant; + +/** + * A custom spin box widget with enhanced functionality over Qt's QSpinBox + */ +class CSpinBox : public QAbstractSpinBox { + Q_OBJECT + +public: + CSpinBox(QWidget* parent = nullptr); + + void stepBy(int steps) override; + StepEnabled stepEnabled() const override; + + void SetValue(qint64 val); + + void SetRange(qint64 min, qint64 max); + + void SetBase(int base); + + void SetPrefix(const QString& prefix); + void SetSuffix(const QString& suffix); + + void SetNumDigits(int num_digits); + + QValidator::State validate(QString& input, int& pos) const override; + +signals: + void ValueChanged(qint64 val); + +private slots: + void OnEditingFinished(); + +private: + void UpdateText(); + + bool HasSign() const; + + QString TextFromValue(); + qint64 ValueFromText(); + + qint64 min_value, max_value; + + qint64 value; + + QString prefix, suffix; + + int base; + + int num_digits; +}; diff --git a/src/common/chunk_file.h b/src/common/chunk_file.h index 609784076..32af74594 100644 --- a/src/common/chunk_file.h +++ b/src/common/chunk_file.h @@ -204,11 +204,11 @@ public: { for (auto it = x.begin(), end = x.end(); it != end; ++it) { - if (it->second != NULL) + if (it->second != nullptr) delete it->second; } } - T *dv = NULL; + T *dv = nullptr; DoMap(x, dv); } @@ -264,11 +264,11 @@ public: { for (auto it = x.begin(), end = x.end(); it != end; ++it) { - if (it->second != NULL) + if (it->second != nullptr) delete it->second; } } - T *dv = NULL; + T *dv = nullptr; DoMultimap(x, dv); } @@ -320,7 +320,7 @@ public: template<class T> void Do(std::vector<T *> &x) { - T *dv = NULL; + T *dv = nullptr; DoVector(x, dv); } @@ -369,7 +369,7 @@ public: template<class T> void Do(std::deque<T *> &x) { - T *dv = NULL; + T *dv = nullptr; DoDeque(x, dv); } @@ -395,7 +395,7 @@ public: template<class T> void Do(std::list<T *> &x) { - T *dv = NULL; + T *dv = nullptr; Do(x, dv); } @@ -433,7 +433,7 @@ public: { for (auto it = x.begin(), end = x.end(); it != end; ++it) { - if (*it != NULL) + if (*it != nullptr) delete *it; } } @@ -518,7 +518,7 @@ public: void DoClass(T *&x) { if (mode == MODE_READ) { - if (x != NULL) + if (x != nullptr) delete x; x = new T(); } @@ -567,7 +567,7 @@ public: { if (mode == MODE_READ) { - cur->next = 0; + cur->next = nullptr; list_cur = cur; if (prev) prev->next = cur; @@ -586,13 +586,13 @@ public: if (mode == MODE_READ) { if (prev) - prev->next = 0; + prev->next = nullptr; if (list_end) *list_end = prev; if (list_cur) { if (list_start == list_cur) - list_start = 0; + list_start = nullptr; do { LinkedListItem<T>* next = list_cur->next; diff --git a/src/common/common_funcs.h b/src/common/common_funcs.h index d84ec4c42..db041780a 100644 --- a/src/common/common_funcs.h +++ b/src/common/common_funcs.h @@ -4,6 +4,8 @@ #pragma once +#include "common_types.h" + #ifdef _WIN32 #define SLEEP(x) Sleep(x) #else @@ -37,6 +39,8 @@ template<> struct CompileTimeAssert<true> {}; #include <sys/endian.h> #endif +#include "common_types.h" + // go to debugger mode #ifdef GEKKO #define Crash() @@ -73,6 +77,8 @@ inline u64 _rotr64(u64 x, unsigned int shift){ } #else // WIN32 +#include <locale.h> + // Function Cross-Compatibility #define strcasecmp _stricmp #define strncasecmp _strnicmp @@ -106,7 +112,7 @@ inline u64 _rotr64(u64 x, unsigned int shift){ // Restore the global locale _configthreadlocale(_DISABLE_PER_THREAD_LOCALE); } - else if(new_locale != NULL) + else if(new_locale != nullptr) { // Configure the thread to set the locale only for this thread _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); diff --git a/src/common/console_listener.cpp b/src/common/console_listener.cpp index d7f27c358..b6042796d 100644 --- a/src/common/console_listener.cpp +++ b/src/common/console_listener.cpp @@ -16,7 +16,7 @@ ConsoleListener::ConsoleListener() { #ifdef _WIN32 - hConsole = NULL; + hConsole = nullptr; bUseColor = true; #else bUseColor = isatty(fileno(stdout)); @@ -66,19 +66,19 @@ void ConsoleListener::UpdateHandle() void ConsoleListener::Close() { #ifdef _WIN32 - if (hConsole == NULL) + if (hConsole == nullptr) return; FreeConsole(); - hConsole = NULL; + hConsole = nullptr; #else - fflush(NULL); + fflush(nullptr); #endif } bool ConsoleListener::IsOpen() { #ifdef _WIN32 - return (hConsole != NULL); + return (hConsole != nullptr); #else return true; #endif diff --git a/src/common/extended_trace.cpp b/src/common/extended_trace.cpp index bf61ac1d1..cf7c346d4 100644 --- a/src/common/extended_trace.cpp +++ b/src/common/extended_trace.cpp @@ -82,7 +82,7 @@ static void InitSymbolPath( PSTR lpszSymbolPath, PCSTR lpszIniPath ) } // Add user defined path - if ( lpszIniPath != NULL ) + if ( lpszIniPath != nullptr ) if ( lpszIniPath[0] != '\0' ) { strcat( lpszSymbolPath, ";" ); @@ -138,7 +138,7 @@ static BOOL GetFunctionInfoFromAddresses( ULONG fnAddress, ULONG stackAddress, L DWORD dwSymSize = 10000; TCHAR lpszUnDSymbol[BUFFERSIZE]=_T("?"); CHAR lpszNonUnicodeUnDSymbol[BUFFERSIZE]="?"; - LPTSTR lpszParamSep = NULL; + LPTSTR lpszParamSep = nullptr; LPTSTR lpszParsed = lpszUnDSymbol; PIMAGEHLP_SYMBOL pSym = (PIMAGEHLP_SYMBOL)GlobalAlloc( GMEM_FIXED, dwSymSize ); @@ -187,13 +187,13 @@ static BOOL GetFunctionInfoFromAddresses( ULONG fnAddress, ULONG stackAddress, L // Let's go through the stack, and modify the function prototype, and insert the actual // parameter values from the stack - if ( _tcsstr( lpszUnDSymbol, _T("(void)") ) == NULL && _tcsstr( lpszUnDSymbol, _T("()") ) == NULL) + if ( _tcsstr( lpszUnDSymbol, _T("(void)") ) == nullptr && _tcsstr( lpszUnDSymbol, _T("()") ) == nullptr) { ULONG index = 0; for( ; ; index++ ) { lpszParamSep = _tcschr( lpszParsed, _T(',') ); - if ( lpszParamSep == NULL ) + if ( lpszParamSep == nullptr ) break; *lpszParamSep = _T('\0'); @@ -205,7 +205,7 @@ static BOOL GetFunctionInfoFromAddresses( ULONG fnAddress, ULONG stackAddress, L } lpszParamSep = _tcschr( lpszParsed, _T(')') ); - if ( lpszParamSep != NULL ) + if ( lpszParamSep != nullptr ) { *lpszParamSep = _T('\0'); @@ -248,7 +248,7 @@ static BOOL GetSourceInfoFromAddress( UINT address, LPTSTR lpszSourceInfo ) PCSTR2LPTSTR( lineInfo.FileName, lpszFileName ); TCHAR fname[_MAX_FNAME]; TCHAR ext[_MAX_EXT]; - _tsplitpath(lpszFileName, NULL, NULL, fname, ext); + _tsplitpath(lpszFileName, nullptr, nullptr, fname, ext); _stprintf( lpszSourceInfo, _T("%s%s(%d)"), fname, ext, lineInfo.LineNumber ); ret = TRUE; } @@ -332,11 +332,11 @@ void StackTrace( HANDLE hThread, const char* lpszMessage, FILE *file ) hProcess, hThread, &callStack, - NULL, - NULL, + nullptr, + nullptr, SymFunctionTableAccess, SymGetModuleBase, - NULL); + nullptr); if ( index == 0 ) continue; @@ -389,11 +389,11 @@ void StackTrace(HANDLE hThread, const char* lpszMessage, FILE *file, DWORD eip, hProcess, hThread, &callStack, - NULL, - NULL, + nullptr, + nullptr, SymFunctionTableAccess, SymGetModuleBase, - NULL); + nullptr); if ( index == 0 ) continue; diff --git a/src/common/fifo_queue.h b/src/common/fifo_queue.h index 2c18285d4..b426e6596 100644 --- a/src/common/fifo_queue.h +++ b/src/common/fifo_queue.h @@ -57,7 +57,7 @@ public: // advance the read pointer m_read_ptr = m_read_ptr->next; // set the next element to NULL to stop the recursive deletion - tmpptr->next = NULL; + tmpptr->next = nullptr; delete tmpptr; // this also deletes the element } @@ -86,7 +86,7 @@ private: class ElementPtr { public: - ElementPtr() : current(NULL), next(NULL) {} + ElementPtr() : current(nullptr), next(nullptr) {} ~ElementPtr() { diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index b6dec838c..6c4860503 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -140,7 +140,7 @@ bool CreateDir(const std::string &path) { INFO_LOG(COMMON, "CreateDir: directory %s", path.c_str()); #ifdef _WIN32 - if (::CreateDirectory(Common::UTF8ToTStr(path).c_str(), NULL)) + if (::CreateDirectory(Common::UTF8ToTStr(path).c_str(), nullptr)) return true; DWORD error = GetLastError(); if (error == ERROR_ALREADY_EXISTS) @@ -423,7 +423,7 @@ u32 ScanDirectoryTree(const std::string &directory, FSTEntry& parentEntry) FSTEntry entry; const std::string virtualName(Common::TStrToUTF8(ffd.cFileName)); #else - struct dirent dirent, *result = NULL; + struct dirent dirent, *result = nullptr; DIR *dirp = opendir(directory.c_str()); if (!dirp) @@ -491,7 +491,7 @@ bool DeleteDirRecursively(const std::string &directory) { const std::string virtualName(Common::TStrToUTF8(ffd.cFileName)); #else - struct dirent dirent, *result = NULL; + struct dirent dirent, *result = nullptr; DIR *dirp = opendir(directory.c_str()); if (!dirp) return false; @@ -552,7 +552,7 @@ void CopyDir(const std::string &source_path, const std::string &dest_path) if (!FileUtil::Exists(source_path)) return; if (!FileUtil::Exists(dest_path)) FileUtil::CreateFullPath(dest_path); - struct dirent dirent, *result = NULL; + struct dirent dirent, *result = nullptr; DIR *dirp = opendir(source_path.c_str()); if (!dirp) return; @@ -586,11 +586,11 @@ std::string GetCurrentDir() { char *dir; // Get the current working directory (getcwd uses malloc) - if (!(dir = __getcwd(NULL, 0))) { + if (!(dir = __getcwd(nullptr, 0))) { ERROR_LOG(COMMON, "GetCurrentDirectory failed: %s", GetLastErrorMsg()); - return NULL; + return nullptr; } std::string strDir = dir; free(dir); @@ -626,7 +626,7 @@ std::string& GetExeDirectory() if (DolphinPath.empty()) { TCHAR Dolphin_exe_Path[2048]; - GetModuleFileName(NULL, Dolphin_exe_Path, 2048); + GetModuleFileName(nullptr, Dolphin_exe_Path, 2048); DolphinPath = Common::TStrToUTF8(Dolphin_exe_Path); DolphinPath = DolphinPath.substr(0, DolphinPath.find_last_of('\\')); } @@ -826,7 +826,7 @@ void SplitFilename83(const std::string& filename, std::array<char, 9>& short_nam } IOFile::IOFile() - : m_file(NULL), m_good(true) + : m_file(nullptr), m_good(true) {} IOFile::IOFile(std::FILE* file) @@ -834,7 +834,7 @@ IOFile::IOFile(std::FILE* file) {} IOFile::IOFile(const std::string& filename, const char openmode[]) - : m_file(NULL), m_good(true) + : m_file(nullptr), m_good(true) { Open(filename, openmode); } @@ -845,7 +845,7 @@ IOFile::~IOFile() } IOFile::IOFile(IOFile&& other) - : m_file(NULL), m_good(true) + : m_file(nullptr), m_good(true) { Swap(other); } @@ -880,14 +880,14 @@ bool IOFile::Close() if (!IsOpen() || 0 != std::fclose(m_file)) m_good = false; - m_file = NULL; + m_file = nullptr; return m_good; } std::FILE* IOFile::ReleaseHandle() { std::FILE* const ret = m_file; - m_file = NULL; + m_file = nullptr; return ret; } diff --git a/src/common/file_util.h b/src/common/file_util.h index 72b80be8a..beaf7174a 100644 --- a/src/common/file_util.h +++ b/src/common/file_util.h @@ -202,11 +202,11 @@ public: return WriteArray(reinterpret_cast<const char*>(data), length); } - bool IsOpen() { return NULL != m_file; } + bool IsOpen() { return nullptr != m_file; } // m_good is set to false when a read, write or other function fails bool IsGood() { return m_good; } - operator void*() { return m_good ? m_file : NULL; } + operator void*() { return m_good ? m_file : nullptr; } std::FILE* ReleaseHandle(); diff --git a/src/common/linear_disk_cache.h b/src/common/linear_disk_cache.h index f4263f72a..bb1b5174f 100644 --- a/src/common/linear_disk_cache.h +++ b/src/common/linear_disk_cache.h @@ -70,7 +70,7 @@ public: // good header, read some key/value pairs K key; - V *value = NULL; + V *value = nullptr; u32 value_size; u32 entry_number; diff --git a/src/common/log.h b/src/common/log.h index 14ad98c08..78f0dae4d 100644 --- a/src/common/log.h +++ b/src/common/log.h @@ -4,6 +4,9 @@ #pragma once +#include "common/common_funcs.h" +#include "common/msg_handler.h" + #ifndef LOGGING #define LOGGING #endif @@ -62,7 +65,6 @@ enum LOG_TYPE { WII_IPC_HID, KERNEL, SVC, - NDMA, HLE, RENDER, GPU, diff --git a/src/common/log_manager.cpp b/src/common/log_manager.cpp index 2ef7d98c0..128c15388 100644 --- a/src/common/log_manager.cpp +++ b/src/common/log_manager.cpp @@ -21,7 +21,7 @@ void GenericLog(LogTypes::LOG_LEVELS level, LogTypes::LOG_TYPE type, const char* va_end(args); } -LogManager *LogManager::m_logManager = NULL; +LogManager *LogManager::m_logManager = nullptr; LogManager::LogManager() { @@ -68,7 +68,6 @@ LogManager::LogManager() m_Log[LogTypes::RENDER] = new LogContainer("RENDER", "RENDER"); m_Log[LogTypes::GPU] = new LogContainer("GPU", "GPU"); m_Log[LogTypes::SVC] = new LogContainer("SVC", "Supervisor Call HLE"); - m_Log[LogTypes::NDMA] = new LogContainer("NDMA", "NDMA"); m_Log[LogTypes::HLE] = new LogContainer("HLE", "High Level Emulation"); m_Log[LogTypes::HW] = new LogContainer("HW", "Hardware"); m_Log[LogTypes::ACTIONREPLAY] = new LogContainer("ActionReplay", "ActionReplay"); @@ -141,7 +140,7 @@ void LogManager::Init() void LogManager::Shutdown() { delete m_logManager; - m_logManager = NULL; + m_logManager = nullptr; } LogContainer::LogContainer(const char* shortName, const char* fullName, bool enable) diff --git a/src/common/mem_arena.cpp b/src/common/mem_arena.cpp index 67dbaf509..7d4fda0e2 100644 --- a/src/common/mem_arena.cpp +++ b/src/common/mem_arena.cpp @@ -30,7 +30,7 @@ #endif #ifdef IOS -void* globalbase = NULL; +void* globalbase = nullptr; #endif #ifdef ANDROID @@ -121,7 +121,7 @@ void MemArena::GrabLowMemSpace(size_t size) { #ifdef _WIN32 #ifndef _XBOX - hMemoryMapping = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, (DWORD)(size), NULL); + hMemoryMapping = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0, (DWORD)(size), nullptr); GetSystemInfo(&sysInfo); #endif #elif defined(ANDROID) @@ -178,7 +178,7 @@ void *MemArena::CreateView(s64 offset, size_t size, void *base) #ifdef _XBOX size = roundup(size); // use 64kb pages - void * ptr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); + void * ptr = VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); return ptr; #else size = roundup(size); @@ -243,8 +243,8 @@ u8* MemArena::Find4GBBase() return base; #else #ifdef IOS - void* base = NULL; - if (globalbase == NULL){ + void* base = nullptr; + if (globalbase == nullptr){ base = mmap(0, 0x08000000, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); if (base == MAP_FAILED) { @@ -357,7 +357,7 @@ bail: if (views[j].out_ptr_low && *views[j].out_ptr_low) { arena->ReleaseView(*views[j].out_ptr_low, views[j].size); - *views[j].out_ptr_low = NULL; + *views[j].out_ptr_low = nullptr; } if (*views[j].out_ptr) { @@ -369,7 +369,7 @@ bail: arena->ReleaseView(*views[j].out_ptr, views[j].size); } #endif - *views[j].out_ptr = NULL; + *views[j].out_ptr = nullptr; } } return false; @@ -415,7 +415,7 @@ u8 *MemoryMap_Setup(const MemoryView *views, int num_views, u32 flags, MemArena #elif defined(_WIN32) // Try a whole range of possible bases. Return once we got a valid one. u32 max_base_addr = 0x7FFF0000 - 0x10000000; - u8 *base = NULL; + u8 *base = nullptr; for (u32 base_addr = 0x01000000; base_addr < max_base_addr; base_addr += 0x400000) { @@ -463,8 +463,8 @@ void MemoryMap_Shutdown(const MemoryView *views, int num_views, u32 flags, MemAr arena->ReleaseView(*views[i].out_ptr_low, views[i].size); if (*views[i].out_ptr && (views[i].out_ptr_low && *views[i].out_ptr != *views[i].out_ptr_low)) arena->ReleaseView(*views[i].out_ptr, views[i].size); - *views[i].out_ptr = NULL; + *views[i].out_ptr = nullptr; if (views[i].out_ptr_low) - *views[i].out_ptr_low = NULL; + *views[i].out_ptr_low = nullptr; } } diff --git a/src/common/memory_util.cpp b/src/common/memory_util.cpp index b6f66e4e1..93da5500b 100644 --- a/src/common/memory_util.cpp +++ b/src/common/memory_util.cpp @@ -93,7 +93,7 @@ void* AllocateMemoryPages(size_t size) // printf("Mapped memory at %p (size %ld)\n", ptr, // (unsigned long)size); - if (ptr == NULL) + if (ptr == nullptr) PanicAlert("Failed to allocate raw memory"); return ptr; @@ -104,7 +104,7 @@ void* AllocateAlignedMemory(size_t size,size_t alignment) #ifdef _WIN32 void* ptr = _aligned_malloc(size,alignment); #else - void* ptr = NULL; + void* ptr = nullptr; #ifdef ANDROID ptr = memalign(alignment, size); #else @@ -116,7 +116,7 @@ void* AllocateAlignedMemory(size_t size,size_t alignment) // printf("Mapped memory at %p (size %ld)\n", ptr, // (unsigned long)size); - if (ptr == NULL) + if (ptr == nullptr) PanicAlert("Failed to allocate aligned memory"); return ptr; @@ -130,7 +130,7 @@ void FreeMemoryPages(void* ptr, size_t size) if (!VirtualFree(ptr, 0, MEM_RELEASE)) PanicAlert("FreeMemoryPages failed!\n%s", GetLastErrorMsg()); - ptr = NULL; // Is this our responsibility? + ptr = nullptr; // Is this our responsibility? #else munmap(ptr, size); @@ -184,7 +184,7 @@ std::string MemUsage() // Print information about the memory usage of the process. hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID); - if (NULL == hProcess) return "MemUsage Error"; + if (nullptr == hProcess) return "MemUsage Error"; if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) Ret = Common::StringFromFormat("%s K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7).c_str()); diff --git a/src/common/misc.cpp b/src/common/misc.cpp index cf6df44e8..bc9d26188 100644 --- a/src/common/misc.cpp +++ b/src/common/misc.cpp @@ -23,9 +23,9 @@ const char* GetLastErrorMsg() #ifdef _WIN32 static __declspec(thread) char err_str[buff_size] = {}; - FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, nullptr, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - err_str, buff_size, NULL); + err_str, buff_size, nullptr); #else static __thread char err_str[buff_size] = {}; diff --git a/src/common/platform.h b/src/common/platform.h index d9f095433..53d98fe74 100644 --- a/src/common/platform.h +++ b/src/common/platform.h @@ -77,7 +77,7 @@ inline struct tm* localtime_r(const time_t *clock, struct tm *result) { if (localtime_s(result, clock) == 0) return result; - return NULL; + return nullptr; } #else diff --git a/src/common/string_util.cpp b/src/common/string_util.cpp index dcec9275f..19e162c27 100644 --- a/src/common/string_util.cpp +++ b/src/common/string_util.cpp @@ -31,7 +31,7 @@ std::string ToUpper(std::string str) { // faster than sscanf bool AsciiToHex(const char* _szValue, u32& result) { - char *endptr = NULL; + char *endptr = nullptr; const u32 value = strtoul(_szValue, &endptr, 16); if (!endptr || *endptr) @@ -69,7 +69,7 @@ bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list ar // will be present in the middle of a multibyte sequence. // // This is why we lookup an ANSI (cp1252) locale here and use _vsnprintf_l. - static locale_t c_locale = NULL; + static locale_t c_locale = nullptr; if (!c_locale) c_locale = _create_locale(LC_ALL, ".1252"); writtenCount = _vsnprintf_l(out, outsize, format, c_locale, args); @@ -92,7 +92,7 @@ bool CharArrayFromFormatV(char* out, int outsize, const char* format, va_list ar std::string StringFromFormat(const char* format, ...) { va_list args; - char *buf = NULL; + char *buf = nullptr; #ifdef _WIN32 int required = 0; @@ -162,7 +162,7 @@ std::string StripQuotes(const std::string& s) bool TryParse(const std::string &str, u32 *const output) { - char *endptr = NULL; + char *endptr = nullptr; // Reset errno to a value other than ERANGE errno = 0; @@ -528,7 +528,7 @@ std::u16string UTF8ToUTF16(const std::string& input) { std::u16string result; - iconv_t const conv_desc = iconv_open("UTF-16", "UTF-8"); + iconv_t const conv_desc = iconv_open("UTF-16LE", "UTF-8"); if ((iconv_t)(-1) == conv_desc) { ERROR_LOG(COMMON, "Iconv initialization failure [UTF-8]: %s", strerror(errno)); @@ -582,7 +582,7 @@ std::u16string UTF8ToUTF16(const std::string& input) std::string UTF16ToUTF8(const std::u16string& input) { - return CodeToUTF8("UTF-16", input); + return CodeToUTF8("UTF-16LE", input); } std::string CP1252ToUTF8(const std::string& input) diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h index 59efbce4c..7e3b620c7 100644 --- a/src/common/thread_queue_list.h +++ b/src/common/thread_queue_list.h @@ -37,7 +37,7 @@ struct ThreadQueueList { ~ThreadQueueList() { for (int i = 0; i < NUM_QUEUES; ++i) { - if (queues[i].data != NULL) + if (queues[i].data != nullptr) free(queues[i].data); } } @@ -46,7 +46,7 @@ struct ThreadQueueList { int contains(const IdType uid) { for (int i = 0; i < NUM_QUEUES; ++i) { - if (queues[i].data == NULL) + if (queues[i].data == nullptr) continue; Queue *cur = &queues[i]; @@ -133,7 +133,7 @@ struct ThreadQueueList { inline void clear() { for (int i = 0; i < NUM_QUEUES; ++i) { - if (queues[i].data != NULL) + if (queues[i].data != nullptr) free(queues[i].data); } memset(queues, 0, sizeof(queues)); @@ -147,7 +147,7 @@ struct ThreadQueueList { inline void prepare(u32 priority) { Queue *cur = &queues[priority]; - if (cur->next == NULL) + if (cur->next == nullptr) link(priority, INITIAL_CAPACITY); } @@ -176,7 +176,7 @@ private: for (int i = (int) priority - 1; i >= 0; --i) { - if (queues[i].next != NULL) + if (queues[i].next != nullptr) { cur->next = queues[i].next; queues[i].next = cur; @@ -193,7 +193,7 @@ private: int size = cur->end - cur->first; if (size >= cur->capacity - 2) { IdType *new_data = (IdType *)realloc(cur->data, cur->capacity * 2 * sizeof(IdType)); - if (new_data != NULL) { + if (new_data != nullptr) { cur->capacity *= 2; cur->data = new_data; } diff --git a/src/common/timer.cpp b/src/common/timer.cpp index ded4a344e..4a797f751 100644 --- a/src/common/timer.cpp +++ b/src/common/timer.cpp @@ -25,7 +25,7 @@ u32 Timer::GetTimeMs() return timeGetTime(); #else struct timeval t; - (void)gettimeofday(&t, NULL); + (void)gettimeofday(&t, nullptr); return ((u32)(t.tv_sec * 1000 + t.tv_usec / 1000)); #endif } @@ -183,7 +183,7 @@ std::string Timer::GetTimeFormatted() return StringFromFormat("%s:%03i", tmp, tp.millitm); #else struct timeval t; - (void)gettimeofday(&t, NULL); + (void)gettimeofday(&t, nullptr); return StringFromFormat("%s:%03d", tmp, (int)(t.tv_usec / 1000)); #endif } @@ -197,7 +197,7 @@ double Timer::GetDoubleTime() (void)::ftime(&tp); #else struct timeval t; - (void)gettimeofday(&t, NULL); + (void)gettimeofday(&t, nullptr); #endif // Get continuous timestamp u64 TmpSeconds = Common::Timer::GetTimeSinceJan1970(); diff --git a/src/common/utf8.cpp b/src/common/utf8.cpp index be4ebc855..66a2f6339 100644 --- a/src/common/utf8.cpp +++ b/src/common/utf8.cpp @@ -281,28 +281,28 @@ int u8_read_escape_sequence(const char *str, u32 *dest) do { digs[dno++] = str[i++]; } while (octal_digit(str[i]) && dno < 3); - ch = strtol(digs, NULL, 8); + ch = strtol(digs, nullptr, 8); } else if (str[0] == 'x') { while (hex_digit(str[i]) && dno < 2) { digs[dno++] = str[i++]; } if (dno > 0) - ch = strtol(digs, NULL, 16); + ch = strtol(digs, nullptr, 16); } else if (str[0] == 'u') { while (hex_digit(str[i]) && dno < 4) { digs[dno++] = str[i++]; } if (dno > 0) - ch = strtol(digs, NULL, 16); + ch = strtol(digs, nullptr, 16); } else if (str[0] == 'U') { while (hex_digit(str[i]) && dno < 8) { digs[dno++] = str[i++]; } if (dno > 0) - ch = strtol(digs, NULL, 16); + ch = strtol(digs, nullptr, 16); } *dest = ch; @@ -353,7 +353,7 @@ const char *u8_strchr(const char *s, u32 ch, int *charn) lasti = i; (*charn)++; } - return NULL; + return nullptr; } const char *u8_memchr(const char *s, u32 ch, size_t sz, int *charn) @@ -378,7 +378,7 @@ const char *u8_memchr(const char *s, u32 ch, size_t sz, int *charn) lasti = i; (*charn)++; } - return NULL; + return nullptr; } int u8_is_locale_utf8(const char *locale) @@ -419,35 +419,35 @@ bool UTF8StringHasNonASCII(const char *utf8string) { std::string ConvertWStringToUTF8(const wchar_t *wstr) { int len = (int)wcslen(wstr); - int size = (int)WideCharToMultiByte(CP_UTF8, 0, wstr, len, 0, 0, NULL, NULL); + int size = (int)WideCharToMultiByte(CP_UTF8, 0, wstr, len, 0, 0, nullptr, nullptr); std::string s; s.resize(size); if (size > 0) { - WideCharToMultiByte(CP_UTF8, 0, wstr, len, &s[0], size, NULL, NULL); + WideCharToMultiByte(CP_UTF8, 0, wstr, len, &s[0], size, nullptr, nullptr); } return s; } std::string ConvertWStringToUTF8(const std::wstring &wstr) { int len = (int)wstr.size(); - int size = (int)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), len, 0, 0, NULL, NULL); + int size = (int)WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), len, 0, 0, nullptr, nullptr); std::string s; s.resize(size); if (size > 0) { - WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), len, &s[0], size, NULL, NULL); + WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), len, &s[0], size, nullptr, nullptr); } return s; } void ConvertUTF8ToWString(wchar_t *dest, size_t destSize, const std::string &source) { int len = (int)source.size(); - int size = (int)MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, NULL, 0); + int size = (int)MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, nullptr, 0); MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, dest, std::min((int)destSize, size)); } std::wstring ConvertUTF8ToWString(const std::string &source) { int len = (int)source.size(); - int size = (int)MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, NULL, 0); + int size = (int)MultiByteToWideChar(CP_UTF8, 0, source.c_str(), len, nullptr, 0); std::wstring str; str.resize(size); if (size > 0) { diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 48241c3d4..8f6792791 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -59,10 +59,10 @@ set(SRCS hle/svc.cpp hw/gpu.cpp hw/hw.cpp - hw/ndma.cpp loader/elf.cpp loader/loader.cpp loader/ncch.cpp + loader/3dsx.cpp core.cpp core_timing.cpp mem_map.cpp @@ -139,10 +139,10 @@ set(HEADERS hle/svc.h hw/gpu.h hw/hw.h - hw/ndma.h loader/elf.h loader/loader.h loader/ncch.h + loader/3dsx.h core.h core_timing.h mem_map.h diff --git a/src/core/arm/interpreter/armemu.cpp b/src/core/arm/interpreter/armemu.cpp index 73223874e..d717bd2c8 100644 --- a/src/core/arm/interpreter/armemu.cpp +++ b/src/core/arm/interpreter/armemu.cpp @@ -5724,7 +5724,7 @@ L_stm_s_takeabort: s16 a2 = ((state->Reg[src1] >> 0x10) & 0xFFFF); s16 b1 = (state->Reg[src2] & 0xFFFF); s16 b2 = ((state->Reg[src2] >> 0x10) & 0xFFFF); - state->Reg[tar] = (a1 - a2)&0xFFFF | (((b1 - b2)&0xFFFF)<< 0x10); + state->Reg[tar] = ((a1 - a2) & 0xFFFF) | (((b1 - b2)&0xFFFF)<< 0x10); return 1; } else if ((instr & 0xFF0) == 0xf10)//sadd16 @@ -5736,7 +5736,7 @@ L_stm_s_takeabort: s16 a2 = ((state->Reg[src1] >> 0x10) & 0xFFFF); s16 b1 = (state->Reg[src2] & 0xFFFF); s16 b2 = ((state->Reg[src2] >> 0x10) & 0xFFFF); - state->Reg[tar] = (a1 + a2)&0xFFFF | (((b1 + b2)&0xFFFF)<< 0x10); + state->Reg[tar] = ((a1 + a2) & 0xFFFF) | (((b1 + b2)&0xFFFF)<< 0x10); return 1; } else if ((instr & 0xFF0) == 0xf50)//ssax @@ -5748,7 +5748,7 @@ L_stm_s_takeabort: s16 a2 = ((state->Reg[src1] >> 0x10) & 0xFFFF); s16 b1 = (state->Reg[src2] & 0xFFFF); s16 b2 = ((state->Reg[src2] >> 0x10) & 0xFFFF); - state->Reg[tar] = (a1 - b2) & 0xFFFF | (((a2 + b1) & 0xFFFF) << 0x10); + state->Reg[tar] = ((a1 + b2) & 0xFFFF) | (((a2 - b1) & 0xFFFF) << 0x10); return 1; } else if ((instr & 0xFF0) == 0xf30)//sasx @@ -5760,7 +5760,7 @@ L_stm_s_takeabort: s16 a2 = ((state->Reg[src1] >> 0x10) & 0xFFFF); s16 b1 = (state->Reg[src2] & 0xFFFF); s16 b2 = ((state->Reg[src2] >> 0x10) & 0xFFFF); - state->Reg[tar] = (a2 - b1) & 0xFFFF | (((a2 + b1) & 0xFFFF) << 0x10); + state->Reg[tar] = ((a1 - b2) & 0xFFFF) | (((a2 + b1) & 0xFFFF) << 0x10); return 1; } else printf ("Unhandled v6 insn: sadd/ssub\n"); diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 558c6cbf7..bf8acf41f 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -67,7 +67,7 @@ s64 idledCycles; static std::recursive_mutex externalEventSection; // Warning: not included in save state. -void(*advanceCallback)(int cyclesExecuted) = NULL; +void(*advanceCallback)(int cyclesExecuted) = nullptr; void SetClockFrequencyMHz(int cpuMhz) { @@ -231,7 +231,7 @@ void ClearPendingEvents() void AddEventToQueue(Event* ne) { - Event* prev = NULL; + Event* prev = nullptr; Event** pNext = &first; for (;;) { @@ -327,7 +327,7 @@ s64 UnscheduleThreadsafeEvent(int event_type, u64 userdata) } if (!tsFirst) { - tsLast = NULL; + tsLast = nullptr; return result; } @@ -433,7 +433,7 @@ void RemoveThreadsafeEvent(int event_type) } if (!tsFirst) { - tsLast = NULL; + tsLast = nullptr; return; } Event *prev = tsFirst; @@ -495,7 +495,7 @@ void MoveEvents() AddEventToQueue(tsFirst); tsFirst = next; } - tsLast = NULL; + tsLast = nullptr; // Move free events to threadsafe pool while (allocatedTsEvents > 0 && eventPool) @@ -614,7 +614,7 @@ void DoState(PointerWrap &p) // These (should) be filled in later by the modules. event_types.resize(n, EventType(AntiCrashCallback, "INVALID EVENT")); - p.DoLinkedList<BaseEvent, GetNewEvent, FreeEvent, Event_DoState>(first, (Event **)NULL); + p.DoLinkedList<BaseEvent, GetNewEvent, FreeEvent, Event_DoState>(first, (Event **)nullptr); p.DoLinkedList<BaseEvent, GetNewTsEvent, FreeTsEvent, Event_DoState>(tsFirst, &tsLast); p.Do(g_clock_rate_arm11); diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp index 169ab0f1c..fc0b9b72d 100644 --- a/src/core/file_sys/archive_sdmc.cpp +++ b/src/core/file_sys/archive_sdmc.cpp @@ -100,6 +100,8 @@ bool Archive_SDMC::RenameDirectory(const FileSys::Path& src_path, const FileSys: std::unique_ptr<Directory> Archive_SDMC::OpenDirectory(const Path& path) const { DEBUG_LOG(FILESYS, "called path=%s", path.DebugStr().c_str()); Directory_SDMC* directory = new Directory_SDMC(this, path); + if (!directory->Open()) + return nullptr; return std::unique_ptr<Directory>(directory); } diff --git a/src/core/file_sys/directory.h b/src/core/file_sys/directory.h index e10431337..1bb4101d6 100644 --- a/src/core/file_sys/directory.h +++ b/src/core/file_sys/directory.h @@ -42,6 +42,12 @@ public: virtual ~Directory() { } /** + * Open the directory + * @return true if the directory opened correctly + */ + virtual bool Open() = 0; + + /** * List files contained in the directory * @param count Number of entries to return at once in entries * @param entries Buffer to read data into diff --git a/src/core/file_sys/directory_romfs.cpp b/src/core/file_sys/directory_romfs.cpp index 4e8f4c04d..e6d571391 100644 --- a/src/core/file_sys/directory_romfs.cpp +++ b/src/core/file_sys/directory_romfs.cpp @@ -17,6 +17,10 @@ Directory_RomFS::Directory_RomFS() { Directory_RomFS::~Directory_RomFS() { } +bool Directory_RomFS::Open() { + return false; +} + /** * List files contained in the directory * @param count Number of entries to return at once in entries diff --git a/src/core/file_sys/directory_romfs.h b/src/core/file_sys/directory_romfs.h index 4b71c4b13..e2944099e 100644 --- a/src/core/file_sys/directory_romfs.h +++ b/src/core/file_sys/directory_romfs.h @@ -20,6 +20,12 @@ public: ~Directory_RomFS() override; /** + * Open the directory + * @return true if the directory opened correctly + */ + bool Open() override; + + /** * List files contained in the directory * @param count Number of entries to return at once in entries * @param entries Buffer to read data into diff --git a/src/core/file_sys/directory_sdmc.cpp b/src/core/file_sys/directory_sdmc.cpp index 60a197ce9..0f156a127 100644 --- a/src/core/file_sys/directory_sdmc.cpp +++ b/src/core/file_sys/directory_sdmc.cpp @@ -19,15 +19,22 @@ Directory_SDMC::Directory_SDMC(const Archive_SDMC* archive, const Path& path) { // TODO(Link Mauve): normalize path into an absolute path without "..", it can currently bypass // the root directory we set while opening the archive. // For example, opening /../../usr/bin can give the emulated program your installed programs. - std::string absolute_path = archive->GetMountPoint() + path.AsString(); - FileUtil::ScanDirectoryTree(absolute_path, directory); - children_iterator = directory.children.begin(); + this->path = archive->GetMountPoint() + path.AsString(); + } Directory_SDMC::~Directory_SDMC() { Close(); } +bool Directory_SDMC::Open() { + if (!FileUtil::IsDirectory(path)) + return false; + FileUtil::ScanDirectoryTree(path, directory); + children_iterator = directory.children.begin(); + return true; +} + /** * List files contained in the directory * @param count Number of entries to return at once in entries diff --git a/src/core/file_sys/directory_sdmc.h b/src/core/file_sys/directory_sdmc.h index 4520d0401..4c08b0d61 100644 --- a/src/core/file_sys/directory_sdmc.h +++ b/src/core/file_sys/directory_sdmc.h @@ -23,6 +23,12 @@ public: ~Directory_SDMC() override; /** + * Open the directory + * @return true if the directory opened correctly + */ + bool Open() override; + + /** * List files contained in the directory * @param count Number of entries to return at once in entries * @param entries Buffer to read data into @@ -37,6 +43,7 @@ public: bool Close() const override; private: + std::string path; u32 total_entries_in_directory; FileUtil::FSTEntry directory; diff --git a/src/core/file_sys/file_sdmc.cpp b/src/core/file_sys/file_sdmc.cpp index a4b90670a..b01d96e3d 100644 --- a/src/core/file_sys/file_sdmc.cpp +++ b/src/core/file_sys/file_sdmc.cpp @@ -38,12 +38,15 @@ bool File_SDMC::Open() { } std::string mode_string; - if (mode.read_flag && mode.write_flag) + if (mode.create_flag) mode_string = "w+"; + else if (mode.write_flag) + mode_string = "r+"; // Files opened with Write access can be read from else if (mode.read_flag) mode_string = "r"; - else if (mode.write_flag) - mode_string = "w"; + + // Open the file in binary mode, to avoid problems with CR/LF on Windows systems + mode_string += "b"; file = new FileUtil::IOFile(path, mode_string.c_str()); return true; diff --git a/src/core/hle/kernel/address_arbiter.cpp b/src/core/hle/kernel/address_arbiter.cpp index db571b895..ce4f3c854 100644 --- a/src/core/hle/kernel/address_arbiter.cpp +++ b/src/core/hle/kernel/address_arbiter.cpp @@ -53,7 +53,7 @@ ResultCode ArbitrateAddress(Handle handle, ArbitrationType type, u32 address, s3 // Wait current thread (acquire the arbiter)... case ArbitrationType::WaitIfLessThan: if ((s32)Memory::Read32(address) <= value) { - Kernel::WaitCurrentThread(WAITTYPE_ARB, handle); + Kernel::WaitCurrentThread(WAITTYPE_ARB, handle, address); HLE::Reschedule(__func__); } break; diff --git a/src/core/hle/kernel/archive.cpp b/src/core/hle/kernel/archive.cpp index 647f0dea9..a875fa7ff 100644 --- a/src/core/hle/kernel/archive.cpp +++ b/src/core/hle/kernel/archive.cpp @@ -421,6 +421,11 @@ ResultVal<Handle> OpenDirectoryFromArchive(Handle archive_handle, const FileSys: directory->path = path; directory->backend = archive->backend->OpenDirectory(path); + if (!directory->backend) { + return ResultCode(ErrorDescription::NotFound, ErrorModule::FS, + ErrorSummary::NotFound, ErrorLevel::Permanent); + } + return MakeResult<Handle>(handle); } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index c01d76e4d..492b917e1 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -64,6 +64,7 @@ public: WaitType wait_type; Handle wait_handle; + VAddr wait_address; std::vector<Handle> waiting_threads; @@ -127,6 +128,7 @@ void ResetThread(Thread* t, u32 arg, s32 lowest_priority) { } t->wait_type = WAITTYPE_NONE; t->wait_handle = 0; + t->wait_address = 0; } /// Change a thread to "ready" state @@ -147,11 +149,17 @@ void ChangeReadyState(Thread* t, bool ready) { } /// Verify that a thread has not been released from waiting -inline bool VerifyWait(const Thread* thread, WaitType type, Handle wait_handle) { +static bool VerifyWait(const Thread* thread, WaitType type, Handle wait_handle) { _dbg_assert_(KERNEL, thread != nullptr); return (type == thread->wait_type) && (wait_handle == thread->wait_handle) && (thread->IsWaiting()); } +/// Verify that a thread has not been released from waiting (with wait address) +static bool VerifyWait(const Thread* thread, WaitType type, Handle wait_handle, VAddr wait_address) { + _dbg_assert_(KERNEL, thread != nullptr); + return VerifyWait(thread, type, wait_handle) && (wait_address == thread->wait_address); +} + /// Stops the current thread ResultCode StopThread(Handle handle, const char* reason) { Thread* thread = g_object_pool.Get<Thread>(handle); @@ -173,6 +181,7 @@ ResultCode StopThread(Handle handle, const char* reason) { // Stopped threads are never waiting. thread->wait_type = WAITTYPE_NONE; thread->wait_handle = 0; + thread->wait_address = 0; return RESULT_SUCCESS; } @@ -201,12 +210,12 @@ Handle ArbitrateHighestPriorityThread(u32 arbiter, u32 address) { for (Handle handle : thread_queue) { Thread* thread = g_object_pool.Get<Thread>(handle); - // TODO(bunnei): Verify arbiter address... - if (!VerifyWait(thread, WAITTYPE_ARB, arbiter)) + if (!VerifyWait(thread, WAITTYPE_ARB, arbiter, address)) continue; if (thread == nullptr) continue; // TODO(yuriks): Thread handle will hang around forever. Should clean up. + if(thread->current_priority <= priority) { highest_priority_thread = handle; priority = thread->current_priority; @@ -226,8 +235,7 @@ void ArbitrateAllThreads(u32 arbiter, u32 address) { for (Handle handle : thread_queue) { Thread* thread = g_object_pool.Get<Thread>(handle); - // TODO(bunnei): Verify arbiter address... - if (VerifyWait(thread, WAITTYPE_ARB, arbiter)) + if (VerifyWait(thread, WAITTYPE_ARB, arbiter, address)) ResumeThreadFromWait(handle); } } @@ -281,11 +289,6 @@ Thread* NextThread() { return Kernel::g_object_pool.Get<Thread>(next); } -/** - * Puts the current thread in the wait state for the given type - * @param wait_type Type of wait - * @param wait_handle Handle of Kernel object that we are waiting on, defaults to current thread - */ void WaitCurrentThread(WaitType wait_type, Handle wait_handle) { Thread* thread = GetCurrentThread(); thread->wait_type = wait_type; @@ -293,6 +296,11 @@ void WaitCurrentThread(WaitType wait_type, Handle wait_handle) { ChangeThreadState(thread, ThreadStatus(THREADSTATUS_WAIT | (thread->status & THREADSTATUS_SUSPEND))); } +void WaitCurrentThread(WaitType wait_type, Handle wait_handle, VAddr wait_address) { + WaitCurrentThread(wait_type, wait_handle); + GetCurrentThread()->wait_address = wait_address; +} + /// Resumes a thread from waiting by marking it as "ready" void ResumeThreadFromWait(Handle handle) { Thread* thread = Kernel::g_object_pool.Get<Thread>(handle); @@ -343,6 +351,7 @@ Thread* CreateThread(Handle& handle, const char* name, u32 entry_point, s32 prio thread->processor_id = processor_id; thread->wait_type = WAITTYPE_NONE; thread->wait_handle = 0; + thread->wait_address = 0; thread->name = name; return thread; diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 53a19d779..be7adface 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -5,6 +5,9 @@ #pragma once #include "common/common_types.h" + +#include "core/mem_map.h" + #include "core/hle/kernel/kernel.h" #include "core/hle/result.h" @@ -85,6 +88,14 @@ Handle GetCurrentThreadHandle(); */ void WaitCurrentThread(WaitType wait_type, Handle wait_handle=GetCurrentThreadHandle()); +/** + * Puts the current thread in the wait state for the given type + * @param wait_type Type of wait + * @param wait_handle Handle of Kernel object that we are waiting on, defaults to current thread + * @param wait_address Arbitration address used to resume from wait + */ +void WaitCurrentThread(WaitType wait_type, Handle wait_handle, VAddr wait_address); + /// Put current thread in a wait state - on WaitSynchronization void WaitThread_Synchronization(); diff --git a/src/core/hle/service/cfg_u.cpp b/src/core/hle/service/cfg_u.cpp index d6b586ea0..82bab5797 100644 --- a/src/core/hle/service/cfg_u.cpp +++ b/src/core/hle/service/cfg_u.cpp @@ -11,33 +11,38 @@ namespace CFG_U { -static const std::array<const char*, 187> country_codes = { - nullptr, "JP", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, // 0-7 - "AI", "AG", "AR", "AW", "BS", "BB", "BZ", "BO", // 8-15 - "BR", "VG", "CA", "KY", "CL", "CO", "CR", "DM", // 16-23 - "DO", "EC", "SV", "GF", "GD", "GP", "GT", "GY", // 24-31 - "HT", "HN", "JM", "MQ", "MX", "MS", "AN", "NI", // 32-39 - "PA", "PY", "PE", "KN", "LC", "VC", "SR", "TT", // 40-47 - "TC", "US", "UY", "VI", "VE", nullptr, nullptr, nullptr, // 48-55 - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, // 56-63 - "AL", "AU", "AT", "BE", "BA", "BW", "BG", "HR", // 64-71 - "CY", "CZ", "DK", "EE", "FI", "FR", "DE", "GR", // 72-79 - "HU", "IS", "IE", "IT", "LV", "LS", "LI", "LT", // 80-87 - "LU", "MK", "MT", "ME", "MZ", "NA", "NL", "NZ", // 88-95 - "NO", "PL", "PT", "RO", "RU", "RS", "SK", "SI", // 96-103 - "ZA", "ES", "SZ", "SE", "CH", "TR", "GB", "ZM", // 104-111 - "ZW", "AZ", "MR", "ML", "NE", "TD", "SD", "ER", // 112-119 - "DJ", "SO", "AD", "GI", "GG", "IM", "JE", "MC", // 120-127 - "TW", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, // 128-135 - "KR", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, // 136-143 - "HK", "MO", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, // 144-151 - "ID", "SG", "TH", "PH", "MY", nullptr, nullptr, nullptr, // 152-159 - "CN", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, // 160-167 - "AE", "IN", "EG", "OM", "QA", "KW", "SA", "SY", // 168-175 - "BH", "JO", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, // 176-183 - "SM", "VA", "BM", // 184-186 +// TODO(Link Mauve): use a constexpr once MSVC starts supporting it. +#define C(code) ((code)[0] | ((code)[1] << 8)) + +static const std::array<u16, 187> country_codes = { + 0, C("JP"), 0, 0, 0, 0, 0, 0, // 0-7 + C("AI"), C("AG"), C("AR"), C("AW"), C("BS"), C("BB"), C("BZ"), C("BO"), // 8-15 + C("BR"), C("VG"), C("CA"), C("KY"), C("CL"), C("CO"), C("CR"), C("DM"), // 16-23 + C("DO"), C("EC"), C("SV"), C("GF"), C("GD"), C("GP"), C("GT"), C("GY"), // 24-31 + C("HT"), C("HN"), C("JM"), C("MQ"), C("MX"), C("MS"), C("AN"), C("NI"), // 32-39 + C("PA"), C("PY"), C("PE"), C("KN"), C("LC"), C("VC"), C("SR"), C("TT"), // 40-47 + C("TC"), C("US"), C("UY"), C("VI"), C("VE"), 0, 0, 0, // 48-55 + 0, 0, 0, 0, 0, 0, 0, 0, // 56-63 + C("AL"), C("AU"), C("AT"), C("BE"), C("BA"), C("BW"), C("BG"), C("HR"), // 64-71 + C("CY"), C("CZ"), C("DK"), C("EE"), C("FI"), C("FR"), C("DE"), C("GR"), // 72-79 + C("HU"), C("IS"), C("IE"), C("IT"), C("LV"), C("LS"), C("LI"), C("LT"), // 80-87 + C("LU"), C("MK"), C("MT"), C("ME"), C("MZ"), C("NA"), C("NL"), C("NZ"), // 88-95 + C("NO"), C("PL"), C("PT"), C("RO"), C("RU"), C("RS"), C("SK"), C("SI"), // 96-103 + C("ZA"), C("ES"), C("SZ"), C("SE"), C("CH"), C("TR"), C("GB"), C("ZM"), // 104-111 + C("ZW"), C("AZ"), C("MR"), C("ML"), C("NE"), C("TD"), C("SD"), C("ER"), // 112-119 + C("DJ"), C("SO"), C("AD"), C("GI"), C("GG"), C("IM"), C("JE"), C("MC"), // 120-127 + C("TW"), 0, 0, 0, 0, 0, 0, 0, // 128-135 + C("KR"), 0, 0, 0, 0, 0, 0, 0, // 136-143 + C("HK"), C("MO"), 0, 0, 0, 0, 0, 0, // 144-151 + C("ID"), C("SG"), C("TH"), C("PH"), C("MY"), 0, 0, 0, // 152-159 + C("CN"), 0, 0, 0, 0, 0, 0, 0, // 160-167 + C("AE"), C("IN"), C("EG"), C("OM"), C("QA"), C("KW"), C("SA"), C("SY"), // 168-175 + C("BH"), C("JO"), 0, 0, 0, 0, 0, 0, // 176-183 + C("SM"), C("VA"), C("BM") // 184-186 }; +#undef C + /** * CFG_User::GetCountryCodeString service function * Inputs: @@ -50,20 +55,14 @@ static void GetCountryCodeString(Service::Interface* self) { u32* cmd_buffer = Service::GetCommandBuffer(); u32 country_code_id = cmd_buffer[1]; - if (country_code_id >= country_codes.size()) { + if (country_code_id >= country_codes.size() || 0 == country_codes[country_code_id]) { ERROR_LOG(KERNEL, "requested country code id=%d is invalid", country_code_id); cmd_buffer[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent).raw; return; } - - const char* code = country_codes[country_code_id]; - if (code != nullptr) { - cmd_buffer[1] = 0; - cmd_buffer[2] = code[0] | (code[1] << 8); - } else { - cmd_buffer[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent).raw; - DEBUG_LOG(KERNEL, "requested country code id=%d is not set", country_code_id); - } + + cmd_buffer[1] = 0; + cmd_buffer[2] = country_codes[country_code_id]; } /** @@ -77,20 +76,25 @@ static void GetCountryCodeString(Service::Interface* self) { static void GetCountryCodeID(Service::Interface* self) { u32* cmd_buffer = Service::GetCommandBuffer(); u16 country_code = cmd_buffer[1]; - u16 country_code_id = -1; + u16 country_code_id = 0; - for (u32 i = 0; i < country_codes.size(); ++i) { - const char* code_string = country_codes[i]; + // The following algorithm will fail if the first country code isn't 0. + _dbg_assert_(HLE, country_codes[0] == 0); - if (code_string != nullptr) { - u16 code = code_string[0] | (code_string[1] << 8); - if (code == country_code) { - country_code_id = i; - break; - } + for (size_t id = 0; id < country_codes.size(); ++id) { + if (country_codes[id] == country_code) { + country_code_id = id; + break; } } + if (0 == country_code_id) { + ERROR_LOG(KERNEL, "requested country code name=%c%c is invalid", country_code & 0xff, country_code >> 8); + cmd_buffer[1] = ResultCode(ErrorDescription::NotFound, ErrorModule::Config, ErrorSummary::WrongArgument, ErrorLevel::Permanent).raw; + cmd_buffer[2] = 0xFFFF; + return; + } + cmd_buffer[1] = 0; cmd_buffer[2] = country_code_id; } diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp index de1bd3f61..34eabac45 100644 --- a/src/core/hle/service/gsp_gpu.cpp +++ b/src/core/hle/service/gsp_gpu.cpp @@ -162,7 +162,8 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) { _assert_msg_(GSP, (g_interrupt_event != 0), "handle is not valid!"); - cmd_buff[2] = g_thread_id++; // ThreadID + cmd_buff[1] = 0x2A07; // Value verified by 3dmoo team, purpose unknown, but needed for GSP init + cmd_buff[2] = g_thread_id++; // Thread ID cmd_buff[4] = g_shared_memory; // GSP shared memory Kernel::SignalEvent(g_interrupt_event); // TODO(bunnei): Is this correct? @@ -172,6 +173,7 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) { * Signals that the specified interrupt type has occurred to userland code * @param interrupt_id ID of interrupt that is being signalled * @todo This should probably take a thread_id parameter and only signal this thread? + * @todo This probably does not belong in the GSP module, instead move to video_core */ void SignalInterrupt(InterruptId interrupt_id) { if (0 == g_interrupt_event) { @@ -210,6 +212,7 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { memcpy(Memory::GetPointer(command.dma_request.dest_address), Memory::GetPointer(command.dma_request.source_address), command.dma_request.size); + SignalInterrupt(InterruptId::DMA); break; // ctrulib homebrew sends all relevant command list data with this command, @@ -218,13 +221,13 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { case CommandId::SET_COMMAND_LIST_LAST: { auto& params = command.set_command_list_last; + WriteGPURegister(GPU_REG_INDEX(command_processor_config.address), Memory::VirtualToPhysicalAddress(params.address) >> 3); - WriteGPURegister(GPU_REG_INDEX(command_processor_config.size), params.size >> 3); + WriteGPURegister(GPU_REG_INDEX(command_processor_config.size), params.size); // TODO: Not sure if we are supposed to always write this .. seems to trigger processing though WriteGPURegister(GPU_REG_INDEX(command_processor_config.trigger), 1); - SignalInterrupt(InterruptId::P3D); break; } @@ -242,6 +245,8 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { WriteGPURegister(GPU_REG_INDEX(memory_fill_config[1].address_end), Memory::VirtualToPhysicalAddress(params.end2) >> 3); WriteGPURegister(GPU_REG_INDEX(memory_fill_config[1].size), params.end2 - params.start2); WriteGPURegister(GPU_REG_INDEX(memory_fill_config[1].value), params.value2); + + SignalInterrupt(InterruptId::PSC0); break; } @@ -255,14 +260,9 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { WriteGPURegister(GPU_REG_INDEX(display_transfer_config.flags), params.flags); WriteGPURegister(GPU_REG_INDEX(display_transfer_config.trigger), 1); - // TODO(bunnei): Signalling all of these interrupts here is totally wrong, but it seems to - // work well enough for running demos. Need to figure out how these all work and trigger - // them correctly. - SignalInterrupt(InterruptId::PSC0); + // TODO(bunnei): Determine if these interrupts should be signalled here. SignalInterrupt(InterruptId::PSC1); SignalInterrupt(InterruptId::PPF); - SignalInterrupt(InterruptId::P3D); - SignalInterrupt(InterruptId::DMA); // Update framebuffer information if requested for (int screen_id = 0; screen_id < 2; ++screen_id) { @@ -305,6 +305,8 @@ static void ExecuteCommand(const Command& command, u32 thread_id) { /// This triggers handling of the GX command written to the command buffer in shared memory. static void TriggerCmdReqQueue(Service::Interface* self) { + DEBUG_LOG(GSP, "called"); + // Iterate through each thread's command queue... for (unsigned thread_id = 0; thread_id < 0x4; ++thread_id) { CommandBuffer* command_buffer = (CommandBuffer*)GetCommandBuffer(thread_id); @@ -320,6 +322,9 @@ static void TriggerCmdReqQueue(Service::Interface* self) { command_buffer->number_commands = command_buffer->number_commands - 1; } } + + u32* cmd_buff = Service::GetCommandBuffer(); + cmd_buff[1] = 0; // No error } const Interface::FunctionInfo FunctionTable[] = { diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 20e7fb4d3..3a7d6c469 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h @@ -10,6 +10,7 @@ #include <string> #include "common/common.h" +#include "common/string_util.h" #include "core/mem_map.h" #include "core/hle/kernel/kernel.h" @@ -79,21 +80,20 @@ public: u32* cmd_buff = GetCommandBuffer(); auto itr = m_functions.find(cmd_buff[0]); - if (itr == m_functions.end()) { - ERROR_LOG(OSHLE, "unknown/unimplemented function: port=%s, command=0x%08X", - GetPortName().c_str(), cmd_buff[0]); + if (itr == m_functions.end() || itr->second.func == nullptr) { + // Number of params == bits 0-5 + bits 6-11 + int num_params = (cmd_buff[0] & 0x3F) + ((cmd_buff[0] >> 6) & 0x3F); - // TODO(bunnei): Hack - ignore error - u32* cmd_buff = Service::GetCommandBuffer(); - cmd_buff[1] = 0; - return MakeResult<bool>(false); - } - if (itr->second.func == nullptr) { - ERROR_LOG(OSHLE, "unimplemented function: port=%s, name=%s", - GetPortName().c_str(), itr->second.name.c_str()); + std::string error = "unknown/unimplemented function '%s': port=%s"; + for (int i = 1; i <= num_params; ++i) { + error += Common::StringFromFormat(", cmd_buff[%i]=%u", i, cmd_buff[i]); + } + + std::string name = (itr == m_functions.end()) ? Common::StringFromFormat("0x%08X", cmd_buff[0]) : itr->second.name; + + ERROR_LOG(OSHLE, error.c_str(), name.c_str(), GetPortName().c_str()); // TODO(bunnei): Hack - ignore error - u32* cmd_buff = Service::GetCommandBuffer(); cmd_buff[1] = 0; return MakeResult<bool>(false); } diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index af5e1b39b..77557e582 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp @@ -154,8 +154,7 @@ inline void Write(u32 addr, const T data) { if (config.trigger & 1) { u32* buffer = (u32*)Memory::GetPointer(Memory::PhysicalToVirtualAddress(config.GetPhysicalAddress())); - u32 size = config.size << 3; - Pica::CommandProcessor::ProcessCommandList(buffer, size); + Pica::CommandProcessor::ProcessCommandList(buffer, config.size); } break; } diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h index 3fa7b9ccf..86cd5e680 100644 --- a/src/core/hw/gpu.h +++ b/src/core/hw/gpu.h @@ -169,7 +169,7 @@ struct Regs { INSERT_PADDING_WORDS(0x331); struct { - // command list size + // command list size (in bytes) u32 size; INSERT_PADDING_WORDS(0x1); diff --git a/src/core/hw/hw.cpp b/src/core/hw/hw.cpp index ea001673a..73a4f1e53 100644 --- a/src/core/hw/hw.cpp +++ b/src/core/hw/hw.cpp @@ -6,7 +6,6 @@ #include "core/hw/hw.h" #include "core/hw/gpu.h" -#include "core/hw/ndma.h" namespace HW { @@ -40,11 +39,6 @@ template <typename T> inline void Read(T &var, const u32 addr) { switch (addr & 0xFFFFF000) { - // TODO(bunnei): What is the virtual address of NDMA? - // case VADDR_NDMA: - // NDMA::Read(var, addr); - // break; - case VADDR_GPU: GPU::Read(var, addr); break; @@ -58,11 +52,6 @@ template <typename T> inline void Write(u32 addr, const T data) { switch (addr & 0xFFFFF000) { - // TODO(bunnei): What is the virtual address of NDMA? - // case VADDR_NDMA - // NDMA::Write(addr, data); - // break; - case VADDR_GPU: GPU::Write(addr, data); break; @@ -87,13 +76,11 @@ template void Write<u8>(u32 addr, const u8 data); /// Update hardware void Update() { GPU::Update(); - NDMA::Update(); } /// Initialize hardware void Init() { GPU::Init(); - NDMA::Init(); NOTICE_LOG(HW, "initialized OK"); } diff --git a/src/core/hw/ndma.cpp b/src/core/hw/ndma.cpp deleted file mode 100644 index 593e5de30..000000000 --- a/src/core/hw/ndma.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#include "common/common_types.h" - -#include "core/hw/ndma.h" - -namespace NDMA { - -template <typename T> -inline void Read(T &var, const u32 addr) { - ERROR_LOG(NDMA, "unknown Read%lu @ 0x%08X", sizeof(var) * 8, addr); -} - -template <typename T> -inline void Write(u32 addr, const T data) { - ERROR_LOG(NDMA, "unknown Write%lu 0x%08X @ 0x%08X", sizeof(data) * 8, (u32)data, addr); -} - -// Explicitly instantiate template functions because we aren't defining this in the header: - -template void Read<u64>(u64 &var, const u32 addr); -template void Read<u32>(u32 &var, const u32 addr); -template void Read<u16>(u16 &var, const u32 addr); -template void Read<u8>(u8 &var, const u32 addr); - -template void Write<u64>(u32 addr, const u64 data); -template void Write<u32>(u32 addr, const u32 data); -template void Write<u16>(u32 addr, const u16 data); -template void Write<u8>(u32 addr, const u8 data); - -/// Update hardware -void Update() { -} - -/// Initialize hardware -void Init() { - NOTICE_LOG(GPU, "initialized OK"); -} - -/// Shutdown hardware -void Shutdown() { - NOTICE_LOG(GPU, "shutdown OK"); -} - -} // namespace diff --git a/src/core/hw/ndma.h b/src/core/hw/ndma.h deleted file mode 100644 index d8fa3d40b..000000000 --- a/src/core/hw/ndma.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2014 Citra Emulator Project -// Licensed under GPLv2 -// Refer to the license.txt file included. - -#pragma once - -#include "common/common_types.h" - -namespace NDMA { - -template <typename T> -inline void Read(T &var, const u32 addr); - -template <typename T> -inline void Write(u32 addr, const T data); - -/// Update hardware -void Update(); - -/// Initialize hardware -void Init(); - -/// Shutdown hardware -void Shutdown(); - -} // namespace diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp new file mode 100644 index 000000000..7ef146359 --- /dev/null +++ b/src/core/loader/3dsx.cpp @@ -0,0 +1,236 @@ +// Copyright 2014 Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <algorithm> +#include <vector> + +#include "core/file_sys/archive_romfs.h" +#include "core/loader/elf.h" +#include "core/loader/ncch.h" +#include "core/hle/kernel/archive.h" +#include "core/mem_map.h" + +#include "3dsx.h" + + +namespace Loader { + + +/** + * File layout: + * - File header + * - Code, rodata and data relocation table headers + * - Code segment + * - Rodata segment + * - Loadable (non-BSS) part of the data segment + * - Code relocation table + * - Rodata relocation table + * - Data relocation table + * + * Memory layout before relocations are applied: + * [0..codeSegSize) -> code segment + * [codeSegSize..rodataSegSize) -> rodata segment + * [rodataSegSize..dataSegSize) -> data segment + * + * Memory layout after relocations are applied: well, however the loader sets it up :) + * The entrypoint is always the start of the code segment. + * The BSS section must be cleared manually by the application. + */ +enum THREEDSX_Error { + ERROR_NONE = 0, + ERROR_READ = 1, + ERROR_FILE = 2, + ERROR_ALLOC = 3 +}; +static const u32 RELOCBUFSIZE = 512; + +// File header +static const u32 THREEDSX_MAGIC = 0x58534433; // '3DSX' +#pragma pack(1) +struct THREEDSX_Header +{ + u32 magic; + u16 header_size, reloc_hdr_size; + u32 format_ver; + u32 flags; + + // Sizes of the code, rodata and data segments + + // size of the BSS section (uninitialized latter half of the data segment) + u32 code_seg_size, rodata_seg_size, data_seg_size, bss_size; +}; + +// Relocation header: all fields (even extra unknown fields) are guaranteed to be relocation counts. +struct THREEDSX_RelocHdr +{ + // # of absolute relocations (that is, fix address to post-relocation memory layout) + u32 cross_segment_absolute; + // # of cross-segment relative relocations (that is, 32bit signed offsets that need to be patched) + u32 cross_segment_relative; + // more? + + // Relocations are written in this order: + // - Absolute relocations + // - Relative relocations +}; + +// Relocation entry: from the current pointer, skip X words and patch Y words +struct THREEDSX_Reloc +{ + u16 skip, patch; +}; +#pragma pack() + +struct THREEloadinfo +{ + u8* seg_ptrs[3]; // code, rodata & data + u32 seg_addrs[3]; + u32 seg_sizes[3]; +}; + +class THREEDSXReader { +public: + static int Load3DSXFile(const std::string& filename, u32 base_addr); +}; + +static u32 TranslateAddr(u32 addr, THREEloadinfo *loadinfo, u32* offsets) +{ + if (addr < offsets[0]) + return loadinfo->seg_addrs[0] + addr; + if (addr < offsets[1]) + return loadinfo->seg_addrs[1] + addr - offsets[0]; + return loadinfo->seg_addrs[2] + addr - offsets[1]; +} + +int THREEDSXReader::Load3DSXFile(const std::string& filename, u32 base_addr) +{ + FileUtil::IOFile file(filename, "rb"); + if (!file.IsOpen()) { + return ERROR_FILE; + } + THREEDSX_Header hdr; + if (file.ReadBytes(&hdr, sizeof(hdr)) != sizeof(hdr)) + return ERROR_READ; + + THREEloadinfo loadinfo; + //loadinfo segments must be a multiple of 0x1000 + loadinfo.seg_sizes[0] = (hdr.code_seg_size + 0xFFF) &~0xFFF; + loadinfo.seg_sizes[1] = (hdr.rodata_seg_size + 0xFFF) &~0xFFF; + loadinfo.seg_sizes[2] = (hdr.data_seg_size + 0xFFF) &~0xFFF; + u32 offsets[2] = { loadinfo.seg_sizes[0], loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1] }; + u32 data_load_size = (hdr.data_seg_size - hdr.bss_size + 0xFFF) &~0xFFF; + u32 bss_load_size = loadinfo.seg_sizes[2] - data_load_size; + u32 n_reloc_tables = hdr.reloc_hdr_size / 4; + std::vector<u8> all_mem(loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1] + loadinfo.seg_sizes[2] + 3 * n_reloc_tables); + + loadinfo.seg_addrs[0] = base_addr; + loadinfo.seg_addrs[1] = loadinfo.seg_addrs[0] + loadinfo.seg_sizes[0]; + loadinfo.seg_addrs[2] = loadinfo.seg_addrs[1] + loadinfo.seg_sizes[1]; + loadinfo.seg_ptrs[0] = &all_mem[0]; + loadinfo.seg_ptrs[1] = loadinfo.seg_ptrs[0] + loadinfo.seg_sizes[0]; + loadinfo.seg_ptrs[2] = loadinfo.seg_ptrs[1] + loadinfo.seg_sizes[1]; + + // Skip header for future compatibility + file.Seek(hdr.header_size, SEEK_SET); + + // Read the relocation headers + u32* relocs = (u32*)(loadinfo.seg_ptrs[2] + hdr.data_seg_size); + + for (u32 current_segment = 0; current_segment < 3; current_segment++) { + if (file.ReadBytes(&relocs[current_segment*n_reloc_tables], n_reloc_tables * 4) != n_reloc_tables * 4) + return ERROR_READ; + } + + // Read the segments + if (file.ReadBytes(loadinfo.seg_ptrs[0], hdr.code_seg_size) != hdr.code_seg_size) + return ERROR_READ; + if (file.ReadBytes(loadinfo.seg_ptrs[1], hdr.rodata_seg_size) != hdr.rodata_seg_size) + return ERROR_READ; + if (file.ReadBytes(loadinfo.seg_ptrs[2], hdr.data_seg_size - hdr.bss_size) != hdr.data_seg_size - hdr.bss_size) + return ERROR_READ; + + // BSS clear + memset((char*)loadinfo.seg_ptrs[2] + hdr.data_seg_size - hdr.bss_size, 0, hdr.bss_size); + + // Relocate the segments + for (u32 current_segment = 0; current_segment < 3; current_segment++) { + for (u32 current_segment_reloc_table = 0; current_segment_reloc_table < n_reloc_tables; current_segment_reloc_table++) { + u32 n_relocs = relocs[current_segment*n_reloc_tables + current_segment_reloc_table]; + if (current_segment_reloc_table >= 2) { + // We are not using this table - ignore it because we don't know what it dose + file.Seek(n_relocs*sizeof(THREEDSX_Reloc), SEEK_CUR); + continue; + } + static THREEDSX_Reloc reloc_table[RELOCBUFSIZE]; + + u32* pos = (u32*)loadinfo.seg_ptrs[current_segment]; + u32* end_pos = pos + (loadinfo.seg_sizes[current_segment] / 4); + + while (n_relocs) { + u32 remaining = std::min(RELOCBUFSIZE, n_relocs); + n_relocs -= remaining; + + if (file.ReadBytes(reloc_table, remaining*sizeof(THREEDSX_Reloc)) != remaining*sizeof(THREEDSX_Reloc)) + return ERROR_READ; + + for (u32 current_inprogress = 0; current_inprogress < remaining && pos < end_pos; current_inprogress++) { + DEBUG_LOG(LOADER, "(t=%d,skip=%u,patch=%u)\n", + current_segment_reloc_table, (u32)reloc_table[current_inprogress].skip, (u32)reloc_table[current_inprogress].patch); + pos += reloc_table[current_inprogress].skip; + s32 num_patches = reloc_table[current_inprogress].patch; + while (0 < num_patches && pos < end_pos) { + u32 in_addr = (char*)pos - (char*)&all_mem[0]; + u32 addr = TranslateAddr(*pos, &loadinfo, offsets); + DEBUG_LOG(LOADER, "Patching %08X <-- rel(%08X,%d) (%08X)\n", + base_addr + in_addr, addr, current_segment_reloc_table, *pos); + switch (current_segment_reloc_table) { + case 0: *pos = (addr); break; + case 1: *pos = (addr - in_addr); break; + default: break; //this should never happen + } + pos++; + num_patches--; + } + } + } + } + } + + // Write the data + memcpy(Memory::GetPointer(base_addr), &all_mem[0], loadinfo.seg_sizes[0] + loadinfo.seg_sizes[1] + loadinfo.seg_sizes[2]); + + DEBUG_LOG(LOADER, "CODE: %u pages\n", loadinfo.seg_sizes[0] / 0x1000); + DEBUG_LOG(LOADER, "RODATA: %u pages\n", loadinfo.seg_sizes[1] / 0x1000); + DEBUG_LOG(LOADER, "DATA: %u pages\n", data_load_size / 0x1000); + DEBUG_LOG(LOADER, "BSS: %u pages\n", bss_load_size / 0x1000); + + return ERROR_NONE; +} + + /// AppLoader_DSX constructor + AppLoader_THREEDSX::AppLoader_THREEDSX(const std::string& filename) : filename(filename) { + } + + /// AppLoader_DSX destructor + AppLoader_THREEDSX::~AppLoader_THREEDSX() { + } + + /** + * Loads a 3DSX file + * @return Success on success, otherwise Error + */ + ResultStatus AppLoader_THREEDSX::Load() { + INFO_LOG(LOADER, "Loading 3DSX file %s...", filename.c_str()); + FileUtil::IOFile file(filename, "rb"); + if (file.IsOpen()) { + + THREEDSXReader reader; + reader.Load3DSXFile(filename, 0x00100000); + Kernel::LoadExec(0x00100000); + } else { + return ResultStatus::Error; + } + return ResultStatus::Success; + } + +} // namespace Loader diff --git a/src/core/loader/3dsx.h b/src/core/loader/3dsx.h new file mode 100644 index 000000000..848d3ef8a --- /dev/null +++ b/src/core/loader/3dsx.h @@ -0,0 +1,32 @@ +// Copyright 2014 Dolphin Emulator Project / Citra Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_types.h" +#include "core/loader/loader.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Loader namespace + +namespace Loader { + +/// Loads an 3DSX file +class AppLoader_THREEDSX final : public AppLoader { +public: + AppLoader_THREEDSX(const std::string& filename); + ~AppLoader_THREEDSX() override; + + /** + * Load the bootable file + * @return ResultStatus result of function + */ + ResultStatus Load() override; + +private: + std::string filename; + bool is_loaded; +}; + +} // namespace Loader diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index a268e021a..174397b05 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp @@ -5,6 +5,7 @@ #include <memory> #include "core/file_sys/archive_romfs.h" +#include "core/loader/3dsx.h" #include "core/loader/elf.h" #include "core/loader/ncch.h" #include "core/hle/kernel/archive.h" @@ -42,6 +43,8 @@ FileType IdentifyFile(const std::string &filename) { return FileType::CCI; } else if (extension == ".bin") { return FileType::BIN; + } else if (extension == ".3dsx") { + return FileType::THREEDSX; } return FileType::Unknown; } @@ -56,6 +59,10 @@ ResultStatus LoadFile(const std::string& filename) { switch (IdentifyFile(filename)) { + //3DSX file format... + case FileType::THREEDSX: + return AppLoader_THREEDSX(filename).Load(); + // Standard ELF file format... case FileType::ELF: return AppLoader_ELF(filename).Load(); diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 68f843005..0f836d285 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -22,6 +22,7 @@ enum class FileType { CIA, ELF, BIN, + THREEDSX, //3DSX }; /// Return type for functions in Loader namespace diff --git a/src/core/mem_map.h b/src/core/mem_map.h index c9529f84c..f17afb60d 100644 --- a/src/core/mem_map.h +++ b/src/core/mem_map.h @@ -26,12 +26,10 @@ enum : u32 { FCRAM_PADDR_END = (FCRAM_PADDR + FCRAM_SIZE), ///< FCRAM end of physical space FCRAM_VADDR = 0x08000000, ///< FCRAM virtual address FCRAM_VADDR_END = (FCRAM_VADDR + FCRAM_SIZE), ///< FCRAM end of virtual space - FCRAM_MASK = (FCRAM_SIZE - 1), ///< FCRAM mask SHARED_MEMORY_SIZE = 0x04000000, ///< Shared memory size SHARED_MEMORY_VADDR = 0x10000000, ///< Shared memory SHARED_MEMORY_VADDR_END = (SHARED_MEMORY_VADDR + SHARED_MEMORY_SIZE), - SHARED_MEMORY_MASK = (SHARED_MEMORY_SIZE - 1), DSP_MEMORY_SIZE = 0x00080000, ///< DSP memory size DSP_MEMORY_VADDR = 0x1FF00000, ///< DSP memory virtual address @@ -39,37 +37,31 @@ enum : u32 { CONFIG_MEMORY_SIZE = 0x00001000, ///< Configuration memory size CONFIG_MEMORY_VADDR = 0x1FF80000, ///< Configuration memory virtual address CONFIG_MEMORY_VADDR_END = (CONFIG_MEMORY_VADDR + CONFIG_MEMORY_SIZE), - CONFIG_MEMORY_MASK = (CONFIG_MEMORY_SIZE - 1), KERNEL_MEMORY_SIZE = 0x00001000, ///< Kernel memory size KERNEL_MEMORY_VADDR = 0xFFFF0000, ///< Kernel memory where the kthread objects etc are KERNEL_MEMORY_VADDR_END = (KERNEL_MEMORY_VADDR + KERNEL_MEMORY_SIZE), - KERNEL_MEMORY_MASK = (KERNEL_MEMORY_SIZE - 1), EXEFS_CODE_SIZE = 0x03F00000, EXEFS_CODE_VADDR = 0x00100000, ///< ExeFS:/.code is loaded here EXEFS_CODE_VADDR_END = (EXEFS_CODE_VADDR + EXEFS_CODE_SIZE), - EXEFS_CODE_MASK = 0x03FFFFFF, // Region of FCRAM used by system SYSTEM_MEMORY_SIZE = 0x02C00000, ///< 44MB SYSTEM_MEMORY_VADDR = 0x04000000, SYSTEM_MEMORY_VADDR_END = (SYSTEM_MEMORY_VADDR + SYSTEM_MEMORY_SIZE), - SYSTEM_MEMORY_MASK = 0x03FFFFFF, HEAP_SIZE = FCRAM_SIZE, ///< Application heap size //HEAP_PADDR = HEAP_GSP_SIZE, //HEAP_PADDR_END = (HEAP_PADDR + HEAP_SIZE), HEAP_VADDR = 0x08000000, HEAP_VADDR_END = (HEAP_VADDR + HEAP_SIZE), - HEAP_MASK = (HEAP_SIZE - 1), HEAP_GSP_SIZE = 0x02000000, ///< GSP heap size... TODO: Define correctly? HEAP_GSP_VADDR = 0x14000000, HEAP_GSP_VADDR_END = (HEAP_GSP_VADDR + HEAP_GSP_SIZE), HEAP_GSP_PADDR = 0x00000000, HEAP_GSP_PADDR_END = (HEAP_GSP_PADDR + HEAP_GSP_SIZE), - HEAP_GSP_MASK = (HEAP_GSP_SIZE - 1), HARDWARE_IO_SIZE = 0x01000000, HARDWARE_IO_PADDR = 0x10000000, ///< IO physical address start @@ -82,12 +74,10 @@ enum : u32 { VRAM_VADDR = 0x1F000000, VRAM_PADDR_END = (VRAM_PADDR + VRAM_SIZE), VRAM_VADDR_END = (VRAM_VADDR + VRAM_SIZE), - VRAM_MASK = 0x007FFFFF, SCRATCHPAD_SIZE = 0x00004000, ///< Typical stack size - TODO: Read from exheader SCRATCHPAD_VADDR_END = 0x10000000, SCRATCHPAD_VADDR = (SCRATCHPAD_VADDR_END - SCRATCHPAD_SIZE), ///< Stack space - SCRATCHPAD_MASK = (SCRATCHPAD_SIZE - 1), ///< Scratchpad memory mask }; //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/core/mem_map_funcs.cpp b/src/core/mem_map_funcs.cpp index e8747840c..1887bcedb 100644 --- a/src/core/mem_map_funcs.cpp +++ b/src/core/mem_map_funcs.cpp @@ -56,7 +56,7 @@ inline void Read(T &var, const VAddr vaddr) { // Kernel memory command buffer if (vaddr >= KERNEL_MEMORY_VADDR && vaddr < KERNEL_MEMORY_VADDR_END) { - var = *((const T*)&g_kernel_mem[vaddr & KERNEL_MEMORY_MASK]); + var = *((const T*)&g_kernel_mem[vaddr - KERNEL_MEMORY_VADDR]); // Hardware I/O register reads // 0x10XXXXXX- is physical address space, 0x1EXXXXXX is virtual address space @@ -65,23 +65,23 @@ inline void Read(T &var, const VAddr vaddr) { // ExeFS:/.code is loaded here } else if ((vaddr >= EXEFS_CODE_VADDR) && (vaddr < EXEFS_CODE_VADDR_END)) { - var = *((const T*)&g_exefs_code[vaddr & EXEFS_CODE_MASK]); + var = *((const T*)&g_exefs_code[vaddr - EXEFS_CODE_VADDR]); // FCRAM - GSP heap } else if ((vaddr >= HEAP_GSP_VADDR) && (vaddr < HEAP_GSP_VADDR_END)) { - var = *((const T*)&g_heap_gsp[vaddr & HEAP_GSP_MASK]); + var = *((const T*)&g_heap_gsp[vaddr - HEAP_GSP_VADDR]); // FCRAM - application heap } else if ((vaddr >= HEAP_VADDR) && (vaddr < HEAP_VADDR_END)) { - var = *((const T*)&g_heap[vaddr & HEAP_MASK]); + var = *((const T*)&g_heap[vaddr - HEAP_VADDR]); // Shared memory } else if ((vaddr >= SHARED_MEMORY_VADDR) && (vaddr < SHARED_MEMORY_VADDR_END)) { - var = *((const T*)&g_shared_mem[vaddr & SHARED_MEMORY_MASK]); + var = *((const T*)&g_shared_mem[vaddr - SHARED_MEMORY_VADDR]); // System memory } else if ((vaddr >= SYSTEM_MEMORY_VADDR) && (vaddr < SYSTEM_MEMORY_VADDR_END)) { - var = *((const T*)&g_system_mem[vaddr & SYSTEM_MEMORY_MASK]); + var = *((const T*)&g_system_mem[vaddr - SYSTEM_MEMORY_VADDR]); // Config memory } else if ((vaddr >= CONFIG_MEMORY_VADDR) && (vaddr < CONFIG_MEMORY_VADDR_END)) { @@ -89,7 +89,7 @@ inline void Read(T &var, const VAddr vaddr) { // VRAM } else if ((vaddr >= VRAM_VADDR) && (vaddr < VRAM_VADDR_END)) { - var = *((const T*)&g_vram[vaddr & VRAM_MASK]); + var = *((const T*)&g_vram[vaddr - VRAM_VADDR]); } else { ERROR_LOG(MEMMAP, "unknown Read%lu @ 0x%08X", sizeof(var) * 8, vaddr); @@ -101,7 +101,7 @@ inline void Write(const VAddr vaddr, const T data) { // Kernel memory command buffer if (vaddr >= KERNEL_MEMORY_VADDR && vaddr < KERNEL_MEMORY_VADDR_END) { - *(T*)&g_kernel_mem[vaddr & KERNEL_MEMORY_MASK] = data; + *(T*)&g_kernel_mem[vaddr - KERNEL_MEMORY_VADDR] = data; // Hardware I/O register writes // 0x10XXXXXX- is physical address space, 0x1EXXXXXX is virtual address space @@ -110,27 +110,27 @@ inline void Write(const VAddr vaddr, const T data) { // ExeFS:/.code is loaded here } else if ((vaddr >= EXEFS_CODE_VADDR) && (vaddr < EXEFS_CODE_VADDR_END)) { - *(T*)&g_exefs_code[vaddr & EXEFS_CODE_MASK] = data; + *(T*)&g_exefs_code[vaddr - EXEFS_CODE_VADDR] = data; // FCRAM - GSP heap } else if ((vaddr >= HEAP_GSP_VADDR) && (vaddr < HEAP_GSP_VADDR_END)) { - *(T*)&g_heap_gsp[vaddr & HEAP_GSP_MASK] = data; + *(T*)&g_heap_gsp[vaddr - HEAP_GSP_VADDR] = data; // FCRAM - application heap } else if ((vaddr >= HEAP_VADDR) && (vaddr < HEAP_VADDR_END)) { - *(T*)&g_heap[vaddr & HEAP_MASK] = data; + *(T*)&g_heap[vaddr - HEAP_VADDR] = data; // Shared memory } else if ((vaddr >= SHARED_MEMORY_VADDR) && (vaddr < SHARED_MEMORY_VADDR_END)) { - *(T*)&g_shared_mem[vaddr & SHARED_MEMORY_MASK] = data; + *(T*)&g_shared_mem[vaddr - SHARED_MEMORY_VADDR] = data; // System memory } else if ((vaddr >= SYSTEM_MEMORY_VADDR) && (vaddr < SYSTEM_MEMORY_VADDR_END)) { - *(T*)&g_system_mem[vaddr & SYSTEM_MEMORY_MASK] = data; + *(T*)&g_system_mem[vaddr - SYSTEM_MEMORY_VADDR] = data; // VRAM } else if ((vaddr >= VRAM_VADDR) && (vaddr < VRAM_VADDR_END)) { - *(T*)&g_vram[vaddr & VRAM_MASK] = data; + *(T*)&g_vram[vaddr - VRAM_VADDR] = data; //} else if ((vaddr & 0xFFF00000) == 0x1FF00000) { // _assert_msg_(MEMMAP, false, "umimplemented write to DSP memory"); @@ -148,31 +148,31 @@ inline void Write(const VAddr vaddr, const T data) { u8 *GetPointer(const VAddr vaddr) { // Kernel memory command buffer if (vaddr >= KERNEL_MEMORY_VADDR && vaddr < KERNEL_MEMORY_VADDR_END) { - return g_kernel_mem + (vaddr & KERNEL_MEMORY_MASK); + return g_kernel_mem + (vaddr - KERNEL_MEMORY_VADDR); // ExeFS:/.code is loaded here } else if ((vaddr >= EXEFS_CODE_VADDR) && (vaddr < EXEFS_CODE_VADDR_END)) { - return g_exefs_code + (vaddr & EXEFS_CODE_MASK); + return g_exefs_code + (vaddr - EXEFS_CODE_VADDR); // FCRAM - GSP heap } else if ((vaddr >= HEAP_GSP_VADDR) && (vaddr < HEAP_GSP_VADDR_END)) { - return g_heap_gsp + (vaddr & HEAP_GSP_MASK); + return g_heap_gsp + (vaddr - HEAP_GSP_VADDR); // FCRAM - application heap } else if ((vaddr >= HEAP_VADDR) && (vaddr < HEAP_VADDR_END)) { - return g_heap + (vaddr & HEAP_MASK); + return g_heap + (vaddr - HEAP_VADDR); // Shared memory } else if ((vaddr >= SHARED_MEMORY_VADDR) && (vaddr < SHARED_MEMORY_VADDR_END)) { - return g_shared_mem + (vaddr & SHARED_MEMORY_MASK); + return g_shared_mem + (vaddr - SHARED_MEMORY_VADDR); // System memory } else if ((vaddr >= SYSTEM_MEMORY_VADDR) && (vaddr < SYSTEM_MEMORY_VADDR_END)) { - return g_system_mem + (vaddr & SYSTEM_MEMORY_MASK); + return g_system_mem + (vaddr - SYSTEM_MEMORY_VADDR); // VRAM } else if ((vaddr >= VRAM_VADDR) && (vaddr < VRAM_VADDR_END)) { - return g_vram + (vaddr & VRAM_MASK); + return g_vram + (vaddr - VRAM_VADDR); } else { ERROR_LOG(MEMMAP, "unknown GetPointer @ 0x%08x", vaddr); diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index 8a6ba2560..431139cc2 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp @@ -8,6 +8,7 @@ #include "pica.h" #include "primitive_assembly.h" #include "vertex_shader.h" +#include "core/hle/service/gsp_gpu.h" #include "debug_utils/debug_utils.h" @@ -34,15 +35,26 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { u32 old_value = registers[id]; registers[id] = (old_value & ~mask) | (value & mask); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::CommandLoaded, reinterpret_cast<void*>(&id)); + DebugUtils::OnPicaRegWrite(id, registers[id]); switch(id) { + // Trigger IRQ + case PICA_REG_INDEX(trigger_irq): + GSP_GPU::SignalInterrupt(GSP_GPU::InterruptId::P3D); + return; + // It seems like these trigger vertex rendering case PICA_REG_INDEX(trigger_draw): case PICA_REG_INDEX(trigger_draw_indexed): { DebugUtils::DumpTevStageConfig(registers.GetTevStages()); + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::IncomingPrimitiveBatch, nullptr); + const auto& attribute_config = registers.vertex_attributes; const u8* const base_address = Memory::GetPointer(attribute_config.GetBaseAddress()); @@ -132,6 +144,10 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { clipper_primitive_assembler.SubmitVertex(output, Clipper::ProcessTriangle); } geometry_dumper.Dump(); + + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::FinishedPrimitiveBatch, nullptr); + break; } @@ -229,6 +245,9 @@ static inline void WritePicaReg(u32 id, u32 value, u32 mask) { default: break; } + + if (g_debug_context) + g_debug_context->OnEvent(DebugContext::Event::CommandProcessed, reinterpret_cast<void*>(&id)); } static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) { @@ -259,8 +278,9 @@ static std::ptrdiff_t ExecuteCommandBlock(const u32* first_command_word) { void ProcessCommandList(const u32* list, u32 size) { u32* read_pointer = (u32*)list; + u32 list_length = size / sizeof(u32); - while (read_pointer < list + size) { + while (read_pointer < list + list_length) { read_pointer += ExecuteCommandBlock(read_pointer); } } diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp index 8a5f11424..71b03f31c 100644 --- a/src/video_core/debug_utils/debug_utils.cpp +++ b/src/video_core/debug_utils/debug_utils.cpp @@ -3,6 +3,8 @@ // Refer to the license.txt file included. #include <algorithm> +#include <condition_variable> +#include <list> #include <map> #include <fstream> #include <mutex> @@ -12,14 +14,56 @@ #include <png.h> #endif +#include "common/log.h" #include "common/file_util.h" +#include "video_core/math.h" #include "video_core/pica.h" #include "debug_utils.h" namespace Pica { +void DebugContext::OnEvent(Event event, void* data) { + if (!breakpoints[event].enabled) + return; + + { + std::unique_lock<std::mutex> lock(breakpoint_mutex); + + // TODO: Should stop the CPU thread here once we multithread emulation. + + active_breakpoint = event; + at_breakpoint = true; + + // Tell all observers that we hit a breakpoint + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnPicaBreakPointHit(event, data); + } + + // Wait until another thread tells us to Resume() + resume_from_breakpoint.wait(lock, [&]{ return !at_breakpoint; }); + } +} + +void DebugContext::Resume() { + { + std::unique_lock<std::mutex> lock(breakpoint_mutex); + + // Tell all observers that we are about to resume + for (auto& breakpoint_observer : breakpoint_observers) { + breakpoint_observer->OnPicaResume(); + } + + // Resume the waiting thread (i.e. OnEvent()) + at_breakpoint = false; + } + + resume_from_breakpoint.notify_one(); +} + +std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global + namespace DebugUtils { void GeometryDumper::AddTriangle(Vertex& v0, Vertex& v1, Vertex& v2) { @@ -312,6 +356,42 @@ std::unique_ptr<PicaTrace> FinishPicaTracing() return std::move(ret); } +const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info) { + _dbg_assert_(GPU, info.format == Pica::Regs::TextureFormat::RGB8); + + // Cf. rasterizer code for an explanation of this algorithm. + int texel_index_within_tile = 0; + for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { + int sub_tile_width = 1 << block_size_index; + int sub_tile_height = 1 << block_size_index; + + int sub_tile_index = (x & sub_tile_width) << block_size_index; + sub_tile_index += 2 * ((y & sub_tile_height) << block_size_index); + texel_index_within_tile += sub_tile_index; + } + + const int block_width = 8; + const int block_height = 8; + + int coarse_x = (x / block_width) * block_width; + int coarse_y = (y / block_height) * block_height; + + const u8* source_ptr = source + coarse_x * block_height * 3 + coarse_y * info.stride + texel_index_within_tile * 3; + return { source_ptr[2], source_ptr[1], source_ptr[0], 255 }; +} + +TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config, + const Regs::TextureFormat& format) +{ + TextureInfo info; + info.address = config.GetPhysicalAddress(); + info.width = config.width; + info.height = config.height; + info.format = format; + info.stride = Pica::Regs::BytesPerPixel(info.format) * info.width; + return info; +} + void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { // NOTE: Permanently enabling this just trashes hard disks for no reason. // Hence, this is currently disabled. @@ -377,27 +457,15 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) { buf = new u8[row_stride * texture_config.height]; for (unsigned y = 0; y < texture_config.height; ++y) { for (unsigned x = 0; x < texture_config.width; ++x) { - // Cf. rasterizer code for an explanation of this algorithm. - int texel_index_within_tile = 0; - for (int block_size_index = 0; block_size_index < 3; ++block_size_index) { - int sub_tile_width = 1 << block_size_index; - int sub_tile_height = 1 << block_size_index; - - int sub_tile_index = (x & sub_tile_width) << block_size_index; - sub_tile_index += 2 * ((y & sub_tile_height) << block_size_index); - texel_index_within_tile += sub_tile_index; - } - - const int block_width = 8; - const int block_height = 8; - - int coarse_x = (x / block_width) * block_width; - int coarse_y = (y / block_height) * block_height; - - u8* source_ptr = (u8*)data + coarse_x * block_height * 3 + coarse_y * row_stride + texel_index_within_tile * 3; - buf[3 * x + y * row_stride ] = source_ptr[2]; - buf[3 * x + y * row_stride + 1] = source_ptr[1]; - buf[3 * x + y * row_stride + 2] = source_ptr[0]; + TextureInfo info; + info.width = texture_config.width; + info.height = texture_config.height; + info.stride = row_stride; + info.format = registers.texture0_format; + Math::Vec4<u8> texture_color = LookupTexture(data, x, y, info); + buf[3 * x + y * row_stride ] = texture_color.r(); + buf[3 * x + y * row_stride + 1] = texture_color.g(); + buf[3 * x + y * row_stride + 2] = texture_color.b(); } } diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h index b1558cfae..51f14f12f 100644 --- a/src/video_core/debug_utils/debug_utils.h +++ b/src/video_core/debug_utils/debug_utils.h @@ -5,13 +5,147 @@ #pragma once #include <array> +#include <condition_variable> +#include <list> +#include <map> #include <memory> +#include <mutex> #include <vector> +#include "video_core/math.h" #include "video_core/pica.h" namespace Pica { +class DebugContext { +public: + enum class Event { + FirstEvent = 0, + + CommandLoaded = FirstEvent, + CommandProcessed, + IncomingPrimitiveBatch, + FinishedPrimitiveBatch, + + NumEvents + }; + + /** + * Inherit from this class to be notified of events registered to some debug context. + * Most importantly this is used for our debugger GUI. + * + * To implement event handling, override the OnPicaBreakPointHit and OnPicaResume methods. + * @warning All BreakPointObservers need to be on the same thread to guarantee thread-safe state access + * @todo Evaluate an alternative interface, in which there is only one managing observer and multiple child observers running (by design) on the same thread. + */ + class BreakPointObserver { + public: + /// Constructs the object such that it observes events of the given DebugContext. + BreakPointObserver(std::shared_ptr<DebugContext> debug_context) : context_weak(debug_context) { + std::unique_lock<std::mutex> lock(debug_context->breakpoint_mutex); + debug_context->breakpoint_observers.push_back(this); + } + + virtual ~BreakPointObserver() { + auto context = context_weak.lock(); + if (context) { + std::unique_lock<std::mutex> lock(context->breakpoint_mutex); + context->breakpoint_observers.remove(this); + + // If we are the last observer to be destroyed, tell the debugger context that + // it is free to continue. In particular, this is required for a proper Citra + // shutdown, when the emulation thread is waiting at a breakpoint. + if (context->breakpoint_observers.empty()) + context->Resume(); + } + } + + /** + * Action to perform when a breakpoint was reached. + * @param event Type of event which triggered the breakpoint + * @param data Optional data pointer (if unused, this is a nullptr) + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnPicaBreakPointHit(Event, void*) { + } + + /** + * Action to perform when emulation is resumed from a breakpoint. + * @note This function will perform nothing unless it is overridden in the child class. + */ + virtual void OnPicaResume() { + } + + protected: + /** + * Weak context pointer. This need not be valid, so when requesting a shared_ptr via + * context_weak.lock(), always compare the result against nullptr. + */ + std::weak_ptr<DebugContext> context_weak; + }; + + /** + * Simple structure defining a breakpoint state + */ + struct BreakPoint { + bool enabled = false; + }; + + /** + * Static constructor used to create a shared_ptr of a DebugContext. + */ + static std::shared_ptr<DebugContext> Construct() { + return std::shared_ptr<DebugContext>(new DebugContext); + } + + /** + * Used by the emulation core when a given event has happened. If a breakpoint has been set + * for this event, OnEvent calls the event handlers of the registered breakpoint observers. + * The current thread then is halted until Resume() is called from another thread (or until + * emulation is stopped). + * @param event Event which has happened + * @param data Optional data pointer (pass nullptr if unused). Needs to remain valid until Resume() is called. + */ + void OnEvent(Event event, void* data); + + /** + * Resume from the current breakpoint. + * @warning Calling this from the same thread that OnEvent was called in will cause a deadlock. Calling from any other thread is safe. + */ + void Resume(); + + /** + * Delete all set breakpoints and resume emulation. + */ + void ClearBreakpoints() { + breakpoints.clear(); + Resume(); + } + + // TODO: Evaluate if access to these members should be hidden behind a public interface. + std::map<Event, BreakPoint> breakpoints; + Event active_breakpoint; + bool at_breakpoint = false; + +private: + /** + * Private default constructor to make sure people always construct this through Construct() + * instead. + */ + DebugContext() = default; + + /// Mutex protecting current breakpoint state and the observer list. + std::mutex breakpoint_mutex; + + /// Used by OnEvent to wait for resumption. + std::condition_variable resume_from_breakpoint; + + /// List of registered observers + std::list<BreakPointObserver*> breakpoint_observers; +}; + +extern std::shared_ptr<DebugContext> g_debug_context; // TODO: Get rid of this global + namespace DebugUtils { // Simple utility class for dumping geometry data to an OBJ file @@ -57,6 +191,18 @@ bool IsPicaTracing(); void OnPicaRegWrite(u32 id, u32 value); std::unique_ptr<PicaTrace> FinishPicaTracing(); +struct TextureInfo { + unsigned int address; + int width; + int height; + int stride; + Pica::Regs::TextureFormat format; + + static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config, + const Pica::Regs::TextureFormat& format); +}; + +const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info); void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data); void DumpTevStageConfig(const std::array<Pica::Regs::TevStageConfig,6>& stages); diff --git a/src/video_core/pica.h b/src/video_core/pica.h index 5fe15a218..8bac178ca 100644 --- a/src/video_core/pica.h +++ b/src/video_core/pica.h @@ -45,10 +45,16 @@ struct Regs { #define INSERT_PADDING_WORDS_HELPER2(x, y) INSERT_PADDING_WORDS_HELPER1(x, y) #define INSERT_PADDING_WORDS(num_words) u32 INSERT_PADDING_WORDS_HELPER2(pad, __LINE__)[(num_words)]; - INSERT_PADDING_WORDS(0x41); + INSERT_PADDING_WORDS(0x10); + + u32 trigger_irq; + + INSERT_PADDING_WORDS(0x30); BitField<0, 24, u32> viewport_size_x; + INSERT_PADDING_WORDS(0x1); + BitField<0, 24, u32> viewport_size_y; INSERT_PADDING_WORDS(0x9); @@ -109,7 +115,7 @@ struct Regs { u32 address; - u32 GetPhysicalAddress() { + u32 GetPhysicalAddress() const { return DecodeAddressRegister(address) - Memory::FCRAM_PADDR + Memory::HEAP_GSP_VADDR; } @@ -130,7 +136,26 @@ struct Regs { // Seems like they are luminance formats and compressed textures. }; - BitField<0, 1, u32> texturing_enable; + static unsigned BytesPerPixel(TextureFormat format) { + switch (format) { + case TextureFormat::RGBA8: + return 4; + + case TextureFormat::RGB8: + return 3; + + case TextureFormat::RGBA5551: + case TextureFormat::RGB565: + case TextureFormat::RGBA4: + return 2; + + default: + // placeholder for yet unknown formats + return 1; + } + } + + BitField< 0, 1, u32> texturing_enable; TextureConfig texture0; INSERT_PADDING_WORDS(0x8); BitField<0, 4, TextureFormat> texture0_format; @@ -517,10 +542,6 @@ struct Regs { static std::string GetCommandName(int index) { std::map<u32, std::string> map; - // TODO: MSVC does not support using offsetof() on non-static data members even though this - // is technically allowed since C++11. Hence, this functionality is disabled until - // MSVC properly supports it. - #ifndef _MSC_VER Regs regs; #define ADD_FIELD(name) \ do { \ @@ -529,6 +550,7 @@ struct Regs { map.insert({i, #name + std::string("+") + std::to_string(i-PICA_REG_INDEX(name))}); \ } while(false) + ADD_FIELD(trigger_irq); ADD_FIELD(viewport_size_x); ADD_FIELD(viewport_size_y); ADD_FIELD(viewport_depth_range); @@ -557,7 +579,6 @@ struct Regs { ADD_FIELD(vs_swizzle_patterns); #undef ADD_FIELD - #endif // _MSC_VER // Return empty string if no match is found return map[index]; @@ -593,6 +614,7 @@ private: #ifndef _MSC_VER #define ASSERT_REG_POSITION(field_name, position) static_assert(offsetof(Regs, field_name) == position * 4, "Field "#field_name" has invalid position") +ASSERT_REG_POSITION(trigger_irq, 0x10); ASSERT_REG_POSITION(viewport_size_x, 0x41); ASSERT_REG_POSITION(viewport_size_y, 0x43); ASSERT_REG_POSITION(viewport_depth_range, 0x4d); diff --git a/src/video_core/renderer_opengl/gl_shader_util.cpp b/src/video_core/renderer_opengl/gl_shader_util.cpp index a0eb0418c..fdac9ae1a 100644 --- a/src/video_core/renderer_opengl/gl_shader_util.cpp +++ b/src/video_core/renderer_opengl/gl_shader_util.cpp @@ -22,7 +22,7 @@ GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) { // Compile Vertex Shader DEBUG_LOG(GPU, "Compiling vertex shader."); - glShaderSource(vertex_shader_id, 1, &vertex_shader, NULL); + glShaderSource(vertex_shader_id, 1, &vertex_shader, nullptr); glCompileShader(vertex_shader_id); // Check Vertex Shader @@ -31,14 +31,14 @@ GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) { if (info_log_length > 1) { std::vector<char> vertex_shader_error(info_log_length); - glGetShaderInfoLog(vertex_shader_id, info_log_length, NULL, &vertex_shader_error[0]); + glGetShaderInfoLog(vertex_shader_id, info_log_length, nullptr, &vertex_shader_error[0]); DEBUG_LOG(GPU, "%s", &vertex_shader_error[0]); } // Compile Fragment Shader DEBUG_LOG(GPU, "Compiling fragment shader."); - glShaderSource(fragment_shader_id, 1, &fragment_shader, NULL); + glShaderSource(fragment_shader_id, 1, &fragment_shader, nullptr); glCompileShader(fragment_shader_id); // Check Fragment Shader @@ -47,7 +47,7 @@ GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) { if (info_log_length > 1) { std::vector<char> fragment_shader_error(info_log_length); - glGetShaderInfoLog(fragment_shader_id, info_log_length, NULL, &fragment_shader_error[0]); + glGetShaderInfoLog(fragment_shader_id, info_log_length, nullptr, &fragment_shader_error[0]); DEBUG_LOG(GPU, "%s", &fragment_shader_error[0]); } @@ -65,7 +65,7 @@ GLuint LoadShaders(const char* vertex_shader, const char* fragment_shader) { if (info_log_length > 1) { std::vector<char> program_error(info_log_length); - glGetProgramInfoLog(program_id, info_log_length, NULL, &program_error[0]); + glGetProgramInfoLog(program_id, info_log_length, nullptr, &program_error[0]); DEBUG_LOG(GPU, "%s", &program_error[0]); } diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp index c779771c5..b581ff4da 100644 --- a/src/video_core/video_core.cpp +++ b/src/video_core/video_core.cpp @@ -17,8 +17,8 @@ namespace VideoCore { -EmuWindow* g_emu_window = NULL; ///< Frontend emulator window -RendererBase* g_renderer = NULL; ///< Renderer plugin +EmuWindow* g_emu_window = nullptr; ///< Frontend emulator window +RendererBase* g_renderer = nullptr; ///< Renderer plugin int g_current_frame = 0; /// Initialize the video core |