summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/citra/emu_window/emu_window_glfw.cpp6
-rw-r--r--src/citra_qt/CMakeLists.txt7
-rw-r--r--src/citra_qt/bootmanager.cpp15
-rw-r--r--src/citra_qt/debugger/graphics_breakpoints.cpp261
-rw-r--r--src/citra_qt/debugger/graphics_breakpoints.hxx53
-rw-r--r--src/citra_qt/debugger/graphics_breakpoints_p.hxx38
-rw-r--r--src/citra_qt/debugger/graphics_cmdlists.cpp241
-rw-r--r--src/citra_qt/debugger/graphics_cmdlists.hxx37
-rw-r--r--src/citra_qt/debugger/graphics_framebuffer.cpp282
-rw-r--r--src/citra_qt/debugger/graphics_framebuffer.hxx92
-rw-r--r--src/citra_qt/hotkeys.cpp4
-rw-r--r--src/citra_qt/main.cpp26
-rw-r--r--src/citra_qt/util/spinbox.cpp303
-rw-r--r--src/citra_qt/util/spinbox.hxx88
-rw-r--r--src/common/chunk_file.h24
-rw-r--r--src/common/common_funcs.h8
-rw-r--r--src/common/console_listener.cpp10
-rw-r--r--src/common/extended_trace.cpp24
-rw-r--r--src/common/fifo_queue.h4
-rw-r--r--src/common/file_util.cpp24
-rw-r--r--src/common/file_util.h4
-rw-r--r--src/common/linear_disk_cache.h2
-rw-r--r--src/common/log.h4
-rw-r--r--src/common/log_manager.cpp5
-rw-r--r--src/common/mem_arena.cpp20
-rw-r--r--src/common/memory_util.cpp10
-rw-r--r--src/common/misc.cpp4
-rw-r--r--src/common/platform.h2
-rw-r--r--src/common/string_util.cpp12
-rw-r--r--src/common/thread_queue_list.h12
-rw-r--r--src/common/timer.cpp6
-rw-r--r--src/common/utf8.cpp24
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/arm/interpreter/armemu.cpp8
-rw-r--r--src/core/core_timing.cpp12
-rw-r--r--src/core/file_sys/archive_sdmc.cpp2
-rw-r--r--src/core/file_sys/directory.h6
-rw-r--r--src/core/file_sys/directory_romfs.cpp4
-rw-r--r--src/core/file_sys/directory_romfs.h6
-rw-r--r--src/core/file_sys/directory_sdmc.cpp13
-rw-r--r--src/core/file_sys/directory_sdmc.h7
-rw-r--r--src/core/file_sys/file_sdmc.cpp9
-rw-r--r--src/core/hle/kernel/address_arbiter.cpp2
-rw-r--r--src/core/hle/kernel/archive.cpp5
-rw-r--r--src/core/hle/kernel/thread.cpp29
-rw-r--r--src/core/hle/kernel/thread.h11
-rw-r--r--src/core/hle/service/cfg_u.cpp92
-rw-r--r--src/core/hle/service/gsp_gpu.cpp23
-rw-r--r--src/core/hle/service/service.h24
-rw-r--r--src/core/hw/gpu.cpp3
-rw-r--r--src/core/hw/gpu.h2
-rw-r--r--src/core/hw/hw.cpp13
-rw-r--r--src/core/hw/ndma.cpp47
-rw-r--r--src/core/hw/ndma.h26
-rw-r--r--src/core/loader/3dsx.cpp236
-rw-r--r--src/core/loader/3dsx.h32
-rw-r--r--src/core/loader/loader.cpp7
-rw-r--r--src/core/loader/loader.h1
-rw-r--r--src/core/mem_map.h10
-rw-r--r--src/core/mem_map_funcs.cpp42
-rw-r--r--src/video_core/command_processor.cpp22
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp110
-rw-r--r--src/video_core/debug_utils/debug_utils.h146
-rw-r--r--src/video_core/pica.h38
-rw-r--r--src/video_core/renderer_opengl/gl_shader_util.cpp10
-rw-r--r--src/video_core/video_core.cpp4
66 files changed, 2279 insertions, 379 deletions
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