Merge pull request #191 from citra-emu/master

uo
This commit is contained in:
emmauss 2016-06-11 23:01:40 +00:00 committed by GitHub
commit ee0669d709
95 changed files with 2718 additions and 1999 deletions

View File

@ -114,7 +114,13 @@ int main(int argc, char **argv) {
System::Init(emu_window.get());
SCOPE_EXIT({ System::Shutdown(); });
Loader::ResultStatus load_result = Loader::LoadFile(boot_filename);
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(boot_filename);
if (!loader) {
LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", boot_filename.c_str());
return -1;
}
Loader::ResultStatus load_result = loader->Load();
if (Loader::ResultStatus::Success != load_result) {
LOG_CRITICAL(Frontend, "Failed to load ROM (Error %i)!", load_result);
return -1;

View File

@ -44,12 +44,16 @@ bool Config::LoadINI(const std::string& default_contents, bool retry) {
}
static const std::array<int, Settings::NativeInput::NUM_INPUTS> defaults = {
// directly mapped keys
SDL_SCANCODE_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X,
SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_1, SDL_SCANCODE_2,
SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B,
SDL_SCANCODE_T, SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H,
SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L,
// indirectly mapped keys
SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT,
SDL_SCANCODE_I, SDL_SCANCODE_K, SDL_SCANCODE_J, SDL_SCANCODE_L
SDL_SCANCODE_D,
};
void Config::ReadValues() {
@ -58,6 +62,7 @@ void Config::ReadValues() {
Settings::values.input_mappings[Settings::NativeInput::All[i]] =
sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[i]);
}
Settings::values.pad_circle_modifier_scale = (float)sdl2_config->GetReal("Controls", "pad_circle_modifier_scale", 0.5);
// Core
Settings::values.frame_skip = sdl2_config->GetInteger("Core", "frame_skip", 0);
@ -77,8 +82,9 @@ void Config::ReadValues() {
// Data Storage
Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
// System Region
Settings::values.region_value = sdl2_config->GetInteger("System Region", "region_value", 1);
// System
Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", false);
Settings::values.region_value = sdl2_config->GetInteger("System", "region_value", 1);
// Miscellaneous
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Info");

View File

@ -23,14 +23,19 @@ pad_l =
pad_r =
pad_zl =
pad_zr =
pad_sup =
pad_sdown =
pad_sleft =
pad_sright =
pad_cup =
pad_cdown =
pad_cleft =
pad_cright =
pad_circle_up =
pad_circle_down =
pad_circle_left =
pad_circle_right =
pad_circle_modifier =
# The applied modifier scale to circle pad.
# Must be in range of 0.0-1.0. Defaults to 0.5
pad_circle_modifier_scale =
[Core]
# The applied frameskip amount. Must be a power of two.
@ -66,7 +71,11 @@ output_engine =
# 1 (default): Yes, 0: No
use_virtual_sd =
[System Region]
[System]
# The system model that Citra will try to emulate
# 0: Old 3DS (default), 1: New 3DS
is_new_3ds =
# The system region that Citra will use during emulation
# 0: Japan, 1: USA (default), 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
region_value =

View File

@ -40,9 +40,9 @@ void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
if (state == SDL_PRESSED) {
KeyPressed({ key, keyboard_id });
KeyMap::PressKey(*this, { key, keyboard_id });
} else if (state == SDL_RELEASED) {
KeyReleased({ key, keyboard_id });
KeyMap::ReleaseKey(*this, { key, keyboard_id });
}
}
@ -168,8 +168,9 @@ void EmuWindow_SDL2::DoneCurrent() {
}
void EmuWindow_SDL2::ReloadSetKeymaps() {
KeyMap::ClearKeyMapping(keyboard_id);
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, Service::HID::pad_mapping[i]);
KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, KeyMap::mapping_targets[i]);
}
}

View File

@ -2,8 +2,6 @@ set(CMAKE_AUTOMOC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(SRCS
config/controller_config.cpp
config/controller_config_util.cpp
config.cpp
debugger/callstack.cpp
debugger/disassembler.cpp
@ -11,7 +9,7 @@ set(SRCS
debugger/graphics_breakpoint_observer.cpp
debugger/graphics_breakpoints.cpp
debugger/graphics_cmdlists.cpp
debugger/graphics_framebuffer.cpp
debugger/graphics_surface.cpp
debugger/graphics_tracing.cpp
debugger/graphics_vertex_shader.cpp
debugger/profiler.cpp
@ -33,8 +31,6 @@ set(SRCS
)
set(HEADERS
config/controller_config.h
config/controller_config_util.h
config.h
debugger/callstack.h
debugger/disassembler.h
@ -43,7 +39,7 @@ set(HEADERS
debugger/graphics_breakpoints.h
debugger/graphics_breakpoints_p.h
debugger/graphics_cmdlists.h
debugger/graphics_framebuffer.h
debugger/graphics_surface.h
debugger/graphics_tracing.h
debugger/graphics_vertex_shader.h
debugger/profiler.h
@ -65,7 +61,6 @@ set(HEADERS
)
set(UIS
config/controller_config.ui
debugger/callstack.ui
debugger/disassembler.ui
debugger/profiler.ui

View File

@ -235,12 +235,12 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
void GRenderWindow::keyPressEvent(QKeyEvent* event)
{
this->KeyPressed({event->key(), keyboard_id});
KeyMap::PressKey(*this, { event->key(), keyboard_id });
}
void GRenderWindow::keyReleaseEvent(QKeyEvent* event)
{
this->KeyReleased({event->key(), keyboard_id});
KeyMap::ReleaseKey(*this, { event->key(), keyboard_id });
}
void GRenderWindow::mousePressEvent(QMouseEvent *event)
@ -270,8 +270,9 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent *event)
void GRenderWindow::ReloadSetKeymaps()
{
KeyMap::ClearKeyMapping(keyboard_id);
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) {
KeyMap::SetKeyMapping({Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id}, Service::HID::pad_mapping[i]);
KeyMap::SetKeyMapping({ Settings::values.input_mappings[Settings::NativeInput::All[i]], keyboard_id }, KeyMap::mapping_targets[i]);
}
}

View File

@ -22,12 +22,16 @@ Config::Config() {
}
static const std::array<QVariant, Settings::NativeInput::NUM_INPUTS> defaults = {
// directly mapped keys
Qt::Key_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X,
Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2,
Qt::Key_M, Qt::Key_N, Qt::Key_B,
Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H,
Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L,
// indirectly mapped keys
Qt::Key_Up, Qt::Key_Down, Qt::Key_Left, Qt::Key_Right,
Qt::Key_I, Qt::Key_K, Qt::Key_J, Qt::Key_L
Qt::Key_D,
};
void Config::ReadValues() {
@ -36,6 +40,7 @@ void Config::ReadValues() {
Settings::values.input_mappings[Settings::NativeInput::All[i]] =
qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]).toInt();
}
Settings::values.pad_circle_modifier_scale = qt_config->value("pad_circle_modifier_scale", 0.5).toFloat();
qt_config->endGroup();
qt_config->beginGroup("Core");
@ -60,7 +65,8 @@ void Config::ReadValues() {
Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
qt_config->endGroup();
qt_config->beginGroup("System Region");
qt_config->beginGroup("System");
Settings::values.is_new_3ds = qt_config->value("is_new_3ds", false).toBool();
Settings::values.region_value = qt_config->value("region_value", 1).toInt();
qt_config->endGroup();
@ -125,6 +131,7 @@ void Config::SaveValues() {
qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]),
Settings::values.input_mappings[Settings::NativeInput::All[i]]);
}
qt_config->setValue("pad_circle_modifier_scale", (double)Settings::values.pad_circle_modifier_scale);
qt_config->endGroup();
qt_config->beginGroup("Core");
@ -150,7 +157,8 @@ void Config::SaveValues() {
qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
qt_config->endGroup();
qt_config->beginGroup("System Region");
qt_config->beginGroup("System");
qt_config->setValue("is_new_3ds", Settings::values.is_new_3ds);
qt_config->setValue("region_value", Settings::values.region_value);
qt_config->endGroup();

View File

@ -1,95 +0,0 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QDialogButtonBox>
#include "controller_config.h"
#include "controller_config_util.h"
/* TODO(bunnei): ImplementMe
using common::Config;
GControllerConfig::GControllerConfig(common::Config::ControllerPort* initial_config, QWidget* parent) : QWidget(parent)
{
ui.setupUi(this);
((QGridLayout*)ui.mainStickTab->layout())->addWidget(new GStickConfig(Config::ANALOG_LEFT, Config::ANALOG_RIGHT, Config::ANALOG_UP, Config::ANALOG_DOWN, this, this), 1, 1);
((QGridLayout*)ui.cStickTab->layout())->addWidget(new GStickConfig(Config::C_LEFT, Config::C_RIGHT, Config::C_UP, Config::C_DOWN, this, this), 1, 1);
((QGridLayout*)ui.dPadTab->layout())->addWidget(new GStickConfig(Config::DPAD_LEFT, Config::DPAD_RIGHT, Config::DPAD_UP, Config::DPAD_DOWN, this, this), 1, 1);
// TODO: Arrange these more compactly?
QVBoxLayout* layout = (QVBoxLayout*)ui.buttonsTab->layout();
layout->addWidget(new GButtonConfigGroup("A Button", Config::BUTTON_A, this, ui.buttonsTab));
layout->addWidget(new GButtonConfigGroup("B Button", Config::BUTTON_B, this, ui.buttonsTab));
layout->addWidget(new GButtonConfigGroup("X Button", Config::BUTTON_X, this, ui.buttonsTab));
layout->addWidget(new GButtonConfigGroup("Y Button", Config::BUTTON_Y, this, ui.buttonsTab));
layout->addWidget(new GButtonConfigGroup("Z Button", Config::BUTTON_Z, this, ui.buttonsTab));
layout->addWidget(new GButtonConfigGroup("L Trigger", Config::TRIGGER_L, this, ui.buttonsTab));
layout->addWidget(new GButtonConfigGroup("R Trigger", Config::TRIGGER_R, this, ui.buttonsTab));
layout->addWidget(new GButtonConfigGroup("Start Button", Config::BUTTON_START, this, ui.buttonsTab));
memcpy(config, initial_config, sizeof(config));
emit ActivePortChanged(config[0]);
}
void GControllerConfig::OnKeyConfigChanged(common::Config::Control id, int key, const QString& name)
{
if (InputSourceJoypad())
{
config[GetActiveController()].pads.key_code[id] = key;
}
else
{
config[GetActiveController()].keys.key_code[id] = key;
}
emit ActivePortChanged(config[GetActiveController()]);
}
int GControllerConfig::GetActiveController()
{
return ui.activeControllerCB->currentIndex();
}
bool GControllerConfig::InputSourceJoypad()
{
return ui.inputSourceCB->currentIndex() == 1;
}
GControllerConfigDialog::GControllerConfigDialog(common::Config::ControllerPort* controller_ports, QWidget* parent) : QDialog(parent), config_ptr(controller_ports)
{
setWindowTitle(tr("Input configuration"));
QVBoxLayout* layout = new QVBoxLayout(this);
config_widget = new GControllerConfig(controller_ports, this);
layout->addWidget(config_widget);
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
layout->addWidget(buttons);
connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
connect(buttons, SIGNAL(accepted()), this, SLOT(accept()));
connect(this, SIGNAL(accepted()), this, SLOT(EnableChanges()));
layout->setSizeConstraint(QLayout::SetFixedSize);
setLayout(layout);
setModal(true);
show();
}
void GControllerConfigDialog::EnableChanges()
{
for (unsigned int i = 0; i < 4; ++i)
{
memcpy(&config_ptr[i], &config_widget->GetControllerConfig(i), sizeof(common::Config::ControllerPort));
if (common::g_config) {
// Apply changes if running a game
memcpy(&common::g_config->controller_ports(i), &config_widget->GetControllerConfig(i), sizeof(common::Config::ControllerPort));
}
}
}
*/

View File

@ -1,56 +0,0 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#ifndef _CONTROLLER_CONFIG_HXX_
#define _CONTROLLER_CONFIG_HXX_
#include <QDialog>
//#include "ui_controller_config.h"
/* TODO(bunnei): ImplementMe
#include "config.h"
class GControllerConfig : public QWidget
{
Q_OBJECT
public:
GControllerConfig(common::Config::ControllerPort* initial_config, QWidget* parent = NULL);
const common::Config::ControllerPort& GetControllerConfig(int index) const { return config[index]; }
signals:
void ActivePortChanged(const common::Config::ControllerPort&);
public slots:
void OnKeyConfigChanged(common::Config::Control id, int key, const QString& name);
private:
int GetActiveController();
bool InputSourceJoypad();
Ui::ControllerConfig ui;
common::Config::ControllerPort config[4];
};
class GControllerConfigDialog : public QDialog
{
Q_OBJECT
public:
GControllerConfigDialog(common::Config::ControllerPort* controller_ports, QWidget* parent = NULL);
public slots:
void EnableChanges();
private:
GControllerConfig* config_widget;
common::Config::ControllerPort* config_ptr;
};
*/
#endif // _CONTROLLER_CONFIG_HXX_

View File

@ -1,308 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ControllerConfig</class>
<widget class="QWidget" name="ControllerConfig">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>503</width>
<height>293</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>Controller Configuration</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="sizeConstraint">
<enum>QLayout::SetFixedSize</enum>
</property>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="1" column="1">
<widget class="QComboBox" name="activeControllerCB">
<item>
<property name="text">
<string>Controller 1</string>
</property>
</item>
<item>
<property name="text">
<string>Controller 2</string>
</property>
</item>
<item>
<property name="text">
<string>Controller 3</string>
</property>
</item>
<item>
<property name="text">
<string>Controller 4</string>
</property>
</item>
</widget>
</item>
<item row="0" column="2">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<widget class="QCheckBox" name="checkBox">
<property name="text">
<string>Enabled</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="inputSourceCB">
<item>
<property name="text">
<string>Keyboard</string>
</property>
</item>
<item>
<property name="text">
<string>Joypad</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Active Controller:</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Input Source:</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="mainStickTab">
<attribute name="title">
<string>Main Stick</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_3">
<item row="2" column="2">
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="2">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="4">
<spacer name="horizontalSpacer_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="cStickTab">
<attribute name="title">
<string>C-Stick</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<item row="1" column="0">
<spacer name="horizontalSpacer_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<spacer name="verticalSpacer_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer_4">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="2">
<spacer name="horizontalSpacer_5">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="dPadTab">
<attribute name="title">
<string>D-Pad</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_5">
<item row="1" column="2">
<spacer name="horizontalSpacer_7">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="0" column="1">
<spacer name="verticalSpacer_7">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="1">
<spacer name="verticalSpacer_6">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0">
<spacer name="horizontalSpacer_8">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<widget class="QWidget" name="buttonsTab">
<attribute name="title">
<string>Buttons</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2"/>
</widget>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
<slots>
<signal>ControlsChanged()</signal>
<signal>MainStickCleared()</signal>
<signal>CStickCleared()</signal>
<signal>DPadCleared()</signal>
<signal>ButtonsCleared()</signal>
<slot>OnControlsChanged()</slot>
<slot>OnMainStickCleared()</slot>
<slot>OnCStickCleared()</slot>
<slot>OnDPadCleared()</slot>
<slot>OnButtonsCleared()</slot>
</slots>
</ui>

View File

@ -1,125 +0,0 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QPushButton>
#include <QStyle>
#include <QGridLayout>
#include <QKeyEvent>
#include <QHBoxLayout>
#include <QLabel>
#include "controller_config_util.h"
/* TODO(bunnei): ImplementMe
GStickConfig::GStickConfig(common::Config::Control leftid, common::Config::Control rightid, common::Config::Control upid, common::Config::Control downid, QObject* change_receiver, QWidget* parent) : QWidget(parent)
{
left = new GKeyConfigButton(leftid, style()->standardIcon(QStyle::SP_ArrowLeft), QString(), change_receiver, this);
right = new GKeyConfigButton(rightid, style()->standardIcon(QStyle::SP_ArrowRight), QString(), change_receiver, this);
up = new GKeyConfigButton(upid, style()->standardIcon(QStyle::SP_ArrowUp), QString(), change_receiver, this);
down = new GKeyConfigButton(downid, style()->standardIcon(QStyle::SP_ArrowDown), QString(), change_receiver, this);
clear = new QPushButton(tr("Clear"), this);
QGridLayout* layout = new QGridLayout(this);
layout->addWidget(left, 1, 0);
layout->addWidget(right, 1, 2);
layout->addWidget(up, 0, 1);
layout->addWidget(down, 2, 1);
layout->addWidget(clear, 1, 1);
setLayout(layout);
}
GKeyConfigButton::GKeyConfigButton(common::Config::Control id, const QIcon& icon, const QString& text, QObject* change_receiver, QWidget* parent) : QPushButton(icon, text, parent), id(id), inputGrabbed(false)
{
connect(this, SIGNAL(clicked()), this, SLOT(OnClicked()));
connect(this, SIGNAL(KeyAssigned(common::Config::Control, int, const QString&)), change_receiver, SLOT(OnKeyConfigChanged(common::Config::Control, int, const QString&)));
connect(change_receiver, SIGNAL(ActivePortChanged(const common::Config::ControllerPort&)), this, SLOT(OnActivePortChanged(const common::Config::ControllerPort&)));
}
GKeyConfigButton::GKeyConfigButton(common::Config::Control id, const QString& text, QObject* change_receiver, QWidget* parent) : QPushButton(text, parent), id(id), inputGrabbed(false)
{
connect(this, SIGNAL(clicked()), this, SLOT(OnClicked()));
connect(this, SIGNAL(KeyAssigned(common::Config::Control, int, const QString&)), change_receiver, SLOT(OnKeyConfigChanged(common::Config::Control, int, const QString&)));
connect(change_receiver, SIGNAL(ActivePortChanged(const common::Config::ControllerPort&)), this, SLOT(OnActivePortChanged(const common::Config::ControllerPort&)));
}
void GKeyConfigButton::OnActivePortChanged(const common::Config::ControllerPort& config)
{
// TODO: Doesn't use joypad struct if that's the input source...
QString text = QKeySequence(config.keys.key_code[id]).toString(); // has a nicer format
if (config.keys.key_code[id] == Qt::Key_Shift) text = tr("Shift");
else if (config.keys.key_code[id] == Qt::Key_Control) text = tr("Control");
else if (config.keys.key_code[id] == Qt::Key_Alt) text = tr("Alt");
else if (config.keys.key_code[id] == Qt::Key_Meta) text = tr("Meta");
setText(text);
}
void GKeyConfigButton::OnClicked()
{
grabKeyboard();
grabMouse();
inputGrabbed = true;
old_text = text();
setText(tr("Input..."));
}
void GKeyConfigButton::keyPressEvent(QKeyEvent* event)
{
if (inputGrabbed)
{
releaseKeyboard();
releaseMouse();
setText(QString());
// TODO: Doesn't capture "return" key
// TODO: This doesn't quite work well, yet... find a better way
QString text = QKeySequence(event->key()).toString(); // has a nicer format than event->text()
int key = event->key();
if (event->modifiers() == Qt::ShiftModifier) { text = tr("Shift"); key = Qt::Key_Shift; }
else if (event->modifiers() == Qt::ControlModifier) { text = tr("Ctrl"); key = Qt::Key_Control; }
else if (event->modifiers() == Qt::AltModifier) { text = tr("Alt"); key = Qt::Key_Alt; }
else if (event->modifiers() == Qt::MetaModifier) { text = tr("Meta"); key = Qt::Key_Meta; }
setText(old_text);
emit KeyAssigned(id, key, text);
inputGrabbed = false;
// TODO: Keys like "return" cause another keyPressEvent to be generated after this one...
}
QPushButton::keyPressEvent(event); // TODO: Necessary?
}
void GKeyConfigButton::mousePressEvent(QMouseEvent* event)
{
// Abort key assignment
if (inputGrabbed)
{
releaseKeyboard();
releaseMouse();
setText(old_text);
inputGrabbed = false;
}
QAbstractButton::mousePressEvent(event);
}
GButtonConfigGroup::GButtonConfigGroup(const QString& name, common::Config::Control id, QObject* change_receiver, QWidget* parent) : QWidget(parent), id(id)
{
QHBoxLayout* layout = new QHBoxLayout(this);
QPushButton* clear_button = new QPushButton(tr("Clear"));
layout->addWidget(new QLabel(name, this));
layout->addWidget(config_button = new GKeyConfigButton(id, QString(), change_receiver, this));
layout->addWidget(clear_button);
// TODO: connect config_button, clear_button
setLayout(layout);
}
*/

View File

@ -1,82 +0,0 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#ifndef _CONTROLLER_CONFIG_UTIL_HXX_
#define _CONTROLLER_CONFIG_UTIL_HXX_
#include <QWidget>
#include <QPushButton>
/* TODO(bunnei): ImplementMe
#include "config.h"
class GStickConfig : public QWidget
{
Q_OBJECT
public:
// change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot!
GStickConfig(common::Config::Control leftid, common::Config::Control rightid, common::Config::Control upid, common::Config::Control downid, QObject* change_receiver, QWidget* parent = NULL);
signals:
void LeftChanged();
void RightChanged();
void UpChanged();
void DownChanged();
private:
QPushButton* left;
QPushButton* right;
QPushButton* up;
QPushButton* down;
QPushButton* clear;
};
class GKeyConfigButton : public QPushButton
{
Q_OBJECT
public:
// TODO: change_receiver also needs to have an ActivePortChanged(const common::Config::ControllerPort&) signal
// change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot!
GKeyConfigButton(common::Config::Control id, const QIcon& icon, const QString& text, QObject* change_receiver, QWidget* parent);
GKeyConfigButton(common::Config::Control id, const QString& text, QObject* change_receiver, QWidget* parent);
signals:
void KeyAssigned(common::Config::Control id, int key, const QString& text);
private slots:
void OnActivePortChanged(const common::Config::ControllerPort& config);
void OnClicked();
void keyPressEvent(QKeyEvent* event); // TODO: bGrabbed?
void mousePressEvent(QMouseEvent* event);
private:
common::Config::Control id;
bool inputGrabbed;
QString old_text;
};
class GButtonConfigGroup : public QWidget
{
Q_OBJECT
public:
// change_receiver needs to have a OnKeyConfigChanged(common::Config::Control, int, const QString&) slot!
GButtonConfigGroup(const QString& name, common::Config::Control id, QObject* change_receiver, QWidget* parent = NULL);
private:
GKeyConfigButton* config_button;
common::Config::Control id;
};
*/
#endif // _CONTROLLER_CONFIG_HXX_

View File

@ -37,10 +37,13 @@ void CallstackWidget::OnDebugModeEntered()
int counter = 0;
for (u32 addr = 0x10000000; addr >= sp; addr -= 4)
{
if (!Memory::IsValidVirtualAddress(addr))
break;
const u32 ret_addr = Memory::Read32(addr);
const u32 call_addr = ret_addr - 4; //get call address???
if (Memory::GetPointer(call_addr) == nullptr)
if (!Memory::IsValidVirtualAddress(call_addr))
break;
/* TODO (mattvail) clean me, move to debugger interface */

View File

@ -50,123 +50,6 @@ public:
}
};
TextureInfoDockWidget::TextureInfoDockWidget(const Pica::DebugUtils::TextureInfo& info, QWidget* parent)
: QDockWidget(tr("Texture 0x%1").arg(info.physical_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.physical_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("RGB5A1"));
format_choice->addItem(tr("RGB565"));
format_choice->addItem(tr("RGBA4"));
format_choice->addItem(tr("IA8"));
format_choice->addItem(tr("RG8"));
format_choice->addItem(tr("I8"));
format_choice->addItem(tr("A8"));
format_choice->addItem(tr("IA4"));
format_choice->addItem(tr("I4"));
format_choice->addItem(tr("A4"));
format_choice->addItem(tr("ETC1"));
format_choice->addItem(tr("ETC1A4"));
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.physical_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());
}
QPixmap TextureInfoDockWidget::ReloadPixmap() const {
u8* src = Memory::GetPhysicalPointer(info.physical_address);
return QPixmap::fromImage(LoadTexture(src, info));
}
GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) {
}
@ -249,16 +132,16 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
index = 0;
} else if (COMMAND_IN_RANGE(command_id, texture1)) {
index = 1;
} else {
} else if (COMMAND_IN_RANGE(command_id, texture2)) {
index = 2;
} else {
UNREACHABLE_MSG("Unknown texture command");
}
auto config = Pica::g_state.regs.GetTextures()[index].config;
auto format = Pica::g_state.regs.GetTextures()[index].format;
auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, 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));
// TODO: Open a surface debugger
}
}

View File

@ -61,25 +61,3 @@ private:
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;
};

View File

@ -1,356 +0,0 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QBoxLayout>
#include <QComboBox>
#include <QDebug>
#include <QLabel>
#include <QPushButton>
#include <QSpinBox>
#include "citra_qt/debugger/graphics_framebuffer.h"
#include "citra_qt/util/spinbox.h"
#include "common/color.h"
#include "core/memory.h"
#include "core/hw/gpu.h"
#include "video_core/pica.h"
#include "video_core/pica_state.h"
#include "video_core/utils.h"
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("Active Depth Buffer"));
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("RGB5A1"));
framebuffer_format_control->addItem(tr("RGB565"));
framebuffer_format_control->addItem(tr("RGBA4"));
framebuffer_format_control->addItem(tr("D16"));
framebuffer_format_control->addItem(tr("D24"));
framebuffer_format_control->addItem(tr("D24X8"));
framebuffer_format_control->addItem(tr("X24S8"));
framebuffer_format_control->addItem(tr("(unknown)"));
// 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
if (debug_context && debug_context->at_breakpoint)
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 != static_cast<unsigned>(new_value)) {
framebuffer_width = static_cast<unsigned>(new_value);
framebuffer_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
emit Update();
}
}
void GraphicsFramebufferWidget::OnFramebufferHeightChanged(int new_value)
{
if (framebuffer_height != static_cast<unsigned>(new_value)) {
framebuffer_height = static_cast<unsigned>(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...
const auto& framebuffer = Pica::g_state.regs.framebuffer;
framebuffer_address = framebuffer.GetColorBufferPhysicalAddress();
framebuffer_width = framebuffer.GetWidth();
framebuffer_height = framebuffer.GetHeight();
switch (framebuffer.color_format) {
case Pica::Regs::ColorFormat::RGBA8:
framebuffer_format = Format::RGBA8;
break;
case Pica::Regs::ColorFormat::RGB8:
framebuffer_format = Format::RGB8;
break;
case Pica::Regs::ColorFormat::RGB5A1:
framebuffer_format = Format::RGB5A1;
break;
case Pica::Regs::ColorFormat::RGB565:
framebuffer_format = Format::RGB565;
break;
case Pica::Regs::ColorFormat::RGBA4:
framebuffer_format = Format::RGBA4;
break;
default:
framebuffer_format = Format::Unknown;
break;
}
break;
}
case Source::DepthBuffer:
{
const auto& framebuffer = Pica::g_state.regs.framebuffer;
framebuffer_address = framebuffer.GetDepthBufferPhysicalAddress();
framebuffer_width = framebuffer.GetWidth();
framebuffer_height = framebuffer.GetHeight();
switch (framebuffer.depth_format) {
case Pica::Regs::DepthFormat::D16:
framebuffer_format = Format::D16;
break;
case Pica::Regs::DepthFormat::D24:
framebuffer_format = Format::D24;
break;
case Pica::Regs::DepthFormat::D24S8:
framebuffer_format = Format::D24X8;
break;
default:
framebuffer_format = Format::Unknown;
break;
}
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
u32 bytes_per_pixel = GraphicsFramebufferWidget::BytesPerPixel(framebuffer_format);
QImage decoded_image(framebuffer_width, framebuffer_height, QImage::Format_ARGB32);
u8* buffer = Memory::GetPhysicalPointer(framebuffer_address);
for (unsigned int y = 0; y < framebuffer_height; ++y) {
for (unsigned int x = 0; x < framebuffer_width; ++x) {
const u32 coarse_y = y & ~7;
u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * framebuffer_width * bytes_per_pixel;
const u8* pixel = buffer + offset;
Math::Vec4<u8> color = { 0, 0, 0, 0 };
switch (framebuffer_format) {
case Format::RGBA8:
color = Color::DecodeRGBA8(pixel);
break;
case Format::RGB8:
color = Color::DecodeRGB8(pixel);
break;
case Format::RGB5A1:
color = Color::DecodeRGB5A1(pixel);
break;
case Format::RGB565:
color = Color::DecodeRGB565(pixel);
break;
case Format::RGBA4:
color = Color::DecodeRGBA4(pixel);
break;
case Format::D16:
{
u32 data = Color::DecodeD16(pixel);
color.r() = data & 0xFF;
color.g() = (data >> 8) & 0xFF;
break;
}
case Format::D24:
{
u32 data = Color::DecodeD24(pixel);
color.r() = data & 0xFF;
color.g() = (data >> 8) & 0xFF;
color.b() = (data >> 16) & 0xFF;
break;
}
case Format::D24X8:
{
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
color.r() = data.x & 0xFF;
color.g() = (data.x >> 8) & 0xFF;
color.b() = (data.x >> 16) & 0xFF;
break;
}
case Format::X24S8:
{
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
color.r() = color.g() = color.b() = data.y;
break;
}
default:
qDebug() << "Unknown fb color format " << static_cast<int>(framebuffer_format);
break;
}
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255));
}
}
pixmap = QPixmap::fromImage(decoded_image);
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);
}
u32 GraphicsFramebufferWidget::BytesPerPixel(GraphicsFramebufferWidget::Format format) {
switch (format) {
case Format::RGBA8:
case Format::D24X8:
case Format::X24S8:
return 4;
case Format::RGB8:
case Format::D24:
return 3;
case Format::RGB5A1:
case Format::RGB565:
case Format::RGBA4:
case Format::D16:
return 2;
default:
UNREACHABLE_MSG("GraphicsFramebufferWidget::BytesPerPixel: this "
"should not be reached as this function should "
"be given a format which is in "
"GraphicsFramebufferWidget::Format. Instead got %i",
static_cast<int>(format));
}
}

View File

@ -1,76 +0,0 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "citra_qt/debugger/graphics_breakpoint_observer.h"
class QComboBox;
class QLabel;
class QSpinBox;
class CSpinBox;
class GraphicsFramebufferWidget : public BreakPointObserverDock {
Q_OBJECT
using Event = Pica::DebugContext::Event;
enum class Source {
PicaTarget = 0,
DepthBuffer = 1,
Custom = 2,
// TODO: Add GPU framebuffer sources!
};
enum class Format {
RGBA8 = 0,
RGB8 = 1,
RGB5A1 = 2,
RGB565 = 3,
RGBA4 = 4,
D16 = 5,
D24 = 6,
D24X8 = 7,
X24S8 = 8,
Unknown = 9
};
static u32 BytesPerPixel(Format format);
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;
};

View File

@ -0,0 +1,736 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <QBoxLayout>
#include <QComboBox>
#include <QDebug>
#include <QFileDialog>
#include <QLabel>
#include <QMouseEvent>
#include <QPushButton>
#include <QScrollArea>
#include <QSpinBox>
#include "citra_qt/debugger/graphics_surface.h"
#include "citra_qt/util/spinbox.h"
#include "common/color.h"
#include "core/memory.h"
#include "core/hw/gpu.h"
#include "video_core/pica.h"
#include "video_core/pica_state.h"
#include "video_core/utils.h"
SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_) : QLabel(parent), surface_widget(surface_widget_) {}
SurfacePicture::~SurfacePicture() {}
void SurfacePicture::mousePressEvent(QMouseEvent* event)
{
// Only do something while the left mouse button is held down
if (!(event->buttons() & Qt::LeftButton))
return;
if (pixmap() == nullptr)
return;
if (surface_widget)
surface_widget->Pick(event->x() * pixmap()->width() / width(),
event->y() * pixmap()->height() / height());
}
void SurfacePicture::mouseMoveEvent(QMouseEvent* event)
{
// We also want to handle the event if the user moves the mouse while holding down the LMB
mousePressEvent(event);
}
GraphicsSurfaceWidget::GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context,
QWidget* parent)
: BreakPointObserverDock(debug_context, tr("Pica Surface Viewer"), parent),
surface_source(Source::ColorBuffer)
{
setObjectName("PicaSurface");
surface_source_list = new QComboBox;
surface_source_list->addItem(tr("Color Buffer"));
surface_source_list->addItem(tr("Depth Buffer"));
surface_source_list->addItem(tr("Stencil Buffer"));
surface_source_list->addItem(tr("Texture 0"));
surface_source_list->addItem(tr("Texture 1"));
surface_source_list->addItem(tr("Texture 2"));
surface_source_list->addItem(tr("Custom"));
surface_source_list->setCurrentIndex(static_cast<int>(surface_source));
surface_address_control = new CSpinBox;
surface_address_control->SetBase(16);
surface_address_control->SetRange(0, 0xFFFFFFFF);
surface_address_control->SetPrefix("0x");
unsigned max_dimension = 16384; // TODO: Find actual maximum
surface_width_control = new QSpinBox;
surface_width_control->setRange(0, max_dimension);
surface_height_control = new QSpinBox;
surface_height_control->setRange(0, max_dimension);
surface_picker_x_control = new QSpinBox;
surface_picker_x_control->setRange(0, max_dimension - 1);
surface_picker_y_control = new QSpinBox;
surface_picker_y_control->setRange(0, max_dimension - 1);
surface_format_control = new QComboBox;
// Color formats sorted by Pica texture format index
surface_format_control->addItem(tr("RGBA8"));
surface_format_control->addItem(tr("RGB8"));
surface_format_control->addItem(tr("RGB5A1"));
surface_format_control->addItem(tr("RGB565"));
surface_format_control->addItem(tr("RGBA4"));
surface_format_control->addItem(tr("IA8"));
surface_format_control->addItem(tr("RG8"));
surface_format_control->addItem(tr("I8"));
surface_format_control->addItem(tr("A8"));
surface_format_control->addItem(tr("IA4"));
surface_format_control->addItem(tr("I4"));
surface_format_control->addItem(tr("A4"));
surface_format_control->addItem(tr("ETC1"));
surface_format_control->addItem(tr("ETC1A4"));
surface_format_control->addItem(tr("D16"));
surface_format_control->addItem(tr("D24"));
surface_format_control->addItem(tr("D24X8"));
surface_format_control->addItem(tr("X24S8"));
surface_format_control->addItem(tr("Unknown"));
surface_info_label = new QLabel();
surface_info_label->setWordWrap(true);
surface_picture_label = new SurfacePicture(0, this);
surface_picture_label->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
surface_picture_label->setAlignment(Qt::AlignLeft | Qt::AlignTop);
surface_picture_label->setScaledContents(false);
auto scroll_area = new QScrollArea();
scroll_area->setBackgroundRole(QPalette::Dark);
scroll_area->setWidgetResizable(false);
scroll_area->setWidget(surface_picture_label);
save_surface = new QPushButton(QIcon::fromTheme("document-save"), tr("Save"));
// Connections
connect(this, SIGNAL(Update()), this, SLOT(OnUpdate()));
connect(surface_source_list, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceSourceChanged(int)));
connect(surface_address_control, SIGNAL(ValueChanged(qint64)), this, SLOT(OnSurfaceAddressChanged(qint64)));
connect(surface_width_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceWidthChanged(int)));
connect(surface_height_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfaceHeightChanged(int)));
connect(surface_format_control, SIGNAL(currentIndexChanged(int)), this, SLOT(OnSurfaceFormatChanged(int)));
connect(surface_picker_x_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerXChanged(int)));
connect(surface_picker_y_control, SIGNAL(valueChanged(int)), this, SLOT(OnSurfacePickerYChanged(int)));
connect(save_surface, SIGNAL(clicked()), this, SLOT(SaveSurface()));
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(surface_source_list);
main_layout->addLayout(sub_layout);
}
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("Physical Address:")));
sub_layout->addWidget(surface_address_control);
main_layout->addLayout(sub_layout);
}
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("Width:")));
sub_layout->addWidget(surface_width_control);
main_layout->addLayout(sub_layout);
}
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("Height:")));
sub_layout->addWidget(surface_height_control);
main_layout->addLayout(sub_layout);
}
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("Format:")));
sub_layout->addWidget(surface_format_control);
main_layout->addLayout(sub_layout);
}
main_layout->addWidget(scroll_area);
auto info_layout = new QHBoxLayout;
{
auto xy_layout = new QVBoxLayout;
{
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("X:")));
sub_layout->addWidget(surface_picker_x_control);
xy_layout->addLayout(sub_layout);
}
{
auto sub_layout = new QHBoxLayout;
sub_layout->addWidget(new QLabel(tr("Y:")));
sub_layout->addWidget(surface_picker_y_control);
xy_layout->addLayout(sub_layout);
}
}
info_layout->addLayout(xy_layout);
surface_info_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum);
info_layout->addWidget(surface_info_label);
}
main_layout->addLayout(info_layout);
main_layout->addWidget(save_surface);
main_widget->setLayout(main_layout);
setWidget(main_widget);
// Load current data - TODO: Make sure this works when emulation is not running
if (debug_context && debug_context->at_breakpoint) {
emit Update();
widget()->setEnabled(debug_context->at_breakpoint);
} else {
widget()->setEnabled(false);
}
}
void GraphicsSurfaceWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data)
{
emit Update();
widget()->setEnabled(true);
}
void GraphicsSurfaceWidget::OnResumed()
{
widget()->setEnabled(false);
}
void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value)
{
surface_source = static_cast<Source>(new_value);
emit Update();
}
void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value)
{
if (surface_address != new_value) {
surface_address = static_cast<unsigned>(new_value);
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
emit Update();
}
}
void GraphicsSurfaceWidget::OnSurfaceWidthChanged(int new_value)
{
if (surface_width != static_cast<unsigned>(new_value)) {
surface_width = static_cast<unsigned>(new_value);
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
emit Update();
}
}
void GraphicsSurfaceWidget::OnSurfaceHeightChanged(int new_value)
{
if (surface_height != static_cast<unsigned>(new_value)) {
surface_height = static_cast<unsigned>(new_value);
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
emit Update();
}
}
void GraphicsSurfaceWidget::OnSurfaceFormatChanged(int new_value)
{
if (surface_format != static_cast<Format>(new_value)) {
surface_format = static_cast<Format>(new_value);
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
emit Update();
}
}
void GraphicsSurfaceWidget::OnSurfacePickerXChanged(int new_value)
{
if (surface_picker_x != new_value) {
surface_picker_x = new_value;
Pick(surface_picker_x, surface_picker_y);
}
}
void GraphicsSurfaceWidget::OnSurfacePickerYChanged(int new_value)
{
if (surface_picker_y != new_value) {
surface_picker_y = new_value;
Pick(surface_picker_x, surface_picker_y);
}
}
void GraphicsSurfaceWidget::Pick(int x, int y)
{
surface_picker_x_control->setValue(x);
surface_picker_y_control->setValue(y);
if (x < 0 || x >= surface_width || y < 0 || y >= surface_height) {
surface_info_label->setText(tr("Pixel out of bounds"));
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
return;
}
u8* buffer = Memory::GetPhysicalPointer(surface_address);
if (buffer == nullptr) {
surface_info_label->setText(tr("(unable to access pixel data)"));
surface_info_label->setAlignment(Qt::AlignCenter);
return;
}
unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format);
unsigned stride = nibbles_per_pixel * surface_width / 2;
unsigned bytes_per_pixel;
bool nibble_mode = (nibbles_per_pixel == 1);
if (nibble_mode) {
// As nibbles are contained in a byte we still need to access one byte per nibble
bytes_per_pixel = 1;
} else {
bytes_per_pixel = nibbles_per_pixel / 2;
}
const u32 coarse_y = y & ~7;
u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
const u8* pixel = buffer + (nibble_mode ? (offset / 2) : offset);
auto GetText = [offset](Format format, const u8* pixel) {
switch (format) {
case Format::RGBA8:
{
auto value = Color::DecodeRGBA8(pixel) / 255.0f;
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2))
.arg(QString::number(value.b(), 'f', 2))
.arg(QString::number(value.a(), 'f', 2));
}
case Format::RGB8:
{
auto value = Color::DecodeRGB8(pixel) / 255.0f;
return QString("Red: %1, Green: %2, Blue: %3")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2))
.arg(QString::number(value.b(), 'f', 2));
}
case Format::RGB5A1:
{
auto value = Color::DecodeRGB5A1(pixel) / 255.0f;
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2))
.arg(QString::number(value.b(), 'f', 2))
.arg(QString::number(value.a(), 'f', 2));
}
case Format::RGB565:
{
auto value = Color::DecodeRGB565(pixel) / 255.0f;
return QString("Red: %1, Green: %2, Blue: %3")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2))
.arg(QString::number(value.b(), 'f', 2));
}
case Format::RGBA4:
{
auto value = Color::DecodeRGBA4(pixel) / 255.0f;
return QString("Red: %1, Green: %2, Blue: %3, Alpha: %4")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2))
.arg(QString::number(value.b(), 'f', 2))
.arg(QString::number(value.a(), 'f', 2));
}
case Format::IA8:
return QString("Index: %1, Alpha: %2")
.arg(pixel[0])
.arg(pixel[1]);
case Format::RG8: {
auto value = Color::DecodeRG8(pixel) / 255.0f;
return QString("Red: %1, Green: %2")
.arg(QString::number(value.r(), 'f', 2))
.arg(QString::number(value.g(), 'f', 2));
}
case Format::I8:
return QString("Index: %1").arg(*pixel);
case Format::A8:
return QString("Alpha: %1").arg(QString::number(*pixel / 255.0f, 'f', 2));
case Format::IA4:
return QString("Index: %1, Alpha: %2")
.arg(*pixel & 0xF)
.arg((*pixel & 0xF0) >> 4);
case Format::I4:
{
u8 i = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
return QString("Index: %1").arg(i);
}
case Format::A4:
{
u8 a = (*pixel >> ((offset % 2) ? 4 : 0)) & 0xF;
return QString("Alpha: %1").arg(QString::number(a / 15.0f, 'f', 2));
}
case Format::ETC1:
case Format::ETC1A4:
// TODO: Display block information or channel values?
return QString("Compressed data");
case Format::D16:
{
auto value = Color::DecodeD16(pixel);
return QString("Depth: %1").arg(QString::number(value / (float)0xFFFF, 'f', 4));
}
case Format::D24:
{
auto value = Color::DecodeD24(pixel);
return QString("Depth: %1").arg(QString::number(value / (float)0xFFFFFF, 'f', 4));
}
case Format::D24X8:
case Format::X24S8:
{
auto values = Color::DecodeD24S8(pixel);
return QString("Depth: %1, Stencil: %2").arg(QString::number(values[0] / (float)0xFFFFFF, 'f', 4)).arg(values[1]);
}
case Format::Unknown:
return QString("Unknown format");
default:
return QString("Unhandled format");
}
return QString("");
};
QString nibbles = "";
for (unsigned i = 0; i < nibbles_per_pixel; i++) {
unsigned nibble_index = i;
if (nibble_mode) {
nibble_index += (offset % 2) ? 0 : 1;
}
u8 byte = pixel[nibble_index / 2];
u8 nibble = (byte >> ((nibble_index % 2) ? 0 : 4)) & 0xF;
nibbles.append(QString::number(nibble, 16).toUpper());
}
surface_info_label->setText(QString("Raw: 0x%3\n(%4)").arg(nibbles).arg(GetText(surface_format, pixel)));
surface_info_label->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
}
void GraphicsSurfaceWidget::OnUpdate()
{
QPixmap pixmap;
switch (surface_source) {
case Source::ColorBuffer:
{
// TODO: Store a reference to the registers in the debug context instead of accessing them directly...
const auto& framebuffer = Pica::g_state.regs.framebuffer;
surface_address = framebuffer.GetColorBufferPhysicalAddress();
surface_width = framebuffer.GetWidth();
surface_height = framebuffer.GetHeight();
switch (framebuffer.color_format) {
case Pica::Regs::ColorFormat::RGBA8:
surface_format = Format::RGBA8;
break;
case Pica::Regs::ColorFormat::RGB8:
surface_format = Format::RGB8;
break;
case Pica::Regs::ColorFormat::RGB5A1:
surface_format = Format::RGB5A1;
break;
case Pica::Regs::ColorFormat::RGB565:
surface_format = Format::RGB565;
break;
case Pica::Regs::ColorFormat::RGBA4:
surface_format = Format::RGBA4;
break;
default:
surface_format = Format::Unknown;
break;
}
break;
}
case Source::DepthBuffer:
{
const auto& framebuffer = Pica::g_state.regs.framebuffer;
surface_address = framebuffer.GetDepthBufferPhysicalAddress();
surface_width = framebuffer.GetWidth();
surface_height = framebuffer.GetHeight();
switch (framebuffer.depth_format) {
case Pica::Regs::DepthFormat::D16:
surface_format = Format::D16;
break;
case Pica::Regs::DepthFormat::D24:
surface_format = Format::D24;
break;
case Pica::Regs::DepthFormat::D24S8:
surface_format = Format::D24X8;
break;
default:
surface_format = Format::Unknown;
break;
}
break;
}
case Source::StencilBuffer:
{
const auto& framebuffer = Pica::g_state.regs.framebuffer;
surface_address = framebuffer.GetDepthBufferPhysicalAddress();
surface_width = framebuffer.GetWidth();
surface_height = framebuffer.GetHeight();
switch (framebuffer.depth_format) {
case Pica::Regs::DepthFormat::D24S8:
surface_format = Format::X24S8;
break;
default:
surface_format = Format::Unknown;
break;
}
break;
}
case Source::Texture0:
case Source::Texture1:
case Source::Texture2:
{
unsigned texture_index;
if (surface_source == Source::Texture0) texture_index = 0;
else if (surface_source == Source::Texture1) texture_index = 1;
else if (surface_source == Source::Texture2) texture_index = 2;
else {
qDebug() << "Unknown texture source " << static_cast<int>(surface_source);
break;
}
const auto texture = Pica::g_state.regs.GetTextures()[texture_index];
auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format);
surface_address = info.physical_address;
surface_width = info.width;
surface_height = info.height;
surface_format = static_cast<Format>(info.format);
if (surface_format > Format::MaxTextureFormat) {
qDebug() << "Unknown texture format " << static_cast<int>(info.format);
}
break;
}
case Source::Custom:
{
// Keep user-specified values
break;
}
default:
qDebug() << "Unknown surface source " << static_cast<int>(surface_source);
break;
}
surface_address_control->SetValue(surface_address);
surface_width_control->setValue(surface_width);
surface_height_control->setValue(surface_height);
surface_format_control->setCurrentIndex(static_cast<int>(surface_format));
// TODO: Implement a good way to visualize alpha components!
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
u8* buffer = Memory::GetPhysicalPointer(surface_address);
if (buffer == nullptr) {
surface_picture_label->hide();
surface_info_label->setText(tr("(invalid surface address)"));
surface_info_label->setAlignment(Qt::AlignCenter);
surface_picker_x_control->setEnabled(false);
surface_picker_y_control->setEnabled(false);
save_surface->setEnabled(false);
return;
}
if (surface_format == Format::Unknown) {
surface_picture_label->hide();
surface_info_label->setText(tr("(unknown surface format)"));
surface_info_label->setAlignment(Qt::AlignCenter);
surface_picker_x_control->setEnabled(false);
surface_picker_y_control->setEnabled(false);
save_surface->setEnabled(false);
return;
}
surface_picture_label->show();
unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format);
unsigned stride = nibbles_per_pixel * surface_width / 2;
// We handle depth formats here because DebugUtils only supports TextureFormats
if (surface_format <= Format::MaxTextureFormat) {
// Generate a virtual texture
Pica::DebugUtils::TextureInfo info;
info.physical_address = surface_address;
info.width = surface_width;
info.height = surface_height;
info.format = static_cast<Pica::Regs::TextureFormat>(surface_format);
info.stride = stride;
for (unsigned int y = 0; y < surface_height; ++y) {
for (unsigned int x = 0; x < surface_width; ++x) {
Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(buffer, x, y, info, true);
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
}
}
} else {
ASSERT_MSG(nibbles_per_pixel >= 2, "Depth decoder only supports formats with at least one byte per pixel");
unsigned bytes_per_pixel = nibbles_per_pixel / 2;
for (unsigned int y = 0; y < surface_height; ++y) {
for (unsigned int x = 0; x < surface_width; ++x) {
const u32 coarse_y = y & ~7;
u32 offset = VideoCore::GetMortonOffset(x, y, bytes_per_pixel) + coarse_y * stride;
const u8* pixel = buffer + offset;
Math::Vec4<u8> color = { 0, 0, 0, 0 };
switch(surface_format) {
case Format::D16:
{
u32 data = Color::DecodeD16(pixel);
color.r() = data & 0xFF;
color.g() = (data >> 8) & 0xFF;
break;
}
case Format::D24:
{
u32 data = Color::DecodeD24(pixel);
color.r() = data & 0xFF;
color.g() = (data >> 8) & 0xFF;
color.b() = (data >> 16) & 0xFF;
break;
}
case Format::D24X8:
{
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
color.r() = data.x & 0xFF;
color.g() = (data.x >> 8) & 0xFF;
color.b() = (data.x >> 16) & 0xFF;
break;
}
case Format::X24S8:
{
Math::Vec2<u32> data = Color::DecodeD24S8(pixel);
color.r() = color.g() = color.b() = data.y;
break;
}
default:
qDebug() << "Unknown surface format " << static_cast<int>(surface_format);
break;
}
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), 255));
}
}
}
pixmap = QPixmap::fromImage(decoded_image);
surface_picture_label->setPixmap(pixmap);
surface_picture_label->resize(pixmap.size());
// Update the info with pixel data
surface_picker_x_control->setEnabled(true);
surface_picker_y_control->setEnabled(true);
Pick(surface_picker_x, surface_picker_y);
// Enable saving the converted pixmap to file
save_surface->setEnabled(true);
}
void GraphicsSurfaceWidget::SaveSurface() {
QString png_filter = tr("Portable Network Graphic (*.png)");
QString bin_filter = tr("Binary data (*.bin)");
QString selectedFilter;
QString filename = QFileDialog::getSaveFileName(this, tr("Save Surface"), QString("texture-0x%1.png").arg(QString::number(surface_address, 16)),
QString("%1;;%2").arg(png_filter, bin_filter), &selectedFilter);
if (filename.isEmpty()) {
// If the user canceled the dialog, don't save anything.
return;
}
if (selectedFilter == png_filter) {
const QPixmap* pixmap = surface_picture_label->pixmap();
ASSERT_MSG(pixmap != nullptr, "No pixmap set");
QFile file(filename);
file.open(QIODevice::WriteOnly);
if (pixmap)
pixmap->save(&file, "PNG");
} else if (selectedFilter == bin_filter) {
const u8* buffer = Memory::GetPhysicalPointer(surface_address);
ASSERT_MSG(buffer != nullptr, "Memory not accessible");
QFile file(filename);
file.open(QIODevice::WriteOnly);
int size = surface_width * surface_height * NibblesPerPixel(surface_format) / 2;
QByteArray data(reinterpret_cast<const char*>(buffer), size);
file.write(data);
} else {
UNREACHABLE_MSG("Unhandled filter selected");
}
}
unsigned int GraphicsSurfaceWidget::NibblesPerPixel(GraphicsSurfaceWidget::Format format) {
if (format <= Format::MaxTextureFormat) {
return Pica::Regs::NibblesPerPixel(static_cast<Pica::Regs::TextureFormat>(format));
}
switch (format) {
case Format::D24X8:
case Format::X24S8:
return 4 * 2;
case Format::D24:
return 3 * 2;
case Format::D16:
return 2 * 2;
default:
UNREACHABLE_MSG("GraphicsSurfaceWidget::BytesPerPixel: this "
"should not be reached as this function should "
"be given a format which is in "
"GraphicsSurfaceWidget::Format. Instead got %i",
static_cast<int>(format));
return 0;
}
}

View File

@ -0,0 +1,120 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "citra_qt/debugger/graphics_breakpoint_observer.h"
#include <QLabel>
#include <QPushButton>
class QComboBox;
class QSpinBox;
class CSpinBox;
class GraphicsSurfaceWidget;
class SurfacePicture : public QLabel
{
Q_OBJECT
public:
SurfacePicture(QWidget* parent = 0, GraphicsSurfaceWidget* surface_widget = nullptr);
~SurfacePicture();
protected slots:
virtual void mouseMoveEvent(QMouseEvent* event);
virtual void mousePressEvent(QMouseEvent* event);
private:
GraphicsSurfaceWidget* surface_widget;
};
class GraphicsSurfaceWidget : public BreakPointObserverDock {
Q_OBJECT
using Event = Pica::DebugContext::Event;
enum class Source {
ColorBuffer = 0,
DepthBuffer = 1,
StencilBuffer = 2,
Texture0 = 3,
Texture1 = 4,
Texture2 = 5,
Custom = 6,
};
enum class Format {
// These must match the TextureFormat type!
RGBA8 = 0,
RGB8 = 1,
RGB5A1 = 2,
RGB565 = 3,
RGBA4 = 4,
IA8 = 5,
RG8 = 6, ///< @note Also called HILO8 in 3DBrew.
I8 = 7,
A8 = 8,
IA4 = 9,
I4 = 10,
A4 = 11,
ETC1 = 12, // compressed
ETC1A4 = 13,
MaxTextureFormat = 13,
D16 = 14,
D24 = 15,
D24X8 = 16,
X24S8 = 17,
Unknown = 18,
};
static unsigned int NibblesPerPixel(Format format);
public:
GraphicsSurfaceWidget(std::shared_ptr<Pica::DebugContext> debug_context, QWidget* parent = nullptr);
void Pick(int x, int y);
public slots:
void OnSurfaceSourceChanged(int new_value);
void OnSurfaceAddressChanged(qint64 new_value);
void OnSurfaceWidthChanged(int new_value);
void OnSurfaceHeightChanged(int new_value);
void OnSurfaceFormatChanged(int new_value);
void OnSurfacePickerXChanged(int new_value);
void OnSurfacePickerYChanged(int new_value);
void OnUpdate();
private slots:
void OnBreakPointHit(Pica::DebugContext::Event event, void* data) override;
void OnResumed() override;
void SaveSurface();
signals:
void Update();
private:
QComboBox* surface_source_list;
CSpinBox* surface_address_control;
QSpinBox* surface_width_control;
QSpinBox* surface_height_control;
QComboBox* surface_format_control;
SurfacePicture* surface_picture_label;
QSpinBox* surface_picker_x_control;
QSpinBox* surface_picker_y_control;
QLabel* surface_info_label;
QPushButton* save_surface;
Source surface_source;
unsigned surface_address;
unsigned surface_width;
unsigned surface_height;
Format surface_format;
int surface_picker_x = 0;
int surface_picker_y = 0;
};

View File

@ -118,46 +118,33 @@ void GameList::LoadInterfaceLayout()
item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
}
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan)
void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion)
{
const auto callback = [&](unsigned* num_entries_out,
const std::string& directory,
const std::string& virtual_name) -> bool {
const std::string& virtual_name,
unsigned int recursion) -> bool {
std::string physical_name = directory + DIR_SEP + virtual_name;
if (stop_processing)
return false; // Breaks the callback loop.
if (deep_scan && FileUtil::IsDirectory(physical_name)) {
AddFstEntriesToGameList(physical_name, true);
} else {
std::string filename_filename, filename_extension;
Common::SplitPath(physical_name, nullptr, &filename_filename, &filename_extension);
Loader::FileType guessed_filetype = Loader::GuessFromExtension(filename_extension);
if (guessed_filetype == Loader::FileType::Unknown)
if (!FileUtil::IsDirectory(physical_name)) {
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
if (!loader)
return true;
Loader::FileType filetype = Loader::IdentifyFile(physical_name);
if (filetype == Loader::FileType::Unknown) {
LOG_WARNING(Frontend, "File %s is of indeterminate type and is possibly corrupted.", physical_name.c_str());
return true;
}
if (guessed_filetype != filetype) {
LOG_WARNING(Frontend, "Filetype and extension of file %s do not match.", physical_name.c_str());
}
std::vector<u8> smdh;
std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(FileUtil::IOFile(physical_name, "rb"), filetype, filename_filename, physical_name);
if (loader)
loader->ReadIcon(smdh);
emit EntryReady({
new GameListItemPath(QString::fromStdString(physical_name), smdh),
new GameListItem(QString::fromStdString(Loader::GetFileTypeString(filetype))),
new GameListItem(QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
new GameListItemSize(FileUtil::GetSize(physical_name)),
});
} else if (recursion > 0) {
AddFstEntriesToGameList(physical_name, recursion - 1);
}
return true;
@ -169,7 +156,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool d
void GameListWorker::run()
{
stop_processing = false;
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan);
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
emit Finished();
}

View File

@ -15,52 +15,21 @@
#include "common/string_util.h"
#include "common/color.h"
#include "core/loader/loader.h"
#include "core/loader/smdh.h"
#include "video_core/utils.h"
/**
* Tests if data is a valid SMDH by its length and magic number.
* @param smdh_data data buffer to test
* @return bool test result
*/
static bool IsValidSMDH(const std::vector<u8>& smdh_data) {
if (smdh_data.size() < sizeof(Loader::SMDH))
return false;
u32 magic;
memcpy(&magic, smdh_data.data(), 4);
return Loader::MakeMagic('S', 'M', 'D', 'H') == magic;
}
/**
* Gets game icon from SMDH
* @param sdmh SMDH data
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
* @return QPixmap game icon
*/
static QPixmap GetIconFromSMDH(const Loader::SMDH& smdh, bool large) {
u32 size;
const u8* icon_data;
if (large) {
size = 48;
icon_data = smdh.large_icon.data();
} else {
size = 24;
icon_data = smdh.small_icon.data();
}
QImage icon(size, size, QImage::Format::Format_RGB888);
for (u32 x = 0; x < size; ++x) {
for (u32 y = 0; y < size; ++y) {
u32 coarse_y = y & ~7;
auto v = Color::DecodeRGB565(
icon_data + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * size * 2);
icon.setPixel(x, y, qRgb(v.r(), v.g(), v.b()));
}
}
static QPixmap GetQPixmapFromSMDH(const Loader::SMDH& smdh, bool large) {
std::vector<u16> icon_data = smdh.GetIcon(large);
const uchar* data = reinterpret_cast<const uchar*>(icon_data.data());
int size = large ? 48 : 24;
QImage icon(data, size, size, QImage::Format::Format_RGB16);
return QPixmap::fromImage(icon);
}
@ -82,8 +51,8 @@ static QPixmap GetDefaultIcon(bool large) {
* @param language title language
* @return QString short title
*/
static QString GetShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) {
return QString::fromUtf16(smdh.titles[static_cast<int>(language)].short_title.data());
static QString GetQStringShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) {
return QString::fromUtf16(smdh.GetShortTitle(language).data());
}
class GameListItem : public QStandardItem {
@ -112,7 +81,7 @@ public:
{
setData(game_path, FullPathRole);
if (!IsValidSMDH(smdh_data)) {
if (!Loader::IsValidSMDH(smdh_data)) {
// SMDH is not valid, set a default icon
setData(GetDefaultIcon(true), Qt::DecorationRole);
return;
@ -122,10 +91,10 @@ public:
memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
// Get icon from SMDH
setData(GetIconFromSMDH(smdh, true), Qt::DecorationRole);
setData(GetQPixmapFromSMDH(smdh, true), Qt::DecorationRole);
// Get title form SMDH
setData(GetShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole);
setData(GetQStringShortTitleFromSMDH(smdh, Loader::SMDH::TitleLanguage::English), TitleRole);
}
QVariant data(int role) const override {
@ -212,5 +181,5 @@ private:
bool deep_scan;
std::atomic_bool stop_processing;
void AddFstEntriesToGameList(const std::string& dir_path, bool deep_scan);
void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
};

View File

@ -29,7 +29,7 @@
#include "citra_qt/debugger/graphics.h"
#include "citra_qt/debugger/graphics_breakpoints.h"
#include "citra_qt/debugger/graphics_cmdlists.h"
#include "citra_qt/debugger/graphics_framebuffer.h"
#include "citra_qt/debugger/graphics_surface.h"
#include "citra_qt/debugger/graphics_tracing.h"
#include "citra_qt/debugger/graphics_vertex_shader.h"
#include "citra_qt/debugger/profiler.h"
@ -101,10 +101,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
graphicsBreakpointsWidget->hide();
auto graphicsFramebufferWidget = new GraphicsFramebufferWidget(Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsFramebufferWidget);
graphicsFramebufferWidget->hide();
auto graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget);
graphicsVertexShaderWidget->hide();
@ -113,7 +109,12 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget);
graphicsTracingWidget->hide();
auto graphicsSurfaceViewerAction = new QAction(tr("Create Pica surface viewer"), this);
connect(graphicsSurfaceViewerAction, SIGNAL(triggered()), this, SLOT(OnCreateGraphicsSurfaceViewer()));
QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));
debug_menu->addAction(graphicsSurfaceViewerAction);
debug_menu->addSeparator();
debug_menu->addAction(profilerWidget->toggleViewAction());
#if MICROPROFILE_ENABLED
debug_menu->addAction(microProfileDialog->toggleViewAction());
@ -124,7 +125,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
debug_menu->addAction(graphicsWidget->toggleViewAction());
debug_menu->addAction(graphicsCommandsWidget->toggleViewAction());
debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction());
debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction());
debug_menu->addAction(graphicsTracingWidget->toggleViewAction());
@ -272,7 +272,15 @@ bool GMainWindow::InitializeSystem() {
}
bool GMainWindow::LoadROM(const std::string& filename) {
Loader::ResultStatus result = Loader::LoadFile(filename);
std::unique_ptr<Loader::AppLoader> app_loader = Loader::GetLoader(filename);
if (!app_loader) {
LOG_CRITICAL(Frontend, "Failed to obtain loader for %s!", filename.c_str());
QMessageBox::critical(this, tr("Error while loading ROM!"),
tr("The ROM format is not supported."));
return false;
}
Loader::ResultStatus result = app_loader->Load();
if (Loader::ResultStatus::Success != result) {
LOG_CRITICAL(Frontend, "Failed to load ROM!");
System::Shutdown();
@ -509,6 +517,13 @@ void GMainWindow::OnConfigure() {
}
}
void GMainWindow::OnCreateGraphicsSurfaceViewer() {
auto graphicsSurfaceViewerWidget = new GraphicsSurfaceWidget(Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsSurfaceViewerWidget);
// TODO: Maybe graphicsSurfaceViewerWidget->setFloating(true);
graphicsSurfaceViewerWidget->show();
}
bool GMainWindow::ConfirmClose() {
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
return true;

View File

@ -108,6 +108,7 @@ private slots:
void OnConfigure();
void OnDisplayTitleBars(bool);
void ToggleWindowMode();
void OnCreateGraphicsSurfaceViewer();
private:
Ui::MainWindow ui;

View File

@ -72,18 +72,24 @@ inline u64 _rotr64(u64 x, unsigned int shift){
}
#else // _MSC_VER
#if (_MSC_VER < 1900)
#if (_MSC_VER < 1900)
// Function Cross-Compatibility
#define snprintf _snprintf
#endif
#endif
// Locale Cross-Compatibility
#define locale_t _locale_t
// Locale Cross-Compatibility
#define locale_t _locale_t
extern "C" {
extern "C" {
__declspec(dllimport) void __stdcall DebugBreak(void);
}
#define Crash() {DebugBreak();}
}
#define Crash() {DebugBreak();}
// cstdlib provides these on MSVC
#define rotr _rotr
#define rotl _rotl
#endif // _MSC_VER ndef
// Generic function to get last error message.

View File

@ -11,12 +11,28 @@
#include "emu_window.h"
#include "video_core/video_core.h"
void EmuWindow::KeyPressed(KeyMap::HostDeviceKey key) {
pad_state.hex |= KeyMap::GetPadKey(key).hex;
void EmuWindow::ButtonPressed(Service::HID::PadState pad) {
pad_state.hex |= pad.hex;
}
void EmuWindow::KeyReleased(KeyMap::HostDeviceKey key) {
pad_state.hex &= ~KeyMap::GetPadKey(key).hex;
void EmuWindow::ButtonReleased(Service::HID::PadState pad) {
pad_state.hex &= ~pad.hex;
}
void EmuWindow::CirclePadUpdated(float x, float y) {
constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position
// Make sure the coordinates are in the unit circle,
// otherwise normalize it.
float r = x * x + y * y;
if (r > 1) {
r = std::sqrt(r);
x /= r;
y /= r;
}
circle_pad_x = static_cast<s16>(x * MAX_CIRCLEPAD_POS);
circle_pad_y = static_cast<s16>(y * MAX_CIRCLEPAD_POS);
}
/**

View File

@ -12,10 +12,6 @@
#include "core/hle/service/hid/hid.h"
namespace KeyMap {
struct HostDeviceKey;
}
/**
* Abstraction class used to provide an interface between emulation code and the frontend
* (e.g. SDL, QGLWidget, GLFW, etc...).
@ -76,11 +72,27 @@ public:
virtual void ReloadSetKeymaps() = 0;
/// Signals a key press action to the HID module
void KeyPressed(KeyMap::HostDeviceKey key);
/**
* Signals a button press action to the HID module.
* @param pad_state indicates which button to press
* @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
*/
void ButtonPressed(Service::HID::PadState pad_state);
/// Signals a key release action to the HID module
void KeyReleased(KeyMap::HostDeviceKey key);
/**
* Signals a button release action to the HID module.
* @param pad_state indicates which button to press
* @note only handles real buttons (A/B/X/Y/...), excluding analog inputs like the circle pad.
*/
void ButtonReleased(Service::HID::PadState pad_state);
/**
* Signals a circle pad change action to the HID module.
* @param x new x-coordinate of the circle pad, in the range [-1.0, 1.0]
* @param y new y-coordinate of the circle pad, in the range [-1.0, 1.0]
* @note the coordinates will be normalized if the radius is larger than 1
*/
void CirclePadUpdated(float x, float y);
/**
* Signal that a touch pressed event has occurred (e.g. mouse click pressed)
@ -100,8 +112,9 @@ public:
void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
/**
* Gets the current pad state (which buttons are pressed and the circle pad direction).
* Gets the current pad state (which buttons are pressed).
* @note This should be called by the core emu thread to get a state set by the window thread.
* @note This doesn't include analog input like circle pad direction
* @todo Fix this function to be thread-safe.
* @return PadState object indicating the current pad state
*/
@ -109,6 +122,16 @@ public:
return pad_state;
}
/**
* Gets the current circle pad state.
* @note This should be called by the core emu thread to get a state set by the window thread.
* @todo Fix this function to be thread-safe.
* @return std::tuple of (x, y), where `x` and `y` are the circle pad coordinates
*/
std::tuple<s16, s16> GetCirclePadState() const {
return std::make_tuple(circle_pad_x, circle_pad_y);
}
/**
* Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
* @note This should be called by the core emu thread to get a state set by the window thread.
@ -200,6 +223,8 @@ protected:
pad_state.hex = 0;
touch_x = 0;
touch_y = 0;
circle_pad_x = 0;
circle_pad_y = 0;
touch_pressed = false;
}
virtual ~EmuWindow() {}
@ -260,6 +285,9 @@ private:
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156)
s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156)
/**
* Clip the provided coordinates to be inside the touchscreen area.
*/

View File

@ -434,7 +434,7 @@ bool CreateEmptyFile(const std::string &filename)
}
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback)
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback, unsigned int recursion)
{
LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
@ -472,7 +472,7 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
continue;
unsigned ret_entries = 0;
if (!callback(&ret_entries, directory, virtual_name)) {
if (!callback(&ret_entries, directory, virtual_name, recursion)) {
callback_error = true;
break;
}
@ -486,30 +486,34 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
closedir(dirp);
#endif
if (!callback_error) {
if (callback_error)
return false;
// num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it
if (num_entries_out != nullptr)
*num_entries_out = found_entries;
return true;
} else {
return false;
}
}
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry)
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry, unsigned int recursion)
{
const auto callback = [&parent_entry](unsigned* num_entries_out,
const std::string& directory,
const std::string& virtual_name) -> bool {
const std::string& virtual_name,
unsigned int recursion) -> bool {
FSTEntry entry;
entry.virtualName = virtual_name;
entry.physicalName = directory + DIR_SEP + virtual_name;
if (IsDirectory(entry.physicalName)) {
entry.isDirectory = true;
// is a directory, lets go inside
entry.size = ScanDirectoryTree(entry.physicalName, entry);
// is a directory, lets go inside if we didn't recurse to often
if (recursion > 0) {
entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1);
*num_entries_out += (int)entry.size;
} else {
entry.size = 0;
}
} else { // is a file
entry.isDirectory = false;
entry.size = GetSize(entry.physicalName);
@ -522,23 +526,27 @@ unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry)
};
unsigned num_entries;
return ForeachDirectoryEntry(&num_entries, directory, callback) ? num_entries : 0;
return ForeachDirectoryEntry(&num_entries, directory, callback, recursion) ? num_entries : 0;
}
bool DeleteDirRecursively(const std::string &directory)
bool DeleteDirRecursively(const std::string &directory, unsigned int recursion)
{
const static auto callback = [](unsigned* num_entries_out,
const std::string& directory,
const std::string& virtual_name) -> bool {
const std::string& virtual_name,
unsigned int recursion) -> bool {
std::string new_path = directory + DIR_SEP_CHR + virtual_name;
if (IsDirectory(new_path))
return DeleteDirRecursively(new_path);
if (IsDirectory(new_path)) {
if (recursion == 0)
return false;
return DeleteDirRecursively(new_path, recursion - 1);
}
return Delete(new_path);
};
if (!ForeachDirectoryEntry(nullptr, directory, callback))
if (!ForeachDirectoryEntry(nullptr, directory, callback, recursion))
return false;
// Delete the outermost directory

View File

@ -105,11 +105,13 @@ bool CreateEmptyFile(const std::string &filename);
* @param num_entries_out to be assigned by the callable with the number of iterated directory entries, never null
* @param directory the path to the enclosing directory
* @param virtual_name the entry name, without any preceding directory info
* @param recursion Number of children directory to read before giving up
* @return whether handling the entry succeeded
*/
using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out,
const std::string& directory,
const std::string& virtual_name)>;
const std::string& virtual_name,
unsigned int recursion)>;
/**
* Scans a directory, calling the callback for each file/directory contained within.
@ -117,20 +119,22 @@ using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out,
* @param num_entries_out assigned by the function with the number of iterated directory entries, can be null
* @param directory the directory to scan
* @param callback The callback which will be called for each entry
* @param recursion Number of children directories to read before giving up
* @return whether scanning the directory succeeded
*/
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback);
bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directory, DirectoryEntryCallable callback, unsigned int recursion = 0);
/**
* Scans the directory tree, storing the results.
* @param directory the parent directory to start scanning from
* @param parent_entry FSTEntry where the filesystem tree results will be stored.
* @param recursion Number of children directories to read before giving up.
* @return the total number of files/directories found
*/
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry);
unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry, unsigned int recursion = 0);
// deletes the given directory and anything under it. Returns true on success.
bool DeleteDirRecursively(const std::string &directory);
bool DeleteDirRecursively(const std::string &directory, unsigned int recursion = 256);
// Returns the current directory
std::string GetCurrentDir();

View File

@ -2,24 +2,138 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "key_map.h"
#include <map>
#include "common/emu_window.h"
#include "common/key_map.h"
namespace KeyMap {
static std::map<HostDeviceKey, Service::HID::PadState> key_map;
// TODO (wwylele): currently we treat c-stick as four direction buttons
// and map it directly to EmuWindow::ButtonPressed.
// It should go the analog input way like circle pad does.
const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets = {{
Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y,
Service::HID::PAD_L, Service::HID::PAD_R, Service::HID::PAD_ZL, Service::HID::PAD_ZR,
Service::HID::PAD_START, Service::HID::PAD_SELECT, Service::HID::PAD_NONE,
Service::HID::PAD_UP, Service::HID::PAD_DOWN, Service::HID::PAD_LEFT, Service::HID::PAD_RIGHT,
Service::HID::PAD_C_UP, Service::HID::PAD_C_DOWN, Service::HID::PAD_C_LEFT, Service::HID::PAD_C_RIGHT,
IndirectTarget::CirclePadUp,
IndirectTarget::CirclePadDown,
IndirectTarget::CirclePadLeft,
IndirectTarget::CirclePadRight,
IndirectTarget::CirclePadModifier,
}};
static std::map<HostDeviceKey, KeyTarget> key_map;
static int next_device_id = 0;
static bool circle_pad_up = false;
static bool circle_pad_down = false;
static bool circle_pad_left = false;
static bool circle_pad_right = false;
static bool circle_pad_modifier = false;
static void UpdateCirclePad(EmuWindow& emu_window) {
constexpr float SQRT_HALF = 0.707106781;
int x = 0, y = 0;
if (circle_pad_right)
++x;
if (circle_pad_left)
--x;
if (circle_pad_up)
++y;
if (circle_pad_down)
--y;
float modifier = circle_pad_modifier ? Settings::values.pad_circle_modifier_scale : 1.0;
emu_window.CirclePadUpdated(x * modifier * (y == 0 ? 1.0 : SQRT_HALF), y * modifier * (x == 0 ? 1.0 : SQRT_HALF));
}
int NewDeviceId() {
return next_device_id++;
}
void SetKeyMapping(HostDeviceKey key, Service::HID::PadState padState) {
key_map[key].hex = padState.hex;
void SetKeyMapping(HostDeviceKey key, KeyTarget target) {
key_map[key] = target;
}
Service::HID::PadState GetPadKey(HostDeviceKey key) {
return key_map[key];
void ClearKeyMapping(int device_id) {
auto iter = key_map.begin();
while (iter != key_map.end()) {
if (iter->first.device_id == device_id)
key_map.erase(iter++);
else
++iter;
}
}
void PressKey(EmuWindow& emu_window, HostDeviceKey key) {
auto target = key_map.find(key);
if (target == key_map.end())
return;
if (target->second.direct) {
emu_window.ButtonPressed({{target->second.target.direct_target_hex}});
} else {
switch (target->second.target.indirect_target) {
case IndirectTarget::CirclePadUp:
circle_pad_up = true;
UpdateCirclePad(emu_window);
break;
case IndirectTarget::CirclePadDown:
circle_pad_down = true;
UpdateCirclePad(emu_window);
break;
case IndirectTarget::CirclePadLeft:
circle_pad_left = true;
UpdateCirclePad(emu_window);
break;
case IndirectTarget::CirclePadRight:
circle_pad_right = true;
UpdateCirclePad(emu_window);
break;
case IndirectTarget::CirclePadModifier:
circle_pad_modifier = true;
UpdateCirclePad(emu_window);
break;
}
}
}
void ReleaseKey(EmuWindow& emu_window,HostDeviceKey key) {
auto target = key_map.find(key);
if (target == key_map.end())
return;
if (target->second.direct) {
emu_window.ButtonReleased({{target->second.target.direct_target_hex}});
} else {
switch (target->second.target.indirect_target) {
case IndirectTarget::CirclePadUp:
circle_pad_up = false;
UpdateCirclePad(emu_window);
break;
case IndirectTarget::CirclePadDown:
circle_pad_down = false;
UpdateCirclePad(emu_window);
break;
case IndirectTarget::CirclePadLeft:
circle_pad_left = false;
UpdateCirclePad(emu_window);
break;
case IndirectTarget::CirclePadRight:
circle_pad_right = false;
UpdateCirclePad(emu_window);
break;
case IndirectTarget::CirclePadModifier:
circle_pad_modifier = false;
UpdateCirclePad(emu_window);
break;
}
}
}
}

View File

@ -4,11 +4,50 @@
#pragma once
#include <array>
#include <tuple>
#include "core/hle/service/hid/hid.h"
class EmuWindow;
namespace KeyMap {
/**
* Represents key mapping targets that are not real 3DS buttons.
* They will be handled by KeyMap and translated to 3DS input.
*/
enum class IndirectTarget {
CirclePadUp,
CirclePadDown,
CirclePadLeft,
CirclePadRight,
CirclePadModifier,
};
/**
* Represents a key mapping target. It can be a PadState that represents real 3DS buttons,
* or an IndirectTarget.
*/
struct KeyTarget {
bool direct;
union {
u32 direct_target_hex;
IndirectTarget indirect_target;
} target;
KeyTarget() : direct(true) {
target.direct_target_hex = 0;
}
KeyTarget(Service::HID::PadState pad) : direct(true) {
target.direct_target_hex = pad.hex;
}
KeyTarget(IndirectTarget i) : direct(false) {
target.indirect_target = i;
}
};
/**
* Represents a key for a specific host device.
*/
@ -27,19 +66,31 @@ struct HostDeviceKey {
}
};
extern const std::array<KeyTarget, Settings::NativeInput::NUM_INPUTS> mapping_targets;
/**
* Generates a new device id, which uniquely identifies a host device within KeyMap.
*/
int NewDeviceId();
/**
* Maps a device-specific key to a PadState.
* Maps a device-specific key to a target (a PadState or an IndirectTarget).
*/
void SetKeyMapping(HostDeviceKey key, Service::HID::PadState padState);
void SetKeyMapping(HostDeviceKey key, KeyTarget target);
/**
* Gets the PadState that's mapped to the provided device-specific key.
* Clears all key mappings belonging to one device.
*/
Service::HID::PadState GetPadKey(HostDeviceKey key);
void ClearKeyMapping(int device_id);
/**
* Maps a key press action and call the corresponding function in EmuWindow
*/
void PressKey(EmuWindow& emu_window, HostDeviceKey key);
/**
* Maps a key release action and call the corresponding function in EmuWindow
*/
void ReleaseKey(EmuWindow& emu_window, HostDeviceKey key);
}

View File

@ -70,7 +70,10 @@ set(SRCS
hle/service/cfg/cfg_s.cpp
hle/service/cfg/cfg_u.cpp
hle/service/csnd_snd.cpp
hle/service/dlp_srvr.cpp
hle/service/dlp/dlp.cpp
hle/service/dlp/dlp_clnt.cpp
hle/service/dlp/dlp_fkcl.cpp
hle/service/dlp/dlp_srvr.cpp
hle/service/dsp_dsp.cpp
hle/service/err_f.cpp
hle/service/frd/frd.cpp
@ -121,6 +124,7 @@ set(SRCS
loader/elf.cpp
loader/loader.cpp
loader/ncch.cpp
loader/smdh.cpp
tracer/recorder.cpp
memory.cpp
settings.cpp
@ -205,7 +209,10 @@ set(HEADERS
hle/service/cfg/cfg_s.h
hle/service/cfg/cfg_u.h
hle/service/csnd_snd.h
hle/service/dlp_srvr.h
hle/service/dlp/dlp.h
hle/service/dlp/dlp_clnt.h
hle/service/dlp/dlp_fkcl.h
hle/service/dlp/dlp_srvr.h
hle/service/dsp_dsp.h
hle/service/err_f.h
hle/service/frd/frd.h
@ -256,6 +263,7 @@ set(HEADERS
loader/elf.h
loader/loader.h
loader/ncch.h
loader/smdh.h
tracer/recorder.h
tracer/citrace.h
memory.h

View File

@ -422,6 +422,10 @@ ARMDecodeStatus DecodeARMInstruction(u32 instr, s32* idx) {
n = arm_instruction[i].attribute_value;
base = 0;
// 3DS has no VFP3 support
if (arm_instruction[i].version == ARMVFP3)
continue;
while (n) {
if (arm_instruction[i].content[base + 1] == 31 && arm_instruction[i].content[base] == 0) {
// clrex

View File

@ -271,8 +271,9 @@ inline int vfp_single_type(const vfp_single* s)
// Unpack a single-precision float. Note that this returns the magnitude
// of the single-precision float mantissa with the 1. if necessary,
// aligned to bit 30.
inline void vfp_single_unpack(vfp_single* s, s32 val, u32* fpscr)
inline u32 vfp_single_unpack(vfp_single* s, s32 val, u32 fpscr)
{
u32 exceptions = 0;
s->sign = vfp_single_packed_sign(val) >> 16,
s->exponent = vfp_single_packed_exponent(val);
@ -283,12 +284,13 @@ inline void vfp_single_unpack(vfp_single* s, s32 val, u32* fpscr)
// If flush-to-zero mode is enabled, turn the denormal into zero.
// On a VFPv2 architecture, the sign of the zero is always positive.
if ((*fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_single_type(s) & VFP_DENORMAL) != 0) {
if ((fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_single_type(s) & VFP_DENORMAL) != 0) {
s->sign = 0;
s->exponent = 0;
s->significand = 0;
*fpscr |= FPSCR_IDC;
exceptions |= FPSCR_IDC;
}
return exceptions;
}
// Re-pack a single-precision float. This assumes that the float is
@ -302,7 +304,7 @@ inline s32 vfp_single_pack(const vfp_single* s)
}
u32 vfp_single_normaliseround(ARMul_State* state, int sd, vfp_single* vs, u32 fpscr, u32 exceptions, const char* func);
u32 vfp_single_normaliseround(ARMul_State* state, int sd, vfp_single* vs, u32 fpscr, const char* func);
// Double-precision
struct vfp_double {
@ -357,8 +359,9 @@ inline int vfp_double_type(const vfp_double* s)
// Unpack a double-precision float. Note that this returns the magnitude
// of the double-precision float mantissa with the 1. if necessary,
// aligned to bit 62.
inline void vfp_double_unpack(vfp_double* s, s64 val, u32* fpscr)
inline u32 vfp_double_unpack(vfp_double* s, s64 val, u32 fpscr)
{
u32 exceptions = 0;
s->sign = vfp_double_packed_sign(val) >> 48;
s->exponent = vfp_double_packed_exponent(val);
@ -369,12 +372,13 @@ inline void vfp_double_unpack(vfp_double* s, s64 val, u32* fpscr)
// If flush-to-zero mode is enabled, turn the denormal into zero.
// On a VFPv2 architecture, the sign of the zero is always positive.
if ((*fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_double_type(s) & VFP_DENORMAL) != 0) {
if ((fpscr & FPSCR_FLUSH_TO_ZERO) != 0 && (vfp_double_type(s) & VFP_DENORMAL) != 0) {
s->sign = 0;
s->exponent = 0;
s->significand = 0;
*fpscr |= FPSCR_IDC;
exceptions |= FPSCR_IDC;
}
return exceptions;
}
// Re-pack a double-precision float. This assumes that the float is
@ -447,4 +451,4 @@ inline u32 fls(u32 x)
u32 vfp_double_multiply(vfp_double* vdd, vfp_double* vdn, vfp_double* vdm, u32 fpscr);
u32 vfp_double_add(vfp_double* vdd, vfp_double* vdn, vfp_double *vdm, u32 fpscr);
u32 vfp_double_normaliseround(ARMul_State* state, int dd, vfp_double* vd, u32 fpscr, u32 exceptions, const char* func);
u32 vfp_double_normaliseround(ARMul_State* state, int dd, vfp_double* vd, u32 fpscr, const char* func);

View File

@ -85,11 +85,12 @@ static void vfp_double_normalise_denormal(struct vfp_double *vd)
vfp_double_dump("normalise_denormal: out", vd);
}
u32 vfp_double_normaliseround(ARMul_State* state, int dd, struct vfp_double *vd, u32 fpscr, u32 exceptions, const char *func)
u32 vfp_double_normaliseround(ARMul_State* state, int dd, struct vfp_double *vd, u32 fpscr, const char *func)
{
u64 significand, incr;
int exponent, shift, underflow;
u32 rmode;
u32 exceptions = 0;
vfp_double_dump("pack: in", vd);
@ -291,8 +292,9 @@ static u32 vfp_double_fsqrt(ARMul_State* state, int dd, int unused, int dm, u32
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
vfp_double vdm, vdd, *vdp;
int ret, tm;
u32 exceptions = 0;
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
tm = vfp_double_type(&vdm);
if (tm & (VFP_NAN|VFP_INFINITY)) {
@ -369,7 +371,8 @@ sqrt_invalid:
}
vdd.significand = vfp_shiftright64jamming(vdd.significand, 1);
return vfp_double_normaliseround(state, dd, &vdd, fpscr, 0, "fsqrt");
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fsqrt");
return exceptions;
}
/*
@ -475,7 +478,7 @@ static u32 vfp_double_fcvts(ARMul_State* state, int sd, int unused, int dm, u32
u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
tm = vfp_double_type(&vdm);
@ -504,7 +507,8 @@ static u32 vfp_double_fcvts(ARMul_State* state, int sd, int unused, int dm, u32
else
vsd.exponent = vdm.exponent - (1023 - 127);
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fcvts");
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fcvts");
return exceptions;
pack_nan:
vfp_put_float(state, vfp_single_pack(&vsd), sd);
@ -514,6 +518,7 @@ pack_nan:
static u32 vfp_double_fuito(ARMul_State* state, int dd, int unused, int dm, u32 fpscr)
{
struct vfp_double vdm;
u32 exceptions = 0;
u32 m = vfp_get_float(state, dm);
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
@ -521,12 +526,14 @@ static u32 vfp_double_fuito(ARMul_State* state, int dd, int unused, int dm, u32
vdm.exponent = 1023 + 63 - 1;
vdm.significand = (u64)m;
return vfp_double_normaliseround(state, dd, &vdm, fpscr, 0, "fuito");
exceptions |= vfp_double_normaliseround(state, dd, &vdm, fpscr, "fuito");
return exceptions;
}
static u32 vfp_double_fsito(ARMul_State* state, int dd, int unused, int dm, u32 fpscr)
{
struct vfp_double vdm;
u32 exceptions = 0;
u32 m = vfp_get_float(state, dm);
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
@ -534,7 +541,8 @@ static u32 vfp_double_fsito(ARMul_State* state, int dd, int unused, int dm, u32
vdm.exponent = 1023 + 63 - 1;
vdm.significand = vdm.sign ? (~m + 1) : m;
return vfp_double_normaliseround(state, dd, &vdm, fpscr, 0, "fsito");
exceptions |= vfp_double_normaliseround(state, dd, &vdm, fpscr, "fsito");
return exceptions;
}
static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32 fpscr)
@ -545,7 +553,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32
int tm;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
/*
* Do we have a denormalised number?
@ -560,7 +568,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32
if (vdm.exponent >= 1023 + 32) {
d = vdm.sign ? 0 : 0xffffffff;
exceptions = FPSCR_IOC;
} else if (vdm.exponent >= 1023 - 1) {
} else if (vdm.exponent >= 1023) {
int shift = 1023 + 63 - vdm.exponent;
u64 rem, incr = 0;
@ -595,12 +603,20 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32
} else {
d = 0;
if (vdm.exponent | vdm.significand) {
if (rmode == FPSCR_ROUND_NEAREST) {
if (vdm.exponent >= 1022) {
d = vdm.sign ? 0 : 1;
exceptions |= vdm.sign ? FPSCR_IOC : FPSCR_IXC;
} else {
exceptions |= FPSCR_IXC;
if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0)
}
} else if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0) {
d = 1;
else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign) {
d = 0;
exceptions |= FPSCR_IOC;
exceptions |= FPSCR_IXC;
} else if (rmode == FPSCR_ROUND_MINUSINF) {
exceptions |= vdm.sign ? FPSCR_IOC : FPSCR_IXC;
} else {
exceptions |= FPSCR_IXC;
}
}
}
@ -615,7 +631,7 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32
static u32 vfp_double_ftouiz(ARMul_State* state, int sd, int unused, int dm, u32 fpscr)
{
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
return vfp_double_ftoui(state, sd, unused, dm, FPSCR_ROUND_TOZERO);
return vfp_double_ftoui(state, sd, unused, dm, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO);
}
static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32 fpscr)
@ -626,7 +642,7 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32
int tm;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
vfp_double_dump("VDM", &vdm);
/*
@ -639,12 +655,12 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32
if (tm & VFP_NAN) {
d = 0;
exceptions |= FPSCR_IOC;
} else if (vdm.exponent >= 1023 + 32) {
} else if (vdm.exponent >= 1023 + 31) {
d = 0x7fffffff;
if (vdm.sign)
d = ~d;
exceptions |= FPSCR_IOC;
} else if (vdm.exponent >= 1023 - 1) {
} else if (vdm.exponent >= 1023) {
int shift = 1023 + 63 - vdm.exponent; /* 58 */
u64 rem, incr = 0;
@ -675,10 +691,17 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32
d = 0;
if (vdm.exponent | vdm.significand) {
exceptions |= FPSCR_IXC;
if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0)
if (rmode == FPSCR_ROUND_NEAREST) {
if (vdm.exponent >= 1022) {
d = vdm.sign ? 0xffffffff : 1;
} else {
d = 0;
}
} else if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0) {
d = 1;
else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign)
d = -1;
} else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign) {
d = 0xffffffff;
}
}
}
@ -692,7 +715,7 @@ static u32 vfp_double_ftosi(ARMul_State* state, int sd, int unused, int dm, u32
static u32 vfp_double_ftosiz(ARMul_State* state, int dd, int unused, int dm, u32 fpscr)
{
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
return vfp_double_ftosi(state, dd, unused, dm, FPSCR_ROUND_TOZERO);
return vfp_double_ftosi(state, dd, unused, dm, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO);
}
static struct op fops_ext[] = {
@ -892,21 +915,21 @@ static u32
vfp_double_multiply_accumulate(ARMul_State* state, int dd, int dn, int dm, u32 fpscr, u32 negate, const char *func)
{
struct vfp_double vdd, vdp, vdn, vdm;
u32 exceptions;
u32 exceptions = 0;
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
if (vdn.exponent == 0 && vdn.significand)
vfp_double_normalise_denormal(&vdn);
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
if (vdm.exponent == 0 && vdm.significand)
vfp_double_normalise_denormal(&vdm);
exceptions = vfp_double_multiply(&vdp, &vdn, &vdm, fpscr);
exceptions |= vfp_double_multiply(&vdp, &vdn, &vdm, fpscr);
if (negate & NEG_MULTIPLY)
vdp.sign = vfp_sign_negate(vdp.sign);
vfp_double_unpack(&vdn, vfp_get_double(state, dd), &fpscr);
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dd), fpscr);
if (vdn.exponent == 0 && vdn.significand != 0)
vfp_double_normalise_denormal(&vdn);
@ -915,7 +938,8 @@ vfp_double_multiply_accumulate(ARMul_State* state, int dd, int dn, int dm, u32 f
exceptions |= vfp_double_add(&vdd, &vdn, &vdp, fpscr);
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, func);
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, func);
return exceptions;
}
/*
@ -964,19 +988,21 @@ static u32 vfp_double_fnmsc(ARMul_State* state, int dd, int dn, int dm, u32 fpsc
static u32 vfp_double_fmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
{
struct vfp_double vdd, vdn, vdm;
u32 exceptions;
u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
if (vdn.exponent == 0 && vdn.significand)
vfp_double_normalise_denormal(&vdn);
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
if (vdm.exponent == 0 && vdm.significand)
vfp_double_normalise_denormal(&vdm);
exceptions = vfp_double_multiply(&vdd, &vdn, &vdm, fpscr);
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fmul");
exceptions |= vfp_double_multiply(&vdd, &vdn, &vdm, fpscr);
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fmul");
return exceptions;
}
/*
@ -985,21 +1011,22 @@ static u32 vfp_double_fmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr
static u32 vfp_double_fnmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
{
struct vfp_double vdd, vdn, vdm;
u32 exceptions;
u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
if (vdn.exponent == 0 && vdn.significand)
vfp_double_normalise_denormal(&vdn);
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
if (vdm.exponent == 0 && vdm.significand)
vfp_double_normalise_denormal(&vdm);
exceptions = vfp_double_multiply(&vdd, &vdn, &vdm, fpscr);
exceptions |= vfp_double_multiply(&vdd, &vdn, &vdm, fpscr);
vdd.sign = vfp_sign_negate(vdd.sign);
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fnmul");
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fnmul");
return exceptions;
}
/*
@ -1008,20 +1035,21 @@ static u32 vfp_double_fnmul(ARMul_State* state, int dd, int dn, int dm, u32 fpsc
static u32 vfp_double_fadd(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
{
struct vfp_double vdd, vdn, vdm;
u32 exceptions;
u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
if (vdn.exponent == 0 && vdn.significand)
vfp_double_normalise_denormal(&vdn);
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
if (vdm.exponent == 0 && vdm.significand)
vfp_double_normalise_denormal(&vdm);
exceptions = vfp_double_add(&vdd, &vdn, &vdm, fpscr);
exceptions |= vfp_double_add(&vdd, &vdn, &vdm, fpscr);
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fadd");
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fadd");
return exceptions;
}
/*
@ -1030,14 +1058,14 @@ static u32 vfp_double_fadd(ARMul_State* state, int dd, int dn, int dm, u32 fpscr
static u32 vfp_double_fsub(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
{
struct vfp_double vdd, vdn, vdm;
u32 exceptions;
u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
if (vdn.exponent == 0 && vdn.significand)
vfp_double_normalise_denormal(&vdn);
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
if (vdm.exponent == 0 && vdm.significand)
vfp_double_normalise_denormal(&vdm);
@ -1046,9 +1074,10 @@ static u32 vfp_double_fsub(ARMul_State* state, int dd, int dn, int dm, u32 fpscr
*/
vdm.sign = vfp_sign_negate(vdm.sign);
exceptions = vfp_double_add(&vdd, &vdn, &vdm, fpscr);
exceptions |= vfp_double_add(&vdd, &vdn, &vdm, fpscr);
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fsub");
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fsub");
return exceptions;
}
/*
@ -1061,8 +1090,8 @@ static u32 vfp_double_fdiv(ARMul_State* state, int dd, int dn, int dm, u32 fpscr
int tm, tn;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
vfp_double_unpack(&vdn, vfp_get_double(state, dn), &fpscr);
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr);
exceptions |= vfp_double_unpack(&vdn, vfp_get_double(state, dn), fpscr);
exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
vdd.sign = vdn.sign ^ vdm.sign;
@ -1131,16 +1160,18 @@ static u32 vfp_double_fdiv(ARMul_State* state, int dd, int dn, int dm, u32 fpscr
}
vdd.significand |= (reml != 0);
}
return vfp_double_normaliseround(state, dd, &vdd, fpscr, 0, "fdiv");
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fdiv");
return exceptions;
vdn_nan:
exceptions = vfp_propagate_nan(&vdd, &vdn, &vdm, fpscr);
exceptions |= vfp_propagate_nan(&vdd, &vdn, &vdm, fpscr);
pack:
vfp_put_double(state, vfp_double_pack(&vdd), dd);
return exceptions;
vdm_nan:
exceptions = vfp_propagate_nan(&vdd, &vdm, &vdn, fpscr);
exceptions |= vfp_propagate_nan(&vdd, &vdm, &vdn, fpscr);
goto pack;
zero:
@ -1149,7 +1180,7 @@ zero:
goto pack;
divzero:
exceptions = FPSCR_DZC;
exceptions |= FPSCR_DZC;
infinity:
vdd.exponent = 2047;
vdd.significand = 0;
@ -1157,7 +1188,8 @@ infinity:
invalid:
vfp_put_double(state, vfp_double_pack(&vfp_double_default_qnan), dd);
return FPSCR_IOC;
exceptions |= FPSCR_IOC;
return exceptions;
}
static struct op fops[] = {

View File

@ -89,10 +89,11 @@ static void vfp_single_normalise_denormal(struct vfp_single *vs)
}
u32 vfp_single_normaliseround(ARMul_State* state, int sd, struct vfp_single *vs, u32 fpscr, u32 exceptions, const char *func)
u32 vfp_single_normaliseround(ARMul_State* state, int sd, struct vfp_single *vs, u32 fpscr, const char *func)
{
u32 significand, incr, rmode;
int exponent, shift, underflow;
u32 exceptions = 0;
vfp_single_dump("pack: in", vs);
@ -334,8 +335,9 @@ static u32 vfp_single_fsqrt(ARMul_State* state, int sd, int unused, s32 m, u32 f
{
struct vfp_single vsm, vsd, *vsp;
int ret, tm;
u32 exceptions = 0;
vfp_single_unpack(&vsm, m, &fpscr);
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
tm = vfp_single_type(&vsm);
if (tm & (VFP_NAN|VFP_INFINITY)) {
vsp = &vsd;
@ -408,7 +410,8 @@ sqrt_invalid:
}
vsd.significand = vfp_shiftright32jamming(vsd.significand, 1);
return vfp_single_normaliseround(state, sd, &vsd, fpscr, 0, "fsqrt");
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fsqrt");
return exceptions;
}
/*
@ -503,7 +506,7 @@ static u32 vfp_single_fcvtd(ARMul_State* state, int dd, int unused, s32 m, u32 f
int tm;
u32 exceptions = 0;
vfp_single_unpack(&vsm, m, &fpscr);
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
tm = vfp_single_type(&vsm);
@ -511,7 +514,7 @@ static u32 vfp_single_fcvtd(ARMul_State* state, int dd, int unused, s32 m, u32 f
* If we have a signalling NaN, signal invalid operation.
*/
if (tm == VFP_SNAN)
exceptions = FPSCR_IOC;
exceptions |= FPSCR_IOC;
if (tm & VFP_DENORMAL)
vfp_single_normalise_denormal(&vsm);
@ -532,7 +535,8 @@ static u32 vfp_single_fcvtd(ARMul_State* state, int dd, int unused, s32 m, u32 f
else
vdd.exponent = vsm.exponent + (1023 - 127);
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fcvtd");
exceptions |= vfp_double_normaliseround(state, dd, &vdd, fpscr, "fcvtd");
return exceptions;
pack_nan:
vfp_put_double(state, vfp_double_pack(&vdd), dd);
@ -542,23 +546,27 @@ pack_nan:
static u32 vfp_single_fuito(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
{
struct vfp_single vs;
u32 exceptions = 0;
vs.sign = 0;
vs.exponent = 127 + 31 - 1;
vs.significand = (u32)m;
return vfp_single_normaliseround(state, sd, &vs, fpscr, 0, "fuito");
exceptions |= vfp_single_normaliseround(state, sd, &vs, fpscr, "fuito");
return exceptions;
}
static u32 vfp_single_fsito(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
{
struct vfp_single vs;
u32 exceptions = 0;
vs.sign = (m & 0x80000000) >> 16;
vs.exponent = 127 + 31 - 1;
vs.significand = vs.sign ? -m : m;
return vfp_single_normaliseround(state, sd, &vs, fpscr, 0, "fsito");
exceptions |= vfp_single_normaliseround(state, sd, &vs, fpscr, "fsito");
return exceptions;
}
static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
@ -568,7 +576,7 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
int rmode = fpscr & FPSCR_RMODE_MASK;
int tm;
vfp_single_unpack(&vsm, m, &fpscr);
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
vfp_single_dump("VSM", &vsm);
/*
@ -583,7 +591,7 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
if (vsm.exponent >= 127 + 32) {
d = vsm.sign ? 0 : 0xffffffff;
exceptions = FPSCR_IOC;
exceptions |= FPSCR_IOC;
} else if (vsm.exponent >= 127) {
int shift = 127 + 31 - vsm.exponent;
u32 rem, incr = 0;
@ -592,7 +600,11 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
* 2^0 <= m < 2^32-2^8
*/
d = (vsm.significand << 1) >> shift;
rem = vsm.significand << (33 - shift);
if (shift > 0) {
rem = (vsm.significand << 1) << (32 - shift);
} else {
rem = 0;
}
if (rmode == FPSCR_ROUND_NEAREST) {
incr = 0x80000000;
@ -619,12 +631,20 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
} else {
d = 0;
if (vsm.exponent | vsm.significand) {
if (rmode == FPSCR_ROUND_NEAREST) {
if (vsm.exponent >= 126) {
d = vsm.sign ? 0 : 1;
exceptions |= vsm.sign ? FPSCR_IOC : FPSCR_IXC;
} else {
exceptions |= FPSCR_IXC;
if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0)
}
} else if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0) {
d = 1;
else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign) {
d = 0;
exceptions |= FPSCR_IOC;
exceptions |= FPSCR_IXC;
} else if (rmode == FPSCR_ROUND_MINUSINF) {
exceptions |= vsm.sign ? FPSCR_IOC : FPSCR_IXC;
} else {
exceptions |= FPSCR_IXC;
}
}
}
@ -638,7 +658,7 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
static u32 vfp_single_ftouiz(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
{
return vfp_single_ftoui(state, sd, unused, m, FPSCR_ROUND_TOZERO);
return vfp_single_ftoui(state, sd, unused, m, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO);
}
static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
@ -648,7 +668,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
int rmode = fpscr & FPSCR_RMODE_MASK;
int tm;
vfp_single_unpack(&vsm, m, &fpscr);
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
vfp_single_dump("VSM", &vsm);
/*
@ -661,7 +681,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
if (tm & VFP_NAN) {
d = 0;
exceptions |= FPSCR_IOC;
} else if (vsm.exponent >= 127 + 32) {
} else if (vsm.exponent >= 127 + 31) {
/*
* m >= 2^31-2^7: invalid
*/
@ -675,7 +695,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
/* 2^0 <= m <= 2^31-2^7 */
d = (vsm.significand << 1) >> shift;
rem = vsm.significand << (33 - shift);
rem = (vsm.significand << 1) << (32 - shift);
if (rmode == FPSCR_ROUND_NEAREST) {
incr = 0x80000000;
@ -701,10 +721,14 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
d = 0;
if (vsm.exponent | vsm.significand) {
exceptions |= FPSCR_IXC;
if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0)
if (rmode == FPSCR_ROUND_NEAREST) {
if (vsm.exponent >= 126)
d = vsm.sign ? 0xffffffff : 1;
} else if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0) {
d = 1;
else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign)
d = -1;
} else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign) {
d = 0xffffffff;
}
}
}
@ -717,7 +741,7 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
static u32 vfp_single_ftosiz(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
{
return vfp_single_ftosi(state, sd, unused, m, FPSCR_ROUND_TOZERO);
return vfp_single_ftosi(state, sd, unused, m, (fpscr & ~FPSCR_RMODE_MASK) | FPSCR_ROUND_TOZERO);
}
static struct op fops_ext[] = {
@ -774,7 +798,7 @@ vfp_single_fadd_nonnumber(struct vfp_single *vsd, struct vfp_single *vsn,
/*
* different signs -> invalid
*/
exceptions = FPSCR_IOC;
exceptions |= FPSCR_IOC;
vsp = &vfp_single_default_qnan;
} else {
/*
@ -921,27 +945,27 @@ static u32
vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr, u32 negate, const char *func)
{
vfp_single vsd, vsp, vsn, vsm;
u32 exceptions;
u32 exceptions = 0;
s32 v;
v = vfp_get_float(state, sn);
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, v);
vfp_single_unpack(&vsn, v, &fpscr);
exceptions |= vfp_single_unpack(&vsn, v, fpscr);
if (vsn.exponent == 0 && vsn.significand)
vfp_single_normalise_denormal(&vsn);
vfp_single_unpack(&vsm, m, &fpscr);
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
if (vsm.exponent == 0 && vsm.significand)
vfp_single_normalise_denormal(&vsm);
exceptions = vfp_single_multiply(&vsp, &vsn, &vsm, fpscr);
exceptions |= vfp_single_multiply(&vsp, &vsn, &vsm, fpscr);
if (negate & NEG_MULTIPLY)
vsp.sign = vfp_sign_negate(vsp.sign);
v = vfp_get_float(state, sd);
LOG_TRACE(Core_ARM11, "s%u = %08x", sd, v);
vfp_single_unpack(&vsn, v, &fpscr);
exceptions |= vfp_single_unpack(&vsn, v, fpscr);
if (vsn.exponent == 0 && vsn.significand != 0)
vfp_single_normalise_denormal(&vsn);
@ -950,7 +974,8 @@ vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fp
exceptions |= vfp_single_add(&vsd, &vsn, &vsp, fpscr);
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, func);
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, func);
return exceptions;
}
/*
@ -962,8 +987,10 @@ vfp_single_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fp
*/
static u32 vfp_single_fmac(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
{
u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, sd);
return vfp_single_multiply_accumulate(state, sd, sn, m, fpscr, 0, "fmac");
exceptions |= vfp_single_multiply_accumulate(state, sd, sn, m, fpscr, 0, "fmac");
return exceptions;
}
/*
@ -1000,21 +1027,23 @@ static u32 vfp_single_fnmsc(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr
static u32 vfp_single_fmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
{
struct vfp_single vsd, vsn, vsm;
u32 exceptions;
u32 exceptions = 0;
s32 n = vfp_get_float(state, sn);
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n);
vfp_single_unpack(&vsn, n, &fpscr);
exceptions |= vfp_single_unpack(&vsn, n, fpscr);
if (vsn.exponent == 0 && vsn.significand)
vfp_single_normalise_denormal(&vsn);
vfp_single_unpack(&vsm, m, &fpscr);
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
if (vsm.exponent == 0 && vsm.significand)
vfp_single_normalise_denormal(&vsm);
exceptions = vfp_single_multiply(&vsd, &vsn, &vsm, fpscr);
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fmul");
exceptions |= vfp_single_multiply(&vsd, &vsn, &vsm, fpscr);
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fmul");
return exceptions;
}
/*
@ -1023,22 +1052,24 @@ static u32 vfp_single_fmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
static u32 vfp_single_fnmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
{
struct vfp_single vsd, vsn, vsm;
u32 exceptions;
u32 exceptions = 0;
s32 n = vfp_get_float(state, sn);
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n);
vfp_single_unpack(&vsn, n, &fpscr);
exceptions |= vfp_single_unpack(&vsn, n, fpscr);
if (vsn.exponent == 0 && vsn.significand)
vfp_single_normalise_denormal(&vsn);
vfp_single_unpack(&vsm, m, &fpscr);
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
if (vsm.exponent == 0 && vsm.significand)
vfp_single_normalise_denormal(&vsm);
exceptions = vfp_single_multiply(&vsd, &vsn, &vsm, fpscr);
exceptions |= vfp_single_multiply(&vsd, &vsn, &vsm, fpscr);
vsd.sign = vfp_sign_negate(vsd.sign);
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fnmul");
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fnmul");
return exceptions;
}
/*
@ -1047,7 +1078,7 @@ static u32 vfp_single_fnmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr
static u32 vfp_single_fadd(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
{
struct vfp_single vsd, vsn, vsm;
u32 exceptions;
u32 exceptions = 0;
s32 n = vfp_get_float(state, sn);
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n);
@ -1055,17 +1086,18 @@ static u32 vfp_single_fadd(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
/*
* Unpack and normalise denormals.
*/
vfp_single_unpack(&vsn, n, &fpscr);
exceptions |= vfp_single_unpack(&vsn, n, fpscr);
if (vsn.exponent == 0 && vsn.significand)
vfp_single_normalise_denormal(&vsn);
vfp_single_unpack(&vsm, m, &fpscr);
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
if (vsm.exponent == 0 && vsm.significand)
vfp_single_normalise_denormal(&vsm);
exceptions = vfp_single_add(&vsd, &vsn, &vsm, fpscr);
exceptions |= vfp_single_add(&vsd, &vsn, &vsm, fpscr);
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fadd");
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fadd");
return exceptions;
}
/*
@ -1095,8 +1127,8 @@ static u32 vfp_single_fdiv(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n);
vfp_single_unpack(&vsn, n, &fpscr);
vfp_single_unpack(&vsm, m, &fpscr);
exceptions |= vfp_single_unpack(&vsn, n, fpscr);
exceptions |= vfp_single_unpack(&vsm, m, fpscr);
vsd.sign = vsn.sign ^ vsm.sign;
@ -1162,16 +1194,17 @@ static u32 vfp_single_fdiv(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
if ((vsd.significand & 0x3f) == 0)
vsd.significand |= ((u64)vsm.significand * vsd.significand != (u64)vsn.significand << 32);
return vfp_single_normaliseround(state, sd, &vsd, fpscr, 0, "fdiv");
exceptions |= vfp_single_normaliseround(state, sd, &vsd, fpscr, "fdiv");
return exceptions;
vsn_nan:
exceptions = vfp_propagate_nan(&vsd, &vsn, &vsm, fpscr);
exceptions |= vfp_propagate_nan(&vsd, &vsn, &vsm, fpscr);
pack:
vfp_put_float(state, vfp_single_pack(&vsd), sd);
return exceptions;
vsm_nan:
exceptions = vfp_propagate_nan(&vsd, &vsm, &vsn, fpscr);
exceptions |= vfp_propagate_nan(&vsd, &vsm, &vsn, fpscr);
goto pack;
zero:
@ -1180,7 +1213,7 @@ zero:
goto pack;
divzero:
exceptions = FPSCR_DZC;
exceptions |= FPSCR_DZC;
infinity:
vsd.exponent = 255;
vsd.significand = 0;
@ -1188,7 +1221,8 @@ infinity:
invalid:
vfp_put_float(state, vfp_single_pack(&vfp_single_default_qnan), sd);
return FPSCR_IOC;
exceptions |= FPSCR_IOC;
return exceptions;
}
static struct op fops[] = {

View File

@ -19,22 +19,22 @@ Path::Path(LowPathType type, u32 size, u32 pointer) : type(type) {
switch (type) {
case Binary:
{
u8* data = Memory::GetPointer(pointer);
binary = std::vector<u8>(data, data + size);
binary.resize(size);
Memory::ReadBlock(pointer, binary.data(), binary.size());
break;
}
case Char:
{
const char* data = reinterpret_cast<const char*>(Memory::GetPointer(pointer));
string = std::string(data, size - 1); // Data is always null-terminated.
string.resize(size - 1); // Data is always null-terminated.
Memory::ReadBlock(pointer, &string[0], string.size());
break;
}
case Wchar:
{
const char16_t* data = reinterpret_cast<const char16_t*>(Memory::GetPointer(pointer));
u16str = std::u16string(data, size/2 - 1); // Data is always null-terminated.
u16str.resize(size / 2 - 1); // Data is always null-terminated.
Memory::ReadBlock(pointer, &u16str[0], u16str.size() * sizeof(char16_t));
break;
}

View File

@ -646,7 +646,7 @@ static void ReadMemory() {
u8* data = Memory::GetPointer(addr);
if (!data) {
return SendReply("E0");
return SendReply("E00");
}
MemToGdbHex(reply, data, len);

View File

@ -32,9 +32,9 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p
// The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory.
// Create the SharedMemory that will hold the framebuffer data
Service::APT::CaptureBufferInfo capture_info;
ASSERT(sizeof(capture_info) == parameter.buffer_size);
ASSERT(sizeof(capture_info) == parameter.buffer.size());
memcpy(&capture_info, parameter.data, sizeof(capture_info));
memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
using Kernel::MemoryPermission;
// Allocate a heap block of the required size for this applet.
@ -47,8 +47,7 @@ ResultCode MiiSelector::ReceiveParameter(const Service::APT::MessageParameter& p
// Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
result.data = nullptr;
result.buffer_size = 0;
result.buffer.clear();
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id);
result.object = framebuffer_memory;
@ -63,15 +62,17 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa
// TODO(Subv): Set the expected fields in the response buffer before resending it to the application.
// TODO(Subv): Reverse the parameter format for the Mii Selector
if(parameter.buffer_size >= sizeof(u32)) {
// TODO: defaults return no error, but garbage in other unknown fields
memset(parameter.data, 0, sizeof(u32));
}
memcpy(&config, parameter.buffer.data(), parameter.buffer.size());
// TODO(Subv): Find more about this structure, result code 0 is enough to let most games continue.
MiiResult result;
memset(&result, 0, sizeof(result));
result.result_code = 0;
// Let the application know that we're closing
Service::APT::MessageParameter message;
message.buffer_size = parameter.buffer_size;
message.data = parameter.data;
message.buffer.resize(sizeof(MiiResult));
std::memcpy(message.buffer.data(), &result, message.buffer.size());
message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id);

View File

@ -24,7 +24,7 @@ struct MiiConfig {
u8 unk_004;
INSERT_PADDING_BYTES(3);
u16 unk_008;
INSERT_PADDING_BYTES(0x8C - 0xA);
INSERT_PADDING_BYTES(0x82);
u8 unk_08C;
INSERT_PADDING_BYTES(3);
u16 unk_090;
@ -75,6 +75,8 @@ public:
/// Whether this applet is currently running instead of the host application or not.
bool started;
MiiConfig config;
};
}

View File

@ -35,9 +35,9 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
// The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory.
// Create the SharedMemory that will hold the framebuffer data
Service::APT::CaptureBufferInfo capture_info;
ASSERT(sizeof(capture_info) == parameter.buffer_size);
ASSERT(sizeof(capture_info) == parameter.buffer.size());
memcpy(&capture_info, parameter.data, sizeof(capture_info));
memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
using Kernel::MemoryPermission;
// Allocate a heap block of the required size for this applet.
@ -50,8 +50,7 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
// Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result;
result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
result.data = nullptr;
result.buffer_size = 0;
result.buffer.clear();
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id);
result.object = framebuffer_memory;
@ -61,9 +60,9 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
}
ResultCode SoftwareKeyboard::StartImpl(Service::APT::AppletStartupParameter const& parameter) {
ASSERT_MSG(parameter.buffer_size == sizeof(config), "The size of the parameter (SoftwareKeyboardConfig) is wrong");
ASSERT_MSG(parameter.buffer.size() == sizeof(config), "The size of the parameter (SoftwareKeyboardConfig) is wrong");
memcpy(&config, parameter.data, parameter.buffer_size);
memcpy(&config, parameter.buffer.data(), parameter.buffer.size());
text_memory = boost::static_pointer_cast<Kernel::SharedMemory, Kernel::Object>(parameter.object);
// TODO(Subv): Verify if this is the correct behavior
@ -99,7 +98,7 @@ void SoftwareKeyboard::DrawScreenKeyboard() {
auto info = bottom_screen->framebuffer_info[bottom_screen->index];
// TODO(Subv): Draw the HLE keyboard, for now just zero-fill the framebuffer
memset(Memory::GetPointer(info.address_left), 0, info.stride * 320);
Memory::ZeroBlock(info.address_left, info.stride * 320);
GSP_GPU::SetBufferSwap(1, info);
}
@ -107,8 +106,8 @@ void SoftwareKeyboard::DrawScreenKeyboard() {
void SoftwareKeyboard::Finalize() {
// Let the application know that we're closing
Service::APT::MessageParameter message;
message.buffer_size = sizeof(SoftwareKeyboardConfig);
message.data = reinterpret_cast<u8*>(&config);
message.buffer.resize(sizeof(SoftwareKeyboardConfig));
std::memcpy(message.buffer.data(), &config, message.buffer.size());
message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id);

View File

@ -403,7 +403,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
priority = new_priority;
}
if (!Memory::GetPointer(entry_point)) {
if (!Memory::IsValidVirtualAddress(entry_point)) {
LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point);
// TODO: Verify error
return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel,

View File

@ -7,6 +7,7 @@
#include "common/assert.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/memory.h"
#include "core/memory_setup.h"
#include "core/mmio.h"

View File

@ -26,6 +26,7 @@ enum class ErrorDescription : u32 {
FS_NotAFile = 250,
FS_NotFormatted = 340, ///< This is used by the FS service when creating a SaveData archive
OutofRangeOrMisalignedAddress = 513, // TODO(purpasmart): Check if this name fits its actual usage
GPU_FirstInitialization = 519,
FS_InvalidPath = 702,
InvalidSection = 1000,
TooLarge = 1001,

View File

@ -14,6 +14,7 @@
#include "core/hle/service/apt/apt_u.h"
#include "core/hle/service/apt/bcfnt/bcfnt.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/ptm/ptm.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/mutex.h"
@ -33,6 +34,9 @@ static Kernel::SharedPtr<Kernel::Event> parameter_event; ///< APT parameter even
static u32 cpu_percent; ///< CPU time available to the running application
// APT::CheckNew3DSApp will check this unknown_ns_state_field to determine processing mode
static u8 unknown_ns_state_field;
/// Parameter data to be returned in the next call to Glance/ReceiveParameter
static MessageParameter next_parameter;
@ -176,12 +180,12 @@ void SendParameter(Service::Interface* self) {
}
MessageParameter param;
param.buffer_size = buffer_size;
param.destination_id = dst_app_id;
param.sender_id = src_app_id;
param.object = Kernel::g_handle_table.GetGeneric(handle);
param.signal = signal_type;
param.data = Memory::GetPointer(buffer);
param.buffer.resize(buffer_size);
Memory::ReadBlock(buffer, param.buffer.data(), param.buffer.size());
cmd_buff[1] = dest_applet->ReceiveParameter(param).raw;
@ -199,16 +203,15 @@ void ReceiveParameter(Service::Interface* self) {
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
cmd_buff[2] = next_parameter.sender_id;
cmd_buff[3] = next_parameter.signal; // Signal type
cmd_buff[4] = next_parameter.buffer_size; // Parameter buffer size
cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size
cmd_buff[5] = 0x10;
cmd_buff[6] = 0;
if (next_parameter.object != nullptr)
cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom();
cmd_buff[7] = (next_parameter.buffer_size << 14) | 2;
cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2;
cmd_buff[8] = buffer;
if (next_parameter.data)
memcpy(Memory::GetPointer(buffer), next_parameter.data, std::min(buffer_size, next_parameter.buffer_size));
Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size());
LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size);
}
@ -222,16 +225,15 @@ void GlanceParameter(Service::Interface* self) {
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
cmd_buff[2] = next_parameter.sender_id;
cmd_buff[3] = next_parameter.signal; // Signal type
cmd_buff[4] = next_parameter.buffer_size; // Parameter buffer size
cmd_buff[4] = next_parameter.buffer.size(); // Parameter buffer size
cmd_buff[5] = 0x10;
cmd_buff[6] = 0;
if (next_parameter.object != nullptr)
cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom();
cmd_buff[7] = (next_parameter.buffer_size << 14) | 2;
cmd_buff[7] = (next_parameter.buffer.size() << 14) | 2;
cmd_buff[8] = buffer;
if (next_parameter.data)
memcpy(Memory::GetPointer(buffer), next_parameter.data, std::min(buffer_size, next_parameter.buffer_size));
Memory::WriteBlock(buffer, next_parameter.buffer.data(), std::min(static_cast<size_t>(buffer_size), next_parameter.buffer.size()));
LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, buffer_size);
}
@ -258,6 +260,10 @@ void PrepareToStartApplication(Service::Interface* self) {
u32 title_info4 = cmd_buff[4];
u32 flags = cmd_buff[5];
if (flags & 0x00000100) {
unknown_ns_state_field = 1;
}
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_APT, "(STUBBED) called title_info1=0x%08X, title_info2=0x%08X, title_info3=0x%08X,"
@ -365,14 +371,36 @@ void StartLibraryApplet(Service::Interface* self) {
return;
}
size_t buffer_size = cmd_buff[2];
VAddr buffer_addr = cmd_buff[6];
AppletStartupParameter parameter;
parameter.buffer_size = cmd_buff[2];
parameter.object = Kernel::g_handle_table.GetGeneric(cmd_buff[4]);
parameter.data = Memory::GetPointer(cmd_buff[6]);
parameter.buffer.resize(buffer_size);
Memory::ReadBlock(buffer_addr, parameter.buffer.data(), parameter.buffer.size());
cmd_buff[1] = applet->Start(parameter).raw;
}
void SetNSStateField(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
unknown_ns_state_field = cmd_buff[1];
cmd_buff[0] = IPC::MakeHeader(0x55, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
LOG_WARNING(Service_APT, "(STUBBED) unknown_ns_state_field=%u", unknown_ns_state_field);
}
void GetNSStateField(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[0] = IPC::MakeHeader(0x56, 2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[8] = unknown_ns_state_field;
LOG_WARNING(Service_APT, "(STUBBED) unknown_ns_state_field=%u", unknown_ns_state_field);
}
void GetAppletInfo(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
auto app_id = static_cast<AppletId>(cmd_buff[1]);
@ -408,6 +436,29 @@ void GetStartupArgument(Service::Interface* self) {
cmd_buff[2] = (parameter_size > 0) ? 1 : 0;
}
void CheckNew3DSApp(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
if (unknown_ns_state_field) {
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = 0;
} else {
PTM::CheckNew3DS(self);
}
cmd_buff[0] = IPC::MakeHeader(0x101, 2, 0);
LOG_WARNING(Service_APT, "(STUBBED) called");
}
void CheckNew3DS(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
PTM::CheckNew3DS(self);
cmd_buff[0] = IPC::MakeHeader(0x102, 2, 0);
LOG_WARNING(Service_APT, "(STUBBED) called");
}
void Init() {
AddService(new APT_A_Interface);
AddService(new APT_S_Interface);
@ -441,6 +492,7 @@ void Init() {
lock = Kernel::Mutex::Create(false, "APT_U:Lock");
cpu_percent = 0;
unknown_ns_state_field = 0;
// TODO(bunnei): Check if these are created in Initialize or on APT process startup.
notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification");

View File

@ -20,16 +20,14 @@ struct MessageParameter {
u32 sender_id = 0;
u32 destination_id = 0;
u32 signal = 0;
u32 buffer_size = 0;
Kernel::SharedPtr<Kernel::Object> object = nullptr;
u8* data = nullptr;
std::vector<u8> buffer;
};
/// Holds information about the parameters used in StartLibraryApplet
struct AppletStartupParameter {
u32 buffer_size = 0;
Kernel::SharedPtr<Kernel::Object> object = nullptr;
u8* data = nullptr;
std::vector<u8> buffer;
};
/// Used by the application to pass information about the current framebuffer to applets.
@ -376,6 +374,50 @@ void StartLibraryApplet(Service::Interface* self);
*/
void GetStartupArgument(Service::Interface* self);
/**
* APT::SetNSStateField service function
* Inputs:
* 1 : u8 NS state field
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* Note:
* This writes the input u8 to a NS state field.
*/
void SetNSStateField(Service::Interface* self);
/**
* APT::GetNSStateField service function
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 8 : u8 NS state field
* Note:
* This returns a u8 NS state field(which can be set by cmd 0x00550040), at cmdreply+8.
*/
void GetNSStateField(Service::Interface* self);
/**
* APT::CheckNew3DSApp service function
* Outputs:
* 1: Result code, 0 on success, otherwise error code
* 2: u8 output: 0 = Old3DS, 1 = New3DS.
* Note:
* This uses PTMSYSM:CheckNew3DS.
* When a certain NS state field is non-zero, the output value is zero,
* Otherwise the output is from PTMSYSM:CheckNew3DS.
* Normally this NS state field is zero, however this state field is set to 1
* when APT:PrepareToStartApplication is used with flags bit8 is set.
*/
void CheckNew3DSApp(Service::Interface* self);
/**
* Wrapper for PTMSYSM:CheckNew3DS
* APT::CheckNew3DS service function
* Outputs:
* 1: Result code, 0 on success, otherwise error code
* 2: u8 output: 0 = Old3DS, 1 = New3DS.
*/
void CheckNew3DS(Service::Interface* self);
/// Initialize the APT service
void Init();

View File

@ -21,6 +21,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x000D0080, ReceiveParameter, "ReceiveParameter"},
{0x000E0080, GlanceParameter, "GlanceParameter"},
{0x000F0100, CancelParameter, "CancelParameter"},
{0x00150140, PrepareToStartApplication, "PrepareToStartApplication"},
{0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"},
{0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"},
{0x001E0084, StartLibraryApplet, "StartLibraryApplet"},
@ -32,7 +33,10 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x004F0080, SetAppCpuTimeLimit, "SetAppCpuTimeLimit"},
{0x00500040, GetAppCpuTimeLimit, "GetAppCpuTimeLimit"},
{0x00510080, GetStartupArgument, "GetStartupArgument"},
{0x00550040, nullptr, "WriteInputToNsState?"},
{0x00550040, SetNSStateField, "SetNSStateField?"},
{0x00560000, GetNSStateField, "GetNSStateField?"},
{0x01010000, CheckNew3DSApp, "CheckNew3DSApp"},
{0x01020000, CheckNew3DS, "CheckNew3DS"}
};
APT_A_Interface::APT_A_Interface() {

View File

@ -29,7 +29,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"},
{0x00130000, nullptr, "GetPreparationState"},
{0x00140040, nullptr, "SetPreparationState"},
{0x00150140, nullptr, "PrepareToStartApplication"},
{0x00150140, PrepareToStartApplication, "PrepareToStartApplication"},
{0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"},
{0x00170040, nullptr, "FinishPreloadingLibraryApplet"},
{0x00180040, PrepareToStartLibraryApplet,"PrepareToStartLibraryApplet"},
@ -92,9 +92,11 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00510080, GetStartupArgument, "GetStartupArgument"},
{0x00520104, nullptr, "Wrap1"},
{0x00530104, nullptr, "Unwrap1"},
{0x00550040, SetNSStateField, "SetNSStateField?" },
{0x00560000, GetNSStateField, "GetNSStateField?" },
{0x00580002, nullptr, "GetProgramID"},
{0x01010000, nullptr, "CheckNew3DSApp"},
{0x01020000, nullptr, "CheckNew3DS"}
{0x01010000, CheckNew3DSApp, "CheckNew3DSApp"},
{0x01020000, CheckNew3DS, "CheckNew3DS"}
};
APT_S_Interface::APT_S_Interface() {

View File

@ -29,7 +29,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"},
{0x00130000, nullptr, "GetPreparationState"},
{0x00140040, nullptr, "SetPreparationState"},
{0x00150140, nullptr, "PrepareToStartApplication"},
{0x00150140, PrepareToStartApplication, "PrepareToStartApplication"},
{0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"},
{0x00170040, nullptr, "FinishPreloadingLibraryApplet"},
{0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"},
@ -92,9 +92,11 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00510080, GetStartupArgument, "GetStartupArgument"},
{0x00520104, nullptr, "Wrap1"},
{0x00530104, nullptr, "Unwrap1"},
{0x00550040, SetNSStateField, "SetNSStateField?"},
{0x00560000, GetNSStateField, "GetNSStateField?"},
{0x00580002, nullptr, "GetProgramID"},
{0x01010000, nullptr, "CheckNew3DSApp"},
{0x01020000, nullptr, "CheckNew3DS"}
{0x01010000, CheckNew3DSApp, "CheckNew3DSApp"},
{0x01020000, CheckNew3DS, "CheckNew3DS"}
};
APT_U_Interface::APT_U_Interface() {

View File

@ -47,6 +47,12 @@ struct UsernameBlock {
};
static_assert(sizeof(UsernameBlock) == 0x1C, "UsernameBlock must be exactly 0x1C bytes");
struct BirthdayBlock {
u8 month; ///< The month of the birthday
u8 day; ///< The day of the birthday
};
static_assert(sizeof(BirthdayBlock) == 2, "BirthdayBlock must be exactly 2 bytes");
struct ConsoleModelInfo {
u8 model; ///< The console model (3DS, 2DS, etc)
u8 unknown[3]; ///< Unknown data
@ -65,9 +71,8 @@ static const u64 CFG_SAVE_ID = 0x00010017;
static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE;
static const ConsoleModelInfo CONSOLE_MODEL = { NINTENDO_3DS_XL, { 0, 0, 0 } };
static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN;
static const char CONSOLE_USERNAME[0x14] = "CITRA";
/// This will be initialized in Init, and will be used when creating the block
static UsernameBlock CONSOLE_USERNAME_BLOCK;
static const UsernameBlock CONSOLE_USERNAME_BLOCK = { u"CITRA", 0, 0 };
static const BirthdayBlock PROFILE_BIRTHDAY = { 3, 25 }; // March 25th, 2014
/// TODO(Subv): Find out what this actually is
static const u8 SOUND_OUTPUT_MODE = 2;
static const u8 UNITED_STATES_COUNTRY_ID = 49;
@ -191,28 +196,32 @@ void GetConfigInfoBlk2(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 size = cmd_buff[1];
u32 block_id = cmd_buff[2];
u8* data_pointer = Memory::GetPointer(cmd_buff[4]);
VAddr data_pointer = cmd_buff[4];
if (data_pointer == nullptr) {
if (!Memory::IsValidVirtualAddress(data_pointer)) {
cmd_buff[1] = -1; // TODO(Subv): Find the right error code
return;
}
cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x2, data_pointer).raw;
std::vector<u8> data(size);
cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x2, data.data()).raw;
Memory::WriteBlock(data_pointer, data.data(), data.size());
}
void GetConfigInfoBlk8(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 size = cmd_buff[1];
u32 block_id = cmd_buff[2];
u8* data_pointer = Memory::GetPointer(cmd_buff[4]);
VAddr data_pointer = cmd_buff[4];
if (data_pointer == nullptr) {
if (!Memory::IsValidVirtualAddress(data_pointer)) {
cmd_buff[1] = -1; // TODO(Subv): Find the right error code
return;
}
cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x8, data_pointer).raw;
std::vector<u8> data(size);
cmd_buff[1] = Service::CFG::GetConfigInfoBlock(block_id, size, 0x8, data.data()).raw;
Memory::WriteBlock(data_pointer, data.data(), data.size());
}
void UpdateConfigNANDSavegame(Service::Interface* self) {
@ -329,32 +338,22 @@ ResultCode FormatConfig() {
res = CreateConfigInfoBlk(0x00050005, sizeof(STEREO_CAMERA_SETTINGS), 0xE, STEREO_CAMERA_SETTINGS.data());
if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x00070001, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE);
if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x00090001, sizeof(CONSOLE_UNIQUE_ID), 0xE, &CONSOLE_UNIQUE_ID);
if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, &CONSOLE_USERNAME_BLOCK);
if (!res.IsSuccess()) return res;
// 0x000A0000 - Profile username
struct {
u16_le username[10];
u8 unused[4];
u32_le wordfilter_version; // Unused by Citra
} profile_username = {};
std::u16string username_string = Common::UTF8ToUTF16("Citra");
std::copy(username_string.cbegin(), username_string.cend(), profile_username.username);
res = CreateConfigInfoBlk(0x000A0000, sizeof(profile_username), 0xE, &profile_username);
if (!res.IsSuccess()) return res;
// 0x000A0001 - Profile birthday
const u8 profile_birthday[2] = {3, 25}; // March 25th, 2014
res = CreateConfigInfoBlk(0x000A0001, sizeof(profile_birthday), 0xE, profile_birthday);
res = CreateConfigInfoBlk(0x000A0001, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY);
if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000A0002, sizeof(CONSOLE_LANGUAGE), 0xE, &CONSOLE_LANGUAGE);
if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO);
if (!res.IsSuccess()) return res;
@ -435,17 +434,6 @@ void Init() {
return;
}
// Initialize the Username block
// TODO(Subv): Initialize this directly in the variable when MSVC supports char16_t string literals
memset(&CONSOLE_USERNAME_BLOCK, 0, sizeof(CONSOLE_USERNAME_BLOCK));
CONSOLE_USERNAME_BLOCK.ng_word = 0;
CONSOLE_USERNAME_BLOCK.zero = 0;
// Copy string to buffer and pad with zeros at the end
auto size = Common::UTF8ToUTF16(CONSOLE_USERNAME).copy(CONSOLE_USERNAME_BLOCK.username, 0x14);
std::fill(std::begin(CONSOLE_USERNAME_BLOCK.username) + size,
std::end(CONSOLE_USERNAME_BLOCK.username), 0);
FormatConfig();
}

View File

@ -0,0 +1,24 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/service.h"
#include "core/hle/service/dlp/dlp.h"
#include "core/hle/service/dlp/dlp_clnt.h"
#include "core/hle/service/dlp/dlp_fkcl.h"
#include "core/hle/service/dlp/dlp_srvr.h"
namespace Service {
namespace DLP {
void Init() {
AddService(new DLP_CLNT_Interface);
AddService(new DLP_FKCL_Interface);
AddService(new DLP_SRVR_Interface);
}
void Shutdown() {
}
} // namespace DLP
} // namespace Service

View File

@ -0,0 +1,15 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
namespace Service {
namespace DLP {
/// Initializes the DLP services.
void Init();
/// Shuts down the DLP services.
void Shutdown();
} // namespace DLP
} // namespace Service

View File

@ -0,0 +1,20 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/dlp/dlp_clnt.h"
namespace Service {
namespace DLP {
const Interface::FunctionInfo FunctionTable[] = {
{0x000100C3, nullptr, "Initialize"},
{0x00110000, nullptr, "GetWirelessRebootPassphrase"},
};
DLP_CLNT_Interface::DLP_CLNT_Interface() {
Register(FunctionTable);
}
} // namespace DLP
} // namespace Service

View File

@ -0,0 +1,22 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Service {
namespace DLP {
class DLP_CLNT_Interface final : public Interface {
public:
DLP_CLNT_Interface();
std::string GetPortName() const override {
return "dlp:CLNT";
}
};
} // namespace DLP
} // namespace Service

View File

@ -0,0 +1,20 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/hle/service/dlp/dlp_fkcl.h"
namespace Service {
namespace DLP {
const Interface::FunctionInfo FunctionTable[] = {
{0x00010083, nullptr, "Initialize"},
{0x000F0000, nullptr, "GetWirelessRebootPassphrase"},
};
DLP_FKCL_Interface::DLP_FKCL_Interface() {
Register(FunctionTable);
}
} // namespace DLP
} // namespace Service

View File

@ -0,0 +1,22 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Service {
namespace DLP {
class DLP_FKCL_Interface final : public Interface {
public:
DLP_FKCL_Interface();
std::string GetPortName() const override {
return "dlp:FKCL";
}
};
} // namespace DLP
} // namespace Service

View File

@ -2,16 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/hle/hle.h"
#include "core/hle/service/dlp_srvr.h"
#include "core/hle/result.h"
#include "core/hle/service/dlp/dlp_srvr.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace DLP_SRVR
namespace Service {
namespace DLP {
namespace DLP_SRVR {
static void unk_0x000E0040(Service::Interface* self) {
static void unk_0x000E0040(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = RESULT_SUCCESS.raw;
@ -23,14 +22,13 @@ static void unk_0x000E0040(Service::Interface* self) {
const Interface::FunctionInfo FunctionTable[] = {
{0x00010183, nullptr, "Initialize"},
{0x00020000, nullptr, "Finalize"},
{0x000800C0, nullptr, "SendWirelessRebootPassphrase"},
{0x000E0040, unk_0x000E0040, "unk_0x000E0040"},
};
////////////////////////////////////////////////////////////////////////////////////////////////////
// Interface class
Interface::Interface() {
DLP_SRVR_Interface::DLP_SRVR_Interface() {
Register(FunctionTable);
}
} // namespace
} // namespace DLP
} // namespace Service

View File

@ -6,18 +6,17 @@
#include "core/hle/service/service.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace DLP_SRVR
namespace Service {
namespace DLP {
namespace DLP_SRVR {
class Interface : public Service::Interface {
class DLP_SRVR_Interface final : public Interface {
public:
Interface();
DLP_SRVR_Interface();
std::string GetPortName() const override {
return "dlp:SRVR";
}
};
} // namespace
} // namespace DLP
} // namespace Service

View File

@ -140,12 +140,15 @@ static void LoadComponent(Service::Interface* self) {
// TODO(bunnei): Implement real DSP firmware loading
ASSERT(Memory::GetPointer(buffer) != nullptr);
ASSERT(size > 0x37C);
ASSERT(Memory::IsValidVirtualAddress(buffer));
LOG_INFO(Service_DSP, "Firmware hash: %#" PRIx64, Common::ComputeHash64(Memory::GetPointer(buffer), size));
std::vector<u8> component_data(size);
Memory::ReadBlock(buffer, component_data.data(), component_data.size());
LOG_INFO(Service_DSP, "Firmware hash: %#" PRIx64, Common::ComputeHash64(component_data.data(), component_data.size()));
// Some versions of the firmware have the location of DSP structures listed here.
LOG_INFO(Service_DSP, "Structures hash: %#" PRIx64, Common::ComputeHash64(Memory::GetPointer(buffer) + 0x340, 60));
ASSERT(size > 0x37C);
LOG_INFO(Service_DSP, "Structures hash: %#" PRIx64, Common::ComputeHash64(component_data.data() + 0x340, 60));
LOG_WARNING(Service_DSP, "(STUBBED) called size=0x%X, prog_mask=0x%08X, data_mask=0x%08X, buffer=0x%08X",
size, prog_mask, data_mask, buffer);
@ -285,7 +288,7 @@ static void WriteProcessPipe(Service::Interface* self) {
return;
}
ASSERT_MSG(Memory::GetPointer(buffer) != nullptr, "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe_index, size, buffer);
ASSERT_MSG(Memory::IsValidVirtualAddress(buffer), "Invalid Buffer: pipe=%u, size=0x%X, buffer=0x%08X", pipe, size, buffer);
std::vector<u8> message(size);
for (u32 i = 0; i < size; i++) {
@ -324,7 +327,7 @@ static void ReadPipeIfPossible(Service::Interface* self) {
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index, unknown, size, addr);
ASSERT_MSG(Memory::IsValidVirtualAddress(addr), "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr);
cmd_buff[0] = IPC::MakeHeader(0x10, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
@ -364,7 +367,7 @@ static void ReadPipe(Service::Interface* self) {
DSP::HLE::DspPipe pipe = static_cast<DSP::HLE::DspPipe>(pipe_index);
ASSERT_MSG(Memory::GetPointer(addr) != nullptr, "Invalid addr: pipe=%u, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe_index, unknown, size, addr);
ASSERT_MSG(Memory::IsValidVirtualAddress(addr), "Invalid addr: pipe=0x%08X, unknown=0x%08X, size=0x%X, buffer=0x%08X", pipe, unknown, size, addr);
if (DSP::HLE::GetPipeReadableSize(pipe) >= size) {
std::vector<u8> response = DSP::HLE::PipeRead(pipe, size);

View File

@ -23,7 +23,7 @@ void GetMyPresence(Service::Interface* self) {
ASSERT(shifted_out_size == ((sizeof(MyPresence) << 14) | 2));
Memory::WriteBlock(my_presence_addr, reinterpret_cast<const u8*>(&my_presence), sizeof(MyPresence));
Memory::WriteBlock(my_presence_addr, &my_presence, sizeof(MyPresence));
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
@ -39,8 +39,7 @@ void GetFriendKeyList(Service::Interface* self) {
FriendKey zero_key = {};
for (u32 i = 0; i < frd_count; ++i) {
Memory::WriteBlock(frd_key_addr + i * sizeof(FriendKey),
reinterpret_cast<const u8*>(&zero_key), sizeof(FriendKey));
Memory::WriteBlock(frd_key_addr + i * sizeof(FriendKey), &zero_key, sizeof(FriendKey));
}
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
@ -58,8 +57,7 @@ void GetFriendProfile(Service::Interface* self) {
Profile zero_profile = {};
for (u32 i = 0; i < count; ++i) {
Memory::WriteBlock(profiles_addr + i * sizeof(Profile),
reinterpret_cast<const u8*>(&zero_profile), sizeof(Profile));
Memory::WriteBlock(profiles_addr + i * sizeof(Profile), &zero_profile, sizeof(Profile));
}
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
@ -88,7 +86,7 @@ void GetMyFriendKey(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
Memory::WriteBlock(cmd_buff[2], reinterpret_cast<const u8*>(&my_friend_key), sizeof(FriendKey));
Memory::WriteBlock(cmd_buff[2], &my_friend_key, sizeof(FriendKey));
LOG_WARNING(Service_FRD, "(STUBBED) called");
}

View File

@ -108,13 +108,14 @@ ResultVal<bool> File::SyncRequest() {
offset, length, backend->GetSize());
}
ResultVal<size_t> read = backend->Read(offset, length, Memory::GetPointer(address));
std::vector<u8> data(length);
ResultVal<size_t> read = backend->Read(offset, data.size(), data.data());
if (read.Failed()) {
cmd_buff[1] = read.Code().raw;
return read.Code();
}
Memory::WriteBlock(address, data.data(), *read);
cmd_buff[2] = static_cast<u32>(*read);
Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(address), length);
break;
}
@ -128,7 +129,9 @@ ResultVal<bool> File::SyncRequest() {
LOG_TRACE(Service_FS, "Write %s %s: offset=0x%llx length=%d address=0x%x, flush=0x%x",
GetTypeName().c_str(), GetName().c_str(), offset, length, address, flush);
ResultVal<size_t> written = backend->Write(offset, length, flush != 0, Memory::GetPointer(address));
std::vector<u8> data(length);
Memory::ReadBlock(address, data.data(), data.size());
ResultVal<size_t> written = backend->Write(offset, data.size(), flush != 0, data.data());
if (written.Failed()) {
cmd_buff[1] = written.Code().raw;
return written.Code();
@ -216,12 +219,14 @@ ResultVal<bool> Directory::SyncRequest() {
{
u32 count = cmd_buff[1];
u32 address = cmd_buff[3];
auto entries = reinterpret_cast<FileSys::Entry*>(Memory::GetPointer(address));
std::vector<FileSys::Entry> entries(count);
LOG_TRACE(Service_FS, "Read %s %s: count=%d",
GetTypeName().c_str(), GetName().c_str(), count);
// Number of entries actually read
cmd_buff[2] = backend->Read(count, entries);
u32 read = backend->Read(entries.size(), entries.data());
cmd_buff[2] = read;
Memory::WriteBlock(address, entries.data(), read * sizeof(FileSys::Entry));
break;
}
@ -456,11 +461,12 @@ ResultCode CreateExtSaveData(MediaType media_type, u32 high, u32 low, VAddr icon
if (result.IsError())
return result;
u8* smdh_icon = Memory::GetPointer(icon_buffer);
if (!smdh_icon)
if (!Memory::IsValidVirtualAddress(icon_buffer))
return ResultCode(-1); // TODO(Subv): Find the right error code
ext_savedata->WriteIcon(path, smdh_icon, icon_size);
std::vector<u8> smdh_icon(icon_size);
Memory::ReadBlock(icon_buffer, smdh_icon.data(), smdh_icon.size());
ext_savedata->WriteIcon(path, smdh_icon.data(), smdh_icon.size());
return RESULT_SUCCESS;
}

View File

@ -44,7 +44,7 @@ Kernel::SharedPtr<Kernel::SharedMemory> g_shared_memory;
u32 g_thread_id = 0;
static bool gpu_right_acquired = false;
static bool first_initialization = true;
/// Gets a pointer to a thread command buffer in GSP shared memory
static inline u8* GetCommandBuffer(u32 thread_id) {
return g_shared_memory->GetPointer(0x800 + (thread_id * sizeof(CommandBuffer)));
@ -65,15 +65,27 @@ static inline InterruptRelayQueue* GetInterruptRelayQueue(u32 thread_id) {
return reinterpret_cast<InterruptRelayQueue*>(ptr);
}
/**
* Writes a single GSP GPU hardware registers with a single u32 value
* (For internal use.)
*
* @param base_address The address of the register in question
* @param data Data to be written
*/
static void WriteSingleHWReg(u32 base_address, u32 data) {
DEBUG_ASSERT_MSG((base_address & 3) == 0 && base_address < 0x420000, "Write address out of range or misaligned");
HW::Write<u32>(base_address + REGS_BEGIN, data);
}
/**
* Writes sequential GSP GPU hardware registers using an array of source data
*
* @param base_address The address of the first register in the sequence
* @param size_in_bytes The number of registers to update (size of data)
* @param data A pointer to the source data
* @param data_vaddr A pointer to the source data
* @return RESULT_SUCCESS if the parameters are valid, error code otherwise
*/
static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* data) {
static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, VAddr data_vaddr) {
// This magic number is verified to be done by the gsp module
const u32 max_size_in_bytes = 0x80;
@ -87,10 +99,10 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* da
return ERR_GSP_REGS_MISALIGNED;
} else {
while (size_in_bytes > 0) {
HW::Write<u32>(base_address + REGS_BEGIN, *data);
WriteSingleHWReg(base_address, Memory::Read32(data_vaddr));
size_in_bytes -= 4;
++data;
data_vaddr += 4;
base_address += 4;
}
return RESULT_SUCCESS;
@ -112,7 +124,7 @@ static ResultCode WriteHWRegs(u32 base_address, u32 size_in_bytes, const u32* da
* @param masks A pointer to the masks
* @return RESULT_SUCCESS if the parameters are valid, error code otherwise
*/
static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const u32* data, const u32* masks) {
static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr data_vaddr, VAddr masks_vaddr) {
// This magic number is verified to be done by the gsp module
const u32 max_size_in_bytes = 0x80;
@ -131,14 +143,17 @@ static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, const
u32 reg_value;
HW::Read<u32>(reg_value, reg_address);
// Update the current value of the register only for set mask bits
reg_value = (reg_value & ~*masks) | (*data | *masks);
u32 data = Memory::Read32(data_vaddr);
u32 mask = Memory::Read32(masks_vaddr);
HW::Write<u32>(reg_address, reg_value);
// Update the current value of the register only for set mask bits
reg_value = (reg_value & ~mask) | (data | mask);
WriteSingleHWReg(base_address, reg_value);
size_in_bytes -= 4;
++data;
++masks;
data_vaddr += 4;
masks_vaddr += 4;
base_address += 4;
}
return RESULT_SUCCESS;
@ -164,8 +179,7 @@ static void WriteHWRegs(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 reg_addr = cmd_buff[1];
u32 size = cmd_buff[2];
u32* src = (u32*)Memory::GetPointer(cmd_buff[4]);
VAddr src = cmd_buff[4];
cmd_buff[1] = WriteHWRegs(reg_addr, size, src).raw;
}
@ -186,8 +200,8 @@ static void WriteHWRegsWithMask(Service::Interface* self) {
u32 reg_addr = cmd_buff[1];
u32 size = cmd_buff[2];
u32* src_data = (u32*)Memory::GetPointer(cmd_buff[4]);
u32* mask_data = (u32*)Memory::GetPointer(cmd_buff[6]);
VAddr src_data = cmd_buff[4];
VAddr mask_data = cmd_buff[6];
cmd_buff[1] = WriteHWRegsWithMask(reg_addr, size, src_data, mask_data).raw;
}
@ -210,13 +224,16 @@ static void ReadHWRegs(Service::Interface* self) {
return;
}
u32* dst = (u32*)Memory::GetPointer(cmd_buff[0x41]);
VAddr dst_vaddr = cmd_buff[0x41];
while (size > 0) {
HW::Read<u32>(*dst, reg_addr + REGS_BEGIN);
u32 value;
HW::Read<u32>(value, reg_addr + REGS_BEGIN);
Memory::Write32(dst_vaddr, value);
size -= 4;
++dst;
dst_vaddr += 4;
reg_addr += 4;
}
}
@ -226,22 +243,22 @@ ResultCode SetBufferSwap(u32 screen_id, const FrameBufferInfo& info) {
PAddr phys_address_left = Memory::VirtualToPhysicalAddress(info.address_left);
PAddr phys_address_right = Memory::VirtualToPhysicalAddress(info.address_right);
if (info.active_fb == 0) {
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left1)),
4, &phys_address_left);
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right1)),
4, &phys_address_right);
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left1)),
phys_address_left);
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right1)),
phys_address_right);
} else {
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left2)),
4, &phys_address_left);
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right2)),
4, &phys_address_right);
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left2)),
phys_address_left);
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right2)),
phys_address_right);
}
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].stride)),
4, &info.stride);
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].color_format)),
4, &info.format);
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].active_fb)),
4, &info.shown_fb);
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].stride)),
info.stride);
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].color_format)),
info.format);
WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].active_fb)),
info.shown_fb);
if (Pica::g_debug_context)
Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::BufferSwapped, nullptr);
@ -330,24 +347,25 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) {
u32 flags = cmd_buff[1];
g_interrupt_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[3]);
// TODO(mailwl): return right error code instead assert
ASSERT_MSG((g_interrupt_event != nullptr), "handle is not valid!");
g_interrupt_event->name = "GSP_GPU::interrupt_event";
using Kernel::MemoryPermission;
g_shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000,
MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
0, Kernel::MemoryRegion::BASE, "GSP:SharedMemory");
Handle shmem_handle = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom();
if (first_initialization) {
// This specific code is required for a successful initialization, rather than 0
cmd_buff[1] = ResultCode((ErrorDescription)519, ErrorModule::GX,
first_initialization = false;
cmd_buff[1] = ResultCode(ErrorDescription::GPU_FirstInitialization, ErrorModule::GX,
ErrorSummary::Success, ErrorLevel::Success).raw;
} else {
cmd_buff[1] = RESULT_SUCCESS.raw;
}
cmd_buff[2] = g_thread_id++; // Thread ID
cmd_buff[4] = shmem_handle; // GSP shared memory
cmd_buff[4] = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom(); // GSP shared memory
g_interrupt_event->Signal(); // TODO(bunnei): Is this correct?
LOG_WARNING(Service_GSP, "called, flags=0x%08X", flags);
}
/**
@ -358,12 +376,12 @@ static void RegisterInterruptRelayQueue(Service::Interface* self) {
static void UnregisterInterruptRelayQueue(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
g_shared_memory = nullptr;
g_thread_id = 0;
g_interrupt_event = nullptr;
cmd_buff[1] = RESULT_SUCCESS.raw;
LOG_WARNING(Service_GSP, "called");
LOG_WARNING(Service_GSP, "(STUBBED) called");
}
/**
@ -432,9 +450,9 @@ static void ExecuteCommand(const Command& command, u32 thread_id) {
Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(command.dma_request.dest_address),
command.dma_request.size);
memcpy(Memory::GetPointer(command.dma_request.dest_address),
Memory::GetPointer(command.dma_request.source_address),
command.dma_request.size);
// TODO(Subv): These memory accesses should not go through the application's memory mapping.
// They should go through the GSP module's memory mapping.
Memory::CopyBlock(command.dma_request.dest_address, command.dma_request.source_address, command.dma_request.size);
SignalInterrupt(InterruptId::DMA);
break;
}
@ -701,10 +719,15 @@ Interface::Interface() {
Register(FunctionTable);
g_interrupt_event = nullptr;
g_shared_memory = nullptr;
using Kernel::MemoryPermission;
g_shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000,
MemoryPermission::ReadWrite, MemoryPermission::ReadWrite,
0, Kernel::MemoryRegion::BASE, "GSP:SharedMemory");
g_thread_id = 0;
gpu_right_acquired = false;
first_initialization = true;
}
Interface::~Interface() {

View File

@ -2,6 +2,8 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cmath>
#include "common/logging/log.h"
#include "common/emu_window.h"
@ -19,8 +21,6 @@
namespace Service {
namespace HID {
static const int MAX_CIRCLEPAD_POS = 0x9C; ///< Max value for a circle pad position
// Handle to shared memory region designated to HID_User service
static Kernel::SharedPtr<Kernel::SharedMemory> shared_mem;
@ -39,38 +39,48 @@ static u32 next_gyroscope_index;
static int enable_accelerometer_count = 0; // positive means enabled
static int enable_gyroscope_count = 0; // positive means enabled
const std::array<Service::HID::PadState, Settings::NativeInput::NUM_INPUTS> pad_mapping = {{
Service::HID::PAD_A, Service::HID::PAD_B, Service::HID::PAD_X, Service::HID::PAD_Y,
Service::HID::PAD_L, Service::HID::PAD_R, Service::HID::PAD_ZL, Service::HID::PAD_ZR,
Service::HID::PAD_START, Service::HID::PAD_SELECT, Service::HID::PAD_NONE,
Service::HID::PAD_UP, Service::HID::PAD_DOWN, Service::HID::PAD_LEFT, Service::HID::PAD_RIGHT,
Service::HID::PAD_CIRCLE_UP, Service::HID::PAD_CIRCLE_DOWN, Service::HID::PAD_CIRCLE_LEFT, Service::HID::PAD_CIRCLE_RIGHT,
Service::HID::PAD_C_UP, Service::HID::PAD_C_DOWN, Service::HID::PAD_C_LEFT, Service::HID::PAD_C_RIGHT
}};
static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
constexpr float TAN30 = 0.577350269, TAN60 = 1 / TAN30; // 30 degree and 60 degree are angular thresholds for directions
constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; // a circle pad radius greater than 40 will trigger circle pad direction
PadState state;
state.hex = 0;
if (circle_pad_x * circle_pad_x + circle_pad_y * circle_pad_y > CIRCLE_PAD_THRESHOLD_SQUARE) {
float t = std::abs(static_cast<float>(circle_pad_y) / circle_pad_x);
// TODO(peachum):
// Add a method for setting analog input from joystick device for the circle Pad.
//
// This method should:
// * Be called after both PadButton<Press, Release>().
// * Be called before PadUpdateComplete()
// * Set current PadEntry.circle_pad_<axis> using analog data
// * Set PadData.raw_circle_pad_data
// * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_x >= 41
// * Set PadData.current_state.circle_up = 1 if current PadEntry.circle_pad_y >= 41
// * Set PadData.current_state.circle_left = 1 if current PadEntry.circle_pad_x <= -41
// * Set PadData.current_state.circle_right = 1 if current PadEntry.circle_pad_y <= -41
if (circle_pad_x != 0 && t < TAN60) {
if (circle_pad_x > 0)
state.circle_right.Assign(1);
else
state.circle_left.Assign(1);
}
if (circle_pad_x == 0 || t > TAN30) {
if (circle_pad_y > 0)
state.circle_up.Assign(1);
else
state.circle_down.Assign(1);
}
}
return state;
}
void Update() {
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
const PadState state = VideoCore::g_emu_window->GetPadState();
if (mem == nullptr) {
LOG_DEBUG(Service_HID, "Cannot update HID prior to mapping shared memory!");
return;
}
PadState state = VideoCore::g_emu_window->GetPadState();
// Get current circle pad position and update circle pad direction
s16 circle_pad_x, circle_pad_y;
std::tie(circle_pad_x, circle_pad_y) = VideoCore::g_emu_window->GetCirclePadState();
state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex;
mem->pad.current_state.hex = state.hex;
mem->pad.index = next_pad_index;
next_pad_index = (next_pad_index + 1) % mem->pad.entries.size();
@ -88,13 +98,9 @@ void Update() {
// Update entry properties
pad_entry.current_state.hex = state.hex;
pad_entry.delta_additions.hex = changed.hex & state.hex;
pad_entry.delta_removals.hex = changed.hex & old_state.hex;;
// Set circle Pad
pad_entry.circle_pad_x = state.circle_left ? -MAX_CIRCLEPAD_POS :
state.circle_right ? MAX_CIRCLEPAD_POS : 0x0;
pad_entry.circle_pad_y = state.circle_down ? -MAX_CIRCLEPAD_POS :
state.circle_up ? MAX_CIRCLEPAD_POS : 0x0;
pad_entry.delta_removals.hex = changed.hex & old_state.hex;
pad_entry.circle_pad_x = circle_pad_x;
pad_entry.circle_pad_y = circle_pad_y;
// If we just updated index 0, provide a new timestamp
if (mem->pad.index == 0) {

View File

@ -215,9 +215,6 @@ const PadState PAD_CIRCLE_LEFT = {{1u << 29}};
const PadState PAD_CIRCLE_UP = {{1u << 30}};
const PadState PAD_CIRCLE_DOWN = {{1u << 31}};
extern const std::array<Service::HID::PadState, Settings::NativeInput::NUM_INPUTS> pad_mapping;
/**
* HID::GetIPCHandles service function
* Inputs:

View File

@ -3,7 +3,7 @@
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/settings.h"
#include "core/file_sys/file_backend.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/ptm/ptm.h"
@ -89,6 +89,20 @@ void IsLegacyPowerOff(Service::Interface* self) {
LOG_WARNING(Service_PTM, "(STUBBED) called");
}
void CheckNew3DS(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
const bool is_new_3ds = Settings::values.is_new_3ds;
if (is_new_3ds) {
LOG_CRITICAL(Service_PTM, "The option 'is_new_3ds' is enabled as part of the 'System' settings. Citra does not fully support New 3DS emulation yet!");
}
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = is_new_3ds ? 1 : 0;
LOG_WARNING(Service_PTM, "(STUBBED) called isNew3DS = 0x%08x", static_cast<u32>(is_new_3ds));
}
void Init() {
AddService(new PTM_Play_Interface);
AddService(new PTM_Sysm_Interface);

View File

@ -88,6 +88,14 @@ void GetTotalStepCount(Interface* self);
*/
void IsLegacyPowerOff(Interface* self);
/**
* PTM::CheckNew3DS service function
* Outputs:
* 1: Result code, 0 on success, otherwise error code
* 2: u8 output: 0 = Old3DS, 1 = New3DS.
*/
void CheckNew3DS(Interface* self);
/// Initialize the PTM service
void Init();

View File

@ -18,7 +18,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x040700C0, nullptr, "ShutdownAsync"},
{0x04080000, nullptr, "Awake"},
{0x04090080, nullptr, "RebootAsync"},
{0x040A0000, nullptr, "CheckNew3DS"},
{0x040A0000, CheckNew3DS, "CheckNew3DS"},
{0x08010640, nullptr, "SetInfoLEDPattern"},
{0x08020040, nullptr, "SetInfoLEDPatternHeader"},
{0x08030000, nullptr, "GetInfoLEDStatus"},
@ -35,7 +35,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x080E0140, nullptr, "NotifyPlayEvent"},
{0x080F0000, IsLegacyPowerOff, "IsLegacyPowerOff"},
{0x08100000, nullptr, "ClearLegacyPowerOff"},
{0x08110000, nullptr, "GetShellStatus"},
{0x08110000, GetShellState, "GetShellState"},
{0x08120000, nullptr, "IsShutdownByBatteryEmpty"},
{0x08130000, nullptr, "FormatSavedata"},
{0x08140000, nullptr, "GetLegacyJumpProhibitedFlag"},

View File

@ -10,7 +10,6 @@
#include "core/hle/service/act_a.h"
#include "core/hle/service/act_u.h"
#include "core/hle/service/csnd_snd.h"
#include "core/hle/service/dlp_srvr.h"
#include "core/hle/service/dsp_dsp.h"
#include "core/hle/service/err_f.h"
#include "core/hle/service/gsp_gpu.h"
@ -31,6 +30,7 @@
#include "core/hle/service/boss/boss.h"
#include "core/hle/service/cam/cam.h"
#include "core/hle/service/cecd/cecd.h"
#include "core/hle/service/dlp/dlp.h"
#include "core/hle/service/frd/frd.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/cfg/cfg.h"
@ -111,6 +111,7 @@ void Init() {
Service::CAM::Init();
Service::CECD::Init();
Service::CFG::Init();
Service::DLP::Init();
Service::FRD::Init();
Service::HID::Init();
Service::IR::Init();
@ -123,7 +124,6 @@ void Init() {
AddService(new ACT_A::Interface);
AddService(new ACT_U::Interface);
AddService(new CSND_SND::Interface);
AddService(new DLP_SRVR::Interface);
AddService(new DSP_DSP::Interface);
AddService(new GSP_GPU::Interface);
AddService(new GSP_LCD::Interface);
@ -150,6 +150,7 @@ void Shutdown() {
Service::IR::Shutdown();
Service::HID::Shutdown();
Service::FRD::Shutdown();
Service::DLP::Shutdown();
Service::CFG::Shutdown();
Service::CECD::Shutdown();
Service::CAM::Shutdown();

View File

@ -373,14 +373,18 @@ static void Bind(Service::Interface* self) {
u32* cmd_buffer = Kernel::GetCommandBuffer();
u32 socket_handle = cmd_buffer[1];
u32 len = cmd_buffer[2];
CTRSockAddr* ctr_sock_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[6]));
if (ctr_sock_addr == nullptr) {
// Virtual address of the sock_addr structure
VAddr sock_addr_addr = cmd_buffer[6];
if (!Memory::IsValidVirtualAddress(sock_addr_addr)) {
cmd_buffer[1] = -1; // TODO(Subv): Correct code
return;
}
sockaddr sock_addr = CTRSockAddr::ToPlatform(*ctr_sock_addr);
CTRSockAddr ctr_sock_addr;
Memory::ReadBlock(sock_addr_addr, reinterpret_cast<u8*>(&ctr_sock_addr), sizeof(CTRSockAddr));
sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr);
int res = ::bind(socket_handle, &sock_addr, std::max<u32>(sizeof(sock_addr), len));
@ -496,7 +500,7 @@ static void Accept(Service::Interface* self) {
result = TranslateError(GET_ERRNO);
} else {
CTRSockAddr ctr_addr = CTRSockAddr::FromPlatform(addr);
Memory::WriteBlock(cmd_buffer[0x104 >> 2], (const u8*)&ctr_addr, max_addr_len);
Memory::WriteBlock(cmd_buffer[0x104 >> 2], &ctr_addr, sizeof(ctr_addr));
}
cmd_buffer[0] = IPC::MakeHeader(4, 2, 2);
@ -547,20 +551,31 @@ static void SendTo(Service::Interface* self) {
u32 flags = cmd_buffer[3];
u32 addr_len = cmd_buffer[4];
u8* input_buff = Memory::GetPointer(cmd_buffer[8]);
CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[10]));
if (ctr_dest_addr == nullptr) {
VAddr input_buff_address = cmd_buffer[8];
if (!Memory::IsValidVirtualAddress(input_buff_address)) {
cmd_buffer[1] = -1; // TODO(Subv): Find the right error code
return;
}
// Memory address of the dest_addr structure
VAddr dest_addr_addr = cmd_buffer[10];
if (!Memory::IsValidVirtualAddress(dest_addr_addr)) {
cmd_buffer[1] = -1; // TODO(Subv): Find the right error code
return;
}
std::vector<u8> input_buff(len);
Memory::ReadBlock(input_buff_address, input_buff.data(), input_buff.size());
CTRSockAddr ctr_dest_addr;
Memory::ReadBlock(dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr));
int ret = -1;
if (addr_len > 0) {
sockaddr dest_addr = CTRSockAddr::ToPlatform(*ctr_dest_addr);
ret = ::sendto(socket_handle, (const char*)input_buff, len, flags, &dest_addr, sizeof(dest_addr));
sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr);
ret = ::sendto(socket_handle, reinterpret_cast<const char*>(input_buff.data()), len, flags, &dest_addr, sizeof(dest_addr));
} else {
ret = ::sendto(socket_handle, (const char*)input_buff, len, flags, nullptr, 0);
ret = ::sendto(socket_handle, reinterpret_cast<const char*>(input_buff.data()), len, flags, nullptr, 0);
}
int result = 0;
@ -591,14 +606,24 @@ static void RecvFrom(Service::Interface* self) {
std::memcpy(&buffer_parameters, &cmd_buffer[64], sizeof(buffer_parameters));
u8* output_buff = Memory::GetPointer(buffer_parameters.output_buffer_addr);
if (!Memory::IsValidVirtualAddress(buffer_parameters.output_buffer_addr)) {
cmd_buffer[1] = -1; // TODO(Subv): Find the right error code
return;
}
if (!Memory::IsValidVirtualAddress(buffer_parameters.output_src_address_buffer)) {
cmd_buffer[1] = -1; // TODO(Subv): Find the right error code
return;
}
std::vector<u8> output_buff(len);
sockaddr src_addr;
socklen_t src_addr_len = sizeof(src_addr);
int ret = ::recvfrom(socket_handle, (char*)output_buff, len, flags, &src_addr, &src_addr_len);
int ret = ::recvfrom(socket_handle, reinterpret_cast<char*>(output_buff.data()), len, flags, &src_addr, &src_addr_len);
if (ret >= 0 && buffer_parameters.output_src_address_buffer != 0 && src_addr_len > 0) {
CTRSockAddr* ctr_src_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(buffer_parameters.output_src_address_buffer));
*ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
CTRSockAddr ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
Memory::WriteBlock(buffer_parameters.output_src_address_buffer, &ctr_src_addr, sizeof(ctr_src_addr));
}
int result = 0;
@ -606,6 +631,9 @@ static void RecvFrom(Service::Interface* self) {
if (ret == SOCKET_ERROR_VALUE) {
result = TranslateError(GET_ERRNO);
total_received = 0;
} else {
// Write only the data we received to avoid overwriting parts of the buffer with zeros
Memory::WriteBlock(buffer_parameters.output_buffer_addr, output_buff.data(), total_received);
}
cmd_buffer[1] = result;
@ -617,18 +645,28 @@ static void Poll(Service::Interface* self) {
u32* cmd_buffer = Kernel::GetCommandBuffer();
u32 nfds = cmd_buffer[1];
int timeout = cmd_buffer[2];
CTRPollFD* input_fds = reinterpret_cast<CTRPollFD*>(Memory::GetPointer(cmd_buffer[6]));
CTRPollFD* output_fds = reinterpret_cast<CTRPollFD*>(Memory::GetPointer(cmd_buffer[0x104 >> 2]));
VAddr input_fds_addr = cmd_buffer[6];
VAddr output_fds_addr = cmd_buffer[0x104 >> 2];
if (!Memory::IsValidVirtualAddress(input_fds_addr) || !Memory::IsValidVirtualAddress(output_fds_addr)) {
cmd_buffer[1] = -1; // TODO(Subv): Find correct error code.
return;
}
std::vector<CTRPollFD> ctr_fds(nfds);
Memory::ReadBlock(input_fds_addr, ctr_fds.data(), nfds * sizeof(CTRPollFD));
// The 3ds_pollfd and the pollfd structures may be different (Windows/Linux have different sizes)
// so we have to copy the data
std::vector<pollfd> platform_pollfd(nfds);
std::transform(input_fds, input_fds + nfds, platform_pollfd.begin(), CTRPollFD::ToPlatform);
std::transform(ctr_fds.begin(), ctr_fds.end(), platform_pollfd.begin(), CTRPollFD::ToPlatform);
const int ret = ::poll(platform_pollfd.data(), nfds, timeout);
// Now update the output pollfd structure
std::transform(platform_pollfd.begin(), platform_pollfd.end(), output_fds, CTRPollFD::FromPlatform);
std::transform(platform_pollfd.begin(), platform_pollfd.end(), ctr_fds.begin(), CTRPollFD::FromPlatform);
Memory::WriteBlock(output_fds_addr, ctr_fds.data(), nfds * sizeof(CTRPollFD));
int result = 0;
if (ret == SOCKET_ERROR_VALUE)
@ -643,14 +681,16 @@ static void GetSockName(Service::Interface* self) {
u32 socket_handle = cmd_buffer[1];
socklen_t ctr_len = cmd_buffer[2];
CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[0x104 >> 2]));
// Memory address of the ctr_dest_addr structure
VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2];
sockaddr dest_addr;
socklen_t dest_addr_len = sizeof(dest_addr);
int ret = ::getsockname(socket_handle, &dest_addr, &dest_addr_len);
if (ctr_dest_addr != nullptr) {
*ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
if (ctr_dest_addr_addr != 0 && Memory::IsValidVirtualAddress(ctr_dest_addr_addr)) {
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
Memory::WriteBlock(ctr_dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr));
} else {
cmd_buffer[1] = -1; // TODO(Subv): Verify error
return;
@ -682,14 +722,16 @@ static void GetPeerName(Service::Interface* self) {
u32 socket_handle = cmd_buffer[1];
socklen_t len = cmd_buffer[2];
CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[0x104 >> 2]));
// Memory address of the ctr_dest_addr structure
VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2];
sockaddr dest_addr;
socklen_t dest_addr_len = sizeof(dest_addr);
int ret = ::getpeername(socket_handle, &dest_addr, &dest_addr_len);
if (ctr_dest_addr != nullptr) {
*ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
if (ctr_dest_addr_addr != 0 && Memory::IsValidVirtualAddress(ctr_dest_addr_addr)) {
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
Memory::WriteBlock(ctr_dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr));
} else {
cmd_buffer[1] = -1;
return;
@ -711,13 +753,17 @@ static void Connect(Service::Interface* self) {
u32 socket_handle = cmd_buffer[1];
socklen_t len = cmd_buffer[2];
CTRSockAddr* ctr_input_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[6]));
if (ctr_input_addr == nullptr) {
// Memory address of the ctr_input_addr structure
VAddr ctr_input_addr_addr = cmd_buffer[6];
if (!Memory::IsValidVirtualAddress(ctr_input_addr_addr)) {
cmd_buffer[1] = -1; // TODO(Subv): Verify error
return;
}
sockaddr input_addr = CTRSockAddr::ToPlatform(*ctr_input_addr);
CTRSockAddr ctr_input_addr;
Memory::ReadBlock(ctr_input_addr_addr, &ctr_input_addr, sizeof(ctr_input_addr));
sockaddr input_addr = CTRSockAddr::ToPlatform(ctr_input_addr);
int ret = ::connect(socket_handle, &input_addr, sizeof(input_addr));
int result = 0;
if (ret != 0)

View File

@ -31,7 +31,6 @@ static void GenerateRandomData(Service::Interface* self) {
u32 size = cmd_buff[1];
VAddr address = cmd_buff[3];
u8* output_buff = Memory::GetPointer(address);
// Fill the output buffer with random data.
u32 data = 0;
@ -44,13 +43,13 @@ static void GenerateRandomData(Service::Interface* self) {
if (size > 4) {
// Use up the entire 4 bytes of the random data for as long as possible
*(u32*)(output_buff + i) = data;
Memory::Write32(address + i, data);
i += 4;
} else if (size == 2) {
*(u16*)(output_buff + i) = (u16)(data & 0xffff);
Memory::Write16(address + i, static_cast<u16>(data & 0xffff));
i += 2;
} else {
*(u8*)(output_buff + i) = (u8)(data & 0xff);
Memory::Write8(address + i, static_cast<u8>(data & 0xff));
i++;
}
}

View File

@ -6,6 +6,7 @@
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "common/scope_exit.h"
#include "common/string_util.h"
#include "common/symbols.h"
@ -326,9 +327,9 @@ static ResultCode WaitSynchronizationN(s32* out, Handle* handles, s32 handle_cou
}
}
HLE::Reschedule(__func__);
SCOPE_EXIT({HLE::Reschedule("WaitSynchronizationN");}); // Reschedule after putting the threads to sleep.
// If thread should wait, then set its state to waiting and then reschedule...
// If thread should wait, then set its state to waiting
if (wait_thread) {
// Actually wait the current thread on each object if we decided to wait...

View File

@ -10,6 +10,7 @@
#include "core/file_sys/archive_romfs.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/service/fs/archive.h"
#include "core/loader/3dsx.h"
#include "core/memory.h"
@ -263,6 +264,8 @@ ResultStatus AppLoader_THREEDSX::Load() {
Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE);
Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), Service::FS::ArchiveIdCode::RomFS);
is_loaded = true;
return ResultStatus::Success;
}

View File

@ -27,6 +27,14 @@ public:
*/
static FileType IdentifyType(FileUtil::IOFile& file);
/**
* Returns the type of this file
* @return FileType corresponding to the loaded file
*/
FileType GetFileType() override {
return IdentifyType(file);
}
/**
* Load the bootable file
* @return ResultStatus result of function

View File

@ -27,6 +27,14 @@ public:
*/
static FileType IdentifyType(FileUtil::IOFile& file);
/**
* Returns the type of this file
* @return FileType corresponding to the loaded file
*/
FileType GetFileType() override {
return IdentifyType(file);
}
/**
* Load the bootable file
* @return ResultStatus result of function

View File

@ -8,9 +8,7 @@
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/file_sys/archive_romfs.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/fs/archive.h"
#include "core/loader/3dsx.h"
#include "core/loader/elf.h"
#include "core/loader/ncch.h"
@ -67,6 +65,9 @@ FileType GuessFromExtension(const std::string& extension_) {
if (extension == ".3dsx")
return FileType::THREEDSX;
if (extension == ".cia")
return FileType::CIA;
return FileType::Unknown;
}
@ -90,7 +91,15 @@ const char* GetFileTypeString(FileType type) {
return "unknown";
}
std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type,
/**
* Get a loader for a file with a specific type
* @param file The file to load
* @param type The type of the file
* @param filename the file name (without path)
* @param filepath the file full path (with name)
* @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
*/
static std::unique_ptr<AppLoader> GetFileLoader(FileUtil::IOFile&& file, FileType type,
const std::string& filename, const std::string& filepath) {
switch (type) {
@ -108,15 +117,15 @@ std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type,
return std::make_unique<AppLoader_NCCH>(std::move(file), filepath);
default:
return std::unique_ptr<AppLoader>();
return nullptr;
}
}
ResultStatus LoadFile(const std::string& filename) {
std::unique_ptr<AppLoader> GetLoader(const std::string& filename) {
FileUtil::IOFile file(filename, "rb");
if (!file.IsOpen()) {
LOG_ERROR(Loader, "Failed to load file %s", filename.c_str());
return ResultStatus::Error;
return nullptr;
}
std::string filename_filename, filename_extension;
@ -133,44 +142,7 @@ ResultStatus LoadFile(const std::string& filename) {
LOG_INFO(Loader, "Loading file %s as %s...", filename.c_str(), GetFileTypeString(type));
std::unique_ptr<AppLoader> app_loader = GetLoader(std::move(file), type, filename_filename, filename);
switch (type) {
// 3DSX file format...
// or NCCH/NCSD container formats...
case FileType::THREEDSX:
case FileType::CXI:
case FileType::CCI:
{
// Load application and RomFS
ResultStatus result = app_loader->Load();
if (ResultStatus::Success == result) {
Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*app_loader), Service::FS::ArchiveIdCode::RomFS);
return ResultStatus::Success;
}
return result;
}
// Standard ELF file format...
case FileType::ELF:
return app_loader->Load();
// CIA file format...
case FileType::CIA:
return ResultStatus::ErrorNotImplemented;
// Error occurred durring IdentifyFile...
case FileType::Error:
// IdentifyFile could know identify file type...
case FileType::Unknown:
{
LOG_CRITICAL(Loader, "File %s is of unknown type.", filename.c_str());
return ResultStatus::ErrorInvalidFormat;
}
}
return ResultStatus::Error;
return GetFileLoader(std::move(file), type, filename_filename, filename);
}
} // namespace Loader

View File

@ -10,10 +10,8 @@
#include <string>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/swap.h"
namespace Kernel {
struct AddressMapping;
@ -80,57 +78,18 @@ constexpr u32 MakeMagic(char a, char b, char c, char d) {
return a | b << 8 | c << 16 | d << 24;
}
/// SMDH data structure that contains titles, icons etc. See https://www.3dbrew.org/wiki/SMDH
struct SMDH {
u32_le magic;
u16_le version;
INSERT_PADDING_BYTES(2);
struct Title {
std::array<u16, 0x40> short_title;
std::array<u16, 0x80> long_title;
std::array<u16, 0x40> publisher;
};
std::array<Title, 16> titles;
std::array<u8, 16> ratings;
u32_le region_lockout;
u32_le match_maker_id;
u64_le match_maker_bit_id;
u32_le flags;
u16_le eula_version;
INSERT_PADDING_BYTES(2);
float_le banner_animation_frame;
u32_le cec_id;
INSERT_PADDING_BYTES(8);
std::array<u8, 0x480> small_icon;
std::array<u8, 0x1200> large_icon;
/// indicates the language used for each title entry
enum class TitleLanguage {
Japanese = 0,
English = 1,
French = 2,
German = 3,
Italian = 4,
Spanish = 5,
SimplifiedChinese = 6,
Korean= 7,
Dutch = 8,
Portuguese = 9,
Russian = 10,
TraditionalChinese = 11
};
};
static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong");
/// Interface for loading an application
class AppLoader : NonCopyable {
public:
AppLoader(FileUtil::IOFile&& file) : file(std::move(file)) { }
virtual ~AppLoader() { }
/**
* Returns the type of this file
* @return FileType corresponding to the loaded file
*/
virtual FileType GetFileType() = 0;
/**
* Load the application
* @return ResultStatus result of function
@ -197,20 +156,10 @@ protected:
extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings;
/**
* Get a loader for a file with a specific type
* @param file The file to load
* @param type The type of the file
* @param filename the file name (without path)
* @param filepath the file full path (with name)
* @return std::unique_ptr<AppLoader> a pointer to a loader object; nullptr for unsupported type
*/
std::unique_ptr<AppLoader> GetLoader(FileUtil::IOFile&& file, FileType type, const std::string& filename, const std::string& filepath);
/**
* Identifies and loads a bootable file
* Identifies a bootable file and return a suitable loader
* @param filename String filename of bootable file
* @return ResultStatus result of function
* @return best loader for this file
*/
ResultStatus LoadFile(const std::string& filename);
std::unique_ptr<AppLoader> GetLoader(const std::string& filename);
} // namespace

View File

@ -10,8 +10,10 @@
#include "common/string_util.h"
#include "common/swap.h"
#include "core/file_sys/archive_romfs.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/service/fs/archive.h"
#include "core/loader/ncch.h"
#include "core/memory.h"
@ -303,7 +305,12 @@ ResultStatus AppLoader_NCCH::Load() {
is_loaded = true; // Set state to loaded
return LoadExec(); // Load the executable into memory for booting
result = LoadExec(); // Load the executable into memory for booting
if (ResultStatus::Success != result)
return result;
Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this), Service::FS::ArchiveIdCode::RomFS);
return ResultStatus::Success;
}
ResultStatus AppLoader_NCCH::ReadCode(std::vector<u8>& buffer) {

View File

@ -173,6 +173,14 @@ public:
*/
static FileType IdentifyType(FileUtil::IOFile& file);
/**
* Returns the type of this file
* @return FileType corresponding to the loaded file
*/
FileType GetFileType() override {
return IdentifyType(file);
}
/**
* Load the application
* @return ResultStatus result of function

54
src/core/loader/smdh.cpp Normal file
View File

@ -0,0 +1,54 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstring>
#include <vector>
#include "common/common_types.h"
#include "core/loader/loader.h"
#include "core/loader/smdh.h"
#include "video_core/utils.h"
namespace Loader {
bool IsValidSMDH(const std::vector<u8>& smdh_data) {
if (smdh_data.size() < sizeof(Loader::SMDH))
return false;
u32 magic;
memcpy(&magic, smdh_data.data(), sizeof(u32));
return Loader::MakeMagic('S', 'M', 'D', 'H') == magic;
}
std::vector<u16> SMDH::GetIcon(bool large) const {
u32 size;
const u8* icon_data;
if (large) {
size = 48;
icon_data = large_icon.data();
} else {
size = 24;
icon_data = small_icon.data();
}
std::vector<u16> icon(size * size);
for (u32 x = 0; x < size; ++x) {
for (u32 y = 0; y < size; ++y) {
u32 coarse_y = y & ~7;
const u8* pixel = icon_data + VideoCore::GetMortonOffset(x, y, 2) + coarse_y * size * 2;
icon[x + size * y] = (pixel[1] << 8) + pixel[0];
}
}
return icon;
}
std::array<u16, 0x40> SMDH::GetShortTitle(Loader::SMDH::TitleLanguage language) const {
return titles[static_cast<int>(language)].short_title;
}
} // namespace

82
src/core/loader/smdh.h Normal file
View File

@ -0,0 +1,82 @@
// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/swap.h"
namespace Loader {
/**
* Tests if data is a valid SMDH by its length and magic number.
* @param smdh_data data buffer to test
* @return bool test result
*/
bool IsValidSMDH(const std::vector<u8>& smdh_data);
/// SMDH data structure that contains titles, icons etc. See https://www.3dbrew.org/wiki/SMDH
struct SMDH {
u32_le magic;
u16_le version;
INSERT_PADDING_BYTES(2);
struct Title {
std::array<u16, 0x40> short_title;
std::array<u16, 0x80> long_title;
std::array<u16, 0x40> publisher;
};
std::array<Title, 16> titles;
std::array<u8, 16> ratings;
u32_le region_lockout;
u32_le match_maker_id;
u64_le match_maker_bit_id;
u32_le flags;
u16_le eula_version;
INSERT_PADDING_BYTES(2);
float_le banner_animation_frame;
u32_le cec_id;
INSERT_PADDING_BYTES(8);
std::array<u8, 0x480> small_icon;
std::array<u8, 0x1200> large_icon;
/// indicates the language used for each title entry
enum class TitleLanguage {
Japanese = 0,
English = 1,
French = 2,
German = 3,
Italian = 4,
Spanish = 5,
SimplifiedChinese = 6,
Korean= 7,
Dutch = 8,
Portuguese = 9,
Russian = 10,
TraditionalChinese = 11
};
/**
* Gets game icon from SMDH
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
* @return vector of RGB565 data
*/
std::vector<u16> GetIcon(bool large) const;
/**
* Gets the short game title from SMDH
* @param language title language
* @return UTF-16 array of the short title
*/
std::array<u16, 0x40> GetShortTitle(Loader::SMDH::TitleLanguage language) const;
};
static_assert(sizeof(SMDH) == 0x36C0, "SMDH structure size is wrong");
} // namespace

View File

@ -246,6 +246,26 @@ void Write(const VAddr vaddr, const T data) {
}
}
bool IsValidVirtualAddress(const VAddr vaddr) {
const u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
if (page_pointer)
return true;
if (current_page_table->attributes[vaddr >> PAGE_BITS] != PageType::Special)
return false;
MMIORegionPointer mmio_region = GetMMIOHandler(vaddr);
if (mmio_region) {
return mmio_region->IsValidAddress(vaddr);
}
return false;
}
bool IsValidPhysicalAddress(const PAddr paddr) {
return IsValidVirtualAddress(PhysicalToVirtualAddress(paddr));
}
u8* GetPointer(const VAddr vaddr) {
u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
if (page_pointer) {
@ -261,6 +281,7 @@ u8* GetPointer(const VAddr vaddr) {
}
u8* GetPhysicalPointer(PAddr address) {
// TODO(Subv): This call should not go through the application's memory mapping.
return GetPointer(PhysicalToVirtualAddress(address));
}
@ -343,6 +364,59 @@ u64 Read64(const VAddr addr) {
return Read<u64_le>(addr);
}
void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) {
size_t remaining_size = size;
size_t page_index = src_addr >> PAGE_BITS;
size_t page_offset = src_addr & PAGE_MASK;
while (remaining_size > 0) {
const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size);
const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset;
switch (current_page_table->attributes[page_index]) {
case PageType::Unmapped: {
LOG_ERROR(HW_Memory, "unmapped ReadBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, src_addr, size);
std::memset(dest_buffer, 0, copy_amount);
break;
}
case PageType::Memory: {
DEBUG_ASSERT(current_page_table->pointers[page_index]);
const u8* src_ptr = current_page_table->pointers[page_index] + page_offset;
std::memcpy(dest_buffer, src_ptr, copy_amount);
break;
}
case PageType::Special: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount);
break;
}
case PageType::RasterizerCachedMemory: {
RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
std::memcpy(dest_buffer, GetPointerFromVMA(current_vaddr), copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount);
break;
}
default:
UNREACHABLE();
}
page_index++;
page_offset = 0;
dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount;
remaining_size -= copy_amount;
}
}
void Write8(const VAddr addr, const u8 data) {
Write<u8>(addr, data);
}
@ -359,9 +433,165 @@ void Write64(const VAddr addr, const u64 data) {
Write<u64_le>(addr, data);
}
void WriteBlock(const VAddr addr, const u8* data, const size_t size) {
for (u32 offset = 0; offset < size; offset++) {
Write8(addr + offset, data[offset]);
void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) {
size_t remaining_size = size;
size_t page_index = dest_addr >> PAGE_BITS;
size_t page_offset = dest_addr & PAGE_MASK;
while (remaining_size > 0) {
const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size);
const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset;
switch (current_page_table->attributes[page_index]) {
case PageType::Unmapped: {
LOG_ERROR(HW_Memory, "unmapped WriteBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, dest_addr, size);
break;
}
case PageType::Memory: {
DEBUG_ASSERT(current_page_table->pointers[page_index]);
u8* dest_ptr = current_page_table->pointers[page_index] + page_offset;
std::memcpy(dest_ptr, src_buffer, copy_amount);
break;
}
case PageType::Special: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount);
break;
}
case PageType::RasterizerCachedMemory: {
RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
std::memcpy(GetPointerFromVMA(current_vaddr), src_buffer, copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount);
break;
}
default:
UNREACHABLE();
}
page_index++;
page_offset = 0;
src_buffer = static_cast<const u8*>(src_buffer) + copy_amount;
remaining_size -= copy_amount;
}
}
void ZeroBlock(const VAddr dest_addr, const size_t size) {
size_t remaining_size = size;
size_t page_index = dest_addr >> PAGE_BITS;
size_t page_offset = dest_addr & PAGE_MASK;
static const std::array<u8, PAGE_SIZE> zeros = {};
while (remaining_size > 0) {
const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size);
const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset;
switch (current_page_table->attributes[page_index]) {
case PageType::Unmapped: {
LOG_ERROR(HW_Memory, "unmapped ZeroBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, dest_addr, size);
break;
}
case PageType::Memory: {
DEBUG_ASSERT(current_page_table->pointers[page_index]);
u8* dest_ptr = current_page_table->pointers[page_index] + page_offset;
std::memset(dest_ptr, 0, copy_amount);
break;
}
case PageType::Special: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount);
break;
}
case PageType::RasterizerCachedMemory: {
RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount);
break;
}
default:
UNREACHABLE();
}
page_index++;
page_offset = 0;
remaining_size -= copy_amount;
}
}
void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
size_t remaining_size = size;
size_t page_index = src_addr >> PAGE_BITS;
size_t page_offset = src_addr & PAGE_MASK;
while (remaining_size > 0) {
const size_t copy_amount = std::min(PAGE_SIZE - page_offset, remaining_size);
const VAddr current_vaddr = (page_index << PAGE_BITS) + page_offset;
switch (current_page_table->attributes[page_index]) {
case PageType::Unmapped: {
LOG_ERROR(HW_Memory, "unmapped CopyBlock @ 0x%08X (start address = 0x%08X, size = %zu)", current_vaddr, src_addr, size);
ZeroBlock(dest_addr, copy_amount);
break;
}
case PageType::Memory: {
DEBUG_ASSERT(current_page_table->pointers[page_index]);
const u8* src_ptr = current_page_table->pointers[page_index] + page_offset;
WriteBlock(dest_addr, src_ptr, copy_amount);
break;
}
case PageType::Special: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
std::vector<u8> buffer(copy_amount);
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size());
WriteBlock(dest_addr, buffer.data(), buffer.size());
break;
}
case PageType::RasterizerCachedMemory: {
RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
std::vector<u8> buffer(copy_amount);
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size());
WriteBlock(dest_addr, buffer.data(), buffer.size());
break;
}
default:
UNREACHABLE();
}
page_index++;
page_offset = 0;
dest_addr += copy_amount;
src_addr += copy_amount;
remaining_size -= copy_amount;
}
}

View File

@ -110,6 +110,9 @@ enum : VAddr {
NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE,
};
bool IsValidVirtualAddress(const VAddr addr);
bool IsValidPhysicalAddress(const PAddr addr);
u8 Read8(VAddr addr);
u16 Read16(VAddr addr);
u32 Read32(VAddr addr);
@ -120,7 +123,10 @@ void Write16(VAddr addr, u16 data);
void Write32(VAddr addr, u32 data);
void Write64(VAddr addr, u64 data);
void WriteBlock(VAddr addr, const u8* data, size_t size);
void ReadBlock(const VAddr src_addr, void* dest_buffer, size_t size);
void WriteBlock(const VAddr dest_addr, const void* src_buffer, size_t size);
void ZeroBlock(const VAddr dest_addr, const size_t size);
void CopyBlock(VAddr dest_addr, VAddr src_addr, size_t size);
u8* GetPointer(VAddr virtual_address);

View File

@ -6,7 +6,7 @@
#include "common/common_types.h"
#include "core/memory.h"
#include "core/mmio.h"
namespace Memory {

View File

@ -18,15 +18,21 @@ class MMIORegion {
public:
virtual ~MMIORegion() = default;
virtual bool IsValidAddress(VAddr addr) = 0;
virtual u8 Read8(VAddr addr) = 0;
virtual u16 Read16(VAddr addr) = 0;
virtual u32 Read32(VAddr addr) = 0;
virtual u64 Read64(VAddr addr) = 0;
virtual bool ReadBlock(VAddr src_addr, void* dest_buffer, size_t size) = 0;
virtual void Write8(VAddr addr, u8 data) = 0;
virtual void Write16(VAddr addr, u16 data) = 0;
virtual void Write32(VAddr addr, u32 data) = 0;
virtual void Write64(VAddr addr, u64 data) = 0;
virtual bool WriteBlock(VAddr dest_addr, const void* src_buffer, size_t size) = 0;
};
using MMIORegionPointer = std::shared_ptr<MMIORegion>;

View File

@ -13,36 +13,51 @@ namespace Settings {
namespace NativeInput {
enum Values {
// directly mapped keys
A, B, X, Y,
L, R, ZL, ZR,
START, SELECT, HOME,
DUP, DDOWN, DLEFT, DRIGHT,
SUP, SDOWN, SLEFT, SRIGHT,
CUP, CDOWN, CLEFT, CRIGHT,
// indirectly mapped keys
CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT,
CIRCLE_MODIFIER,
NUM_INPUTS
};
static const std::array<const char*, NUM_INPUTS> Mapping = {{
// directly mapped keys
"pad_a", "pad_b", "pad_x", "pad_y",
"pad_l", "pad_r", "pad_zl", "pad_zr",
"pad_start", "pad_select", "pad_home",
"pad_dup", "pad_ddown", "pad_dleft", "pad_dright",
"pad_sup", "pad_sdown", "pad_sleft", "pad_sright",
"pad_cup", "pad_cdown", "pad_cleft", "pad_cright"
"pad_cup", "pad_cdown", "pad_cleft", "pad_cright",
// indirectly mapped keys
"pad_circle_up", "pad_circle_down", "pad_circle_left", "pad_circle_right",
"pad_circle_modifier",
}};
static const std::array<Values, NUM_INPUTS> All = {{
A, B, X, Y,
L, R, ZL, ZR,
START, SELECT, HOME,
DUP, DDOWN, DLEFT, DRIGHT,
SUP, SDOWN, SLEFT, SRIGHT,
CUP, CDOWN, CLEFT, CRIGHT
CUP, CDOWN, CLEFT, CRIGHT,
CIRCLE_UP, CIRCLE_DOWN, CIRCLE_LEFT, CIRCLE_RIGHT,
CIRCLE_MODIFIER,
}};
}
struct Values {
// CheckNew3DS
bool is_new_3ds;
// Controls
std::array<int, NativeInput::NUM_INPUTS> input_mappings;
float pad_circle_modifier_scale;
// Core
int frame_skip;

View File

@ -149,7 +149,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
// Send to vertex shader
if (g_debug_context)
g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, static_cast<void*>(&immediate_input));
Shader::OutputVertex output = g_state.vs.Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1);
g_state.vs.Run(shader_unit, immediate_input, regs.vs.num_input_attributes+1);
Shader::OutputVertex output_vertex = shader_unit.output_registers.ToVertex(regs.vs);
// Send to renderer
using Pica::Shader::OutputVertex;
@ -157,7 +158,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
};
g_state.primitive_assembler.SubmitVertex(output, AddTriangle);
g_state.primitive_assembler.SubmitVertex(output_vertex, AddTriangle);
}
}
}
@ -199,9 +200,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
// Processes information about internal vertex attributes to figure out how a vertex is loaded.
// Later, these can be compiled and cached.
VertexLoader loader;
const u32 base_address = regs.vertex_attributes.GetPhysicalBaseAddress();
loader.Setup(regs);
VertexLoader loader(regs);
// Load vertices
bool is_indexed = (id == PICA_REG_INDEX(trigger_draw_indexed));
@ -231,7 +231,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
// The size has been tuned for optimal balance between hit-rate and the cost of lookup
const size_t VERTEX_CACHE_SIZE = 32;
std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids;
std::array<Shader::OutputVertex, VERTEX_CACHE_SIZE> vertex_cache;
std::array<Shader::OutputRegisters, VERTEX_CACHE_SIZE> vertex_cache;
unsigned int vertex_cache_pos = 0;
vertex_cache_ids.fill(-1);
@ -249,7 +249,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
ASSERT(vertex != -1);
bool vertex_cache_hit = false;
Shader::OutputVertex output;
Shader::OutputRegisters output_registers;
if (is_indexed) {
if (g_debug_context && Pica::g_debug_context->recorder) {
@ -259,7 +259,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) {
if (vertex == vertex_cache_ids[i]) {
output = vertex_cache[i];
output_registers = vertex_cache[i];
vertex_cache_hit = true;
break;
}
@ -274,15 +274,19 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
// Send to vertex shader
if (g_debug_context)
g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation, (void*)&input);
output = g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes());
g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes());
output_registers = shader_unit.output_registers;
if (is_indexed) {
vertex_cache[vertex_cache_pos] = output;
vertex_cache[vertex_cache_pos] = output_registers;
vertex_cache_ids[vertex_cache_pos] = vertex;
vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE;
}
}
// Retreive vertex from register data
Shader::OutputVertex output_vertex = output_registers.ToVertex(regs.vs);
// Send to renderer
using Pica::Shader::OutputVertex;
auto AddTriangle = [](
@ -290,7 +294,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
};
primitive_assembler.SubmitVertex(output, AddTriangle);
primitive_assembler.SubmitVertex(output_vertex, AddTriangle);
}
for (auto& range : memory_accesses.ranges) {

View File

@ -787,7 +787,6 @@ struct Regs {
LightColor diffuse; // material.diffuse * light.diffuse
LightColor ambient; // material.ambient * light.ambient
struct {
// Encoded as 16-bit floating point
union {
BitField< 0, 16, u32> x;
@ -802,8 +801,7 @@ struct Regs {
union {
BitField<0, 1, u32> directional;
BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0
};
};
} config;
BitField<0, 20, u32> dist_atten_bias;
BitField<0, 20, u32> dist_atten_scale;
@ -824,7 +822,7 @@ struct Regs {
BitField<27, 1, u32> clamp_highlights;
BitField<28, 2, LightingBumpMode> bump_mode;
BitField<30, 1, u32> disable_bump_renorm;
};
} config0;
union {
BitField<16, 1, u32> disable_lut_d0;
@ -845,13 +843,13 @@ struct Regs {
BitField<29, 1, u32> disable_dist_atten_light_5;
BitField<30, 1, u32> disable_dist_atten_light_6;
BitField<31, 1, u32> disable_dist_atten_light_7;
};
} config1;
bool IsDistAttenDisabled(unsigned index) const {
const unsigned disable[] = { disable_dist_atten_light_0, disable_dist_atten_light_1,
disable_dist_atten_light_2, disable_dist_atten_light_3,
disable_dist_atten_light_4, disable_dist_atten_light_5,
disable_dist_atten_light_6, disable_dist_atten_light_7 };
const unsigned disable[] = { config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1,
config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3,
config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5,
config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7 };
return disable[index] != 0;
}

View File

@ -380,6 +380,17 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
SyncCombinerColor();
break;
// Fragment lighting switches
case PICA_REG_INDEX(lighting.disable):
case PICA_REG_INDEX(lighting.num_lights):
case PICA_REG_INDEX(lighting.config0):
case PICA_REG_INDEX(lighting.config1):
case PICA_REG_INDEX(lighting.abs_lut_input):
case PICA_REG_INDEX(lighting.lut_input):
case PICA_REG_INDEX(lighting.lut_scale):
case PICA_REG_INDEX(lighting.light_enable):
break;
// Fragment lighting specular 0 color
case PICA_REG_INDEX_WORKAROUND(lighting.light[0].specular_0, 0x140 + 0 * 0x10):
SyncLightSpecular0(0);
@ -518,6 +529,70 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
SyncLightPosition(7);
break;
// Fragment lighting light source config
case PICA_REG_INDEX_WORKAROUND(lighting.light[0].config, 0x149 + 0 * 0x10):
case PICA_REG_INDEX_WORKAROUND(lighting.light[1].config, 0x149 + 1 * 0x10):
case PICA_REG_INDEX_WORKAROUND(lighting.light[2].config, 0x149 + 2 * 0x10):
case PICA_REG_INDEX_WORKAROUND(lighting.light[3].config, 0x149 + 3 * 0x10):
case PICA_REG_INDEX_WORKAROUND(lighting.light[4].config, 0x149 + 4 * 0x10):
case PICA_REG_INDEX_WORKAROUND(lighting.light[5].config, 0x149 + 5 * 0x10):
case PICA_REG_INDEX_WORKAROUND(lighting.light[6].config, 0x149 + 6 * 0x10):
case PICA_REG_INDEX_WORKAROUND(lighting.light[7].config, 0x149 + 7 * 0x10):
shader_dirty = true;
break;
// Fragment lighting distance attenuation bias
case PICA_REG_INDEX_WORKAROUND(lighting.light[0].dist_atten_bias, 0x014A + 0 * 0x10):
SyncLightDistanceAttenuationBias(0);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[1].dist_atten_bias, 0x014A + 1 * 0x10):
SyncLightDistanceAttenuationBias(1);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[2].dist_atten_bias, 0x014A + 2 * 0x10):
SyncLightDistanceAttenuationBias(2);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[3].dist_atten_bias, 0x014A + 3 * 0x10):
SyncLightDistanceAttenuationBias(3);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[4].dist_atten_bias, 0x014A + 4 * 0x10):
SyncLightDistanceAttenuationBias(4);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[5].dist_atten_bias, 0x014A + 5 * 0x10):
SyncLightDistanceAttenuationBias(5);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[6].dist_atten_bias, 0x014A + 6 * 0x10):
SyncLightDistanceAttenuationBias(6);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[7].dist_atten_bias, 0x014A + 7 * 0x10):
SyncLightDistanceAttenuationBias(7);
break;
// Fragment lighting distance attenuation scale
case PICA_REG_INDEX_WORKAROUND(lighting.light[0].dist_atten_scale, 0x014B + 0 * 0x10):
SyncLightDistanceAttenuationScale(0);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[1].dist_atten_scale, 0x014B + 1 * 0x10):
SyncLightDistanceAttenuationScale(1);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[2].dist_atten_scale, 0x014B + 2 * 0x10):
SyncLightDistanceAttenuationScale(2);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[3].dist_atten_scale, 0x014B + 3 * 0x10):
SyncLightDistanceAttenuationScale(3);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[4].dist_atten_scale, 0x014B + 4 * 0x10):
SyncLightDistanceAttenuationScale(4);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[5].dist_atten_scale, 0x014B + 5 * 0x10):
SyncLightDistanceAttenuationScale(5);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[6].dist_atten_scale, 0x014B + 6 * 0x10):
SyncLightDistanceAttenuationScale(6);
break;
case PICA_REG_INDEX_WORKAROUND(lighting.light[7].dist_atten_scale, 0x014B + 7 * 0x10):
SyncLightDistanceAttenuationScale(7);
break;
// Fragment lighting global ambient color (emission + ambient * ambient)
case PICA_REG_INDEX_WORKAROUND(lighting.global_ambient, 0x1c0):
SyncGlobalAmbient();
@ -896,6 +971,8 @@ void RasterizerOpenGL::SetShader() {
SyncLightDiffuse(light_index);
SyncLightAmbient(light_index);
SyncLightPosition(light_index);
SyncLightDistanceAttenuationBias(light_index);
SyncLightDistanceAttenuationScale(light_index);
}
}
}
@ -1105,3 +1182,21 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) {
uniform_block_data.dirty = true;
}
}
void RasterizerOpenGL::SyncLightDistanceAttenuationBias(int light_index) {
GLfloat dist_atten_bias = Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_bias).ToFloat32();
if (dist_atten_bias != uniform_block_data.data.light_src[light_index].dist_atten_bias) {
uniform_block_data.data.light_src[light_index].dist_atten_bias = dist_atten_bias;
uniform_block_data.dirty = true;
}
}
void RasterizerOpenGL::SyncLightDistanceAttenuationScale(int light_index) {
GLfloat dist_atten_scale = Pica::float20::FromRaw(Pica::g_state.regs.lighting.light[light_index].dist_atten_scale).ToFloat32();
if (dist_atten_scale != uniform_block_data.data.light_src[light_index].dist_atten_scale) {
uniform_block_data.data.light_src[light_index].dist_atten_scale = dist_atten_scale;
uniform_block_data.dirty = true;
}
}

View File

@ -89,49 +89,47 @@ union PicaShaderConfig {
unsigned num = regs.lighting.light_enable.GetNum(light_index);
const auto& light = regs.lighting.light[num];
state.lighting.light[light_index].num = num;
state.lighting.light[light_index].directional = light.directional != 0;
state.lighting.light[light_index].two_sided_diffuse = light.two_sided_diffuse != 0;
state.lighting.light[light_index].directional = light.config.directional != 0;
state.lighting.light[light_index].two_sided_diffuse = light.config.two_sided_diffuse != 0;
state.lighting.light[light_index].dist_atten_enable = !regs.lighting.IsDistAttenDisabled(num);
state.lighting.light[light_index].dist_atten_bias = Pica::float20::FromRaw(light.dist_atten_bias).ToFloat32();
state.lighting.light[light_index].dist_atten_scale = Pica::float20::FromRaw(light.dist_atten_scale).ToFloat32();
}
state.lighting.lut_d0.enable = regs.lighting.disable_lut_d0 == 0;
state.lighting.lut_d0.enable = regs.lighting.config1.disable_lut_d0 == 0;
state.lighting.lut_d0.abs_input = regs.lighting.abs_lut_input.disable_d0 == 0;
state.lighting.lut_d0.type = regs.lighting.lut_input.d0.Value();
state.lighting.lut_d0.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d0);
state.lighting.lut_d1.enable = regs.lighting.disable_lut_d1 == 0;
state.lighting.lut_d1.enable = regs.lighting.config1.disable_lut_d1 == 0;
state.lighting.lut_d1.abs_input = regs.lighting.abs_lut_input.disable_d1 == 0;
state.lighting.lut_d1.type = regs.lighting.lut_input.d1.Value();
state.lighting.lut_d1.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.d1);
state.lighting.lut_fr.enable = regs.lighting.disable_lut_fr == 0;
state.lighting.lut_fr.enable = regs.lighting.config1.disable_lut_fr == 0;
state.lighting.lut_fr.abs_input = regs.lighting.abs_lut_input.disable_fr == 0;
state.lighting.lut_fr.type = regs.lighting.lut_input.fr.Value();
state.lighting.lut_fr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.fr);
state.lighting.lut_rr.enable = regs.lighting.disable_lut_rr == 0;
state.lighting.lut_rr.enable = regs.lighting.config1.disable_lut_rr == 0;
state.lighting.lut_rr.abs_input = regs.lighting.abs_lut_input.disable_rr == 0;
state.lighting.lut_rr.type = regs.lighting.lut_input.rr.Value();
state.lighting.lut_rr.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rr);
state.lighting.lut_rg.enable = regs.lighting.disable_lut_rg == 0;
state.lighting.lut_rg.enable = regs.lighting.config1.disable_lut_rg == 0;
state.lighting.lut_rg.abs_input = regs.lighting.abs_lut_input.disable_rg == 0;
state.lighting.lut_rg.type = regs.lighting.lut_input.rg.Value();
state.lighting.lut_rg.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rg);
state.lighting.lut_rb.enable = regs.lighting.disable_lut_rb == 0;
state.lighting.lut_rb.enable = regs.lighting.config1.disable_lut_rb == 0;
state.lighting.lut_rb.abs_input = regs.lighting.abs_lut_input.disable_rb == 0;
state.lighting.lut_rb.type = regs.lighting.lut_input.rb.Value();
state.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb);
state.lighting.config = regs.lighting.config;
state.lighting.fresnel_selector = regs.lighting.fresnel_selector;
state.lighting.bump_mode = regs.lighting.bump_mode;
state.lighting.bump_selector = regs.lighting.bump_selector;
state.lighting.bump_renorm = regs.lighting.disable_bump_renorm == 0;
state.lighting.clamp_highlights = regs.lighting.clamp_highlights != 0;
state.lighting.config = regs.lighting.config0.config;
state.lighting.fresnel_selector = regs.lighting.config0.fresnel_selector;
state.lighting.bump_mode = regs.lighting.config0.bump_mode;
state.lighting.bump_selector = regs.lighting.config0.bump_selector;
state.lighting.bump_renorm = regs.lighting.config0.disable_bump_renorm == 0;
state.lighting.clamp_highlights = regs.lighting.config0.clamp_highlights != 0;
return res;
}
@ -184,8 +182,6 @@ union PicaShaderConfig {
bool directional;
bool two_sided_diffuse;
bool dist_atten_enable;
GLfloat dist_atten_scale;
GLfloat dist_atten_bias;
} light[8];
bool enable;
@ -316,6 +312,8 @@ private:
alignas(16) GLvec3 diffuse;
alignas(16) GLvec3 ambient;
alignas(16) GLvec3 position;
GLfloat dist_atten_bias;
GLfloat dist_atten_scale;
};
/// Uniform structure for the Uniform Buffer Object, all members must be 16-byte aligned
@ -330,7 +328,7 @@ private:
LightSrc light_src[8];
};
static_assert(sizeof(UniformData) == 0x310, "The size of the UniformData structure has changed, update the structure in the shader");
static_assert(sizeof(UniformData) == 0x390, "The size of the UniformData structure has changed, update the structure in the shader");
static_assert(sizeof(UniformData) < 16384, "UniformData structure must be less than 16kb as per the OpenGL spec");
/// Sets the OpenGL shader in accordance with the current PICA register state
@ -402,6 +400,12 @@ private:
/// Syncs the specified light's position to match the PICA register
void SyncLightPosition(int light_index);
/// Syncs the specified light's distance attenuation bias to match the PICA register
void SyncLightDistanceAttenuationBias(int light_index);
/// Syncs the specified light's distance attenuation scale to match the PICA register
void SyncLightDistanceAttenuationScale(int light_index);
OpenGLState state;
RasterizerCacheOpenGL res_cache;

View File

@ -439,9 +439,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
// If enabled, compute distance attenuation value
std::string dist_atten = "1.0";
if (light_config.dist_atten_enable) {
std::string scale = std::to_string(light_config.dist_atten_scale);
std::string bias = std::to_string(light_config.dist_atten_bias);
std::string index = "(" + scale + " * length(-view - " + light_src + ".position) + " + bias + ")";
std::string index = "(" + light_src + ".dist_atten_scale * length(-view - " + light_src + ".position) + " + light_src + ".dist_atten_bias)";
index = "((clamp(" + index + ", 0.0, FLOAT_255)))";
const unsigned lut_num = ((unsigned)Regs::LightingSampler::DistanceAttenuation + light_config.num);
dist_atten = GetLutValue((Regs::LightingSampler)lut_num, index);
@ -549,6 +547,8 @@ struct LightSrc {
vec3 diffuse;
vec3 ambient;
vec3 position;
float dist_atten_bias;
float dist_atten_scale;
};
layout (std140) uniform shader_data {

View File

@ -450,7 +450,7 @@ static const char* GetType(GLenum type) {
#undef RET
}
static void DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
static void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
const GLchar* message, const void* user_param) {
Log::Level level;
switch (severity) {

View File

@ -30,6 +30,58 @@ namespace Pica {
namespace Shader {
OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) {
// Setup output data
OutputVertex ret;
// TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to
// figure out what those circumstances are and enable the remaining outputs then.
unsigned index = 0;
for (unsigned i = 0; i < 7; ++i) {
if (index >= g_state.regs.vs_output_total)
break;
if ((config.output_mask & (1 << i)) == 0)
continue;
const auto& output_register_map = g_state.regs.vs_output_attributes[index];
u32 semantics[4] = {
output_register_map.map_x, output_register_map.map_y,
output_register_map.map_z, output_register_map.map_w
};
for (unsigned comp = 0; comp < 4; ++comp) {
float24* out = ((float24*)&ret) + semantics[comp];
if (semantics[comp] != Regs::VSOutputAttributes::INVALID) {
*out = value[i][comp];
} else {
// Zero output so that attributes which aren't output won't have denormals in them,
// which would slow us down later.
memset(out, 0, sizeof(*out));
}
}
index++;
}
// The hardware takes the absolute and saturates vertex colors like this, *before* doing interpolation
for (unsigned i = 0; i < 4; ++i) {
ret.color[i] = float24::FromFloat32(
std::fmin(std::fabs(ret.color[i].ToFloat32()), 1.0f));
}
LOG_TRACE(HW_GPU, "Output vertex: pos(%.2f, %.2f, %.2f, %.2f), quat(%.2f, %.2f, %.2f, %.2f), "
"col(%.2f, %.2f, %.2f, %.2f), tc0(%.2f, %.2f), view(%.2f, %.2f, %.2f)",
ret.pos.x.ToFloat32(), ret.pos.y.ToFloat32(), ret.pos.z.ToFloat32(), ret.pos.w.ToFloat32(),
ret.quat.x.ToFloat32(), ret.quat.y.ToFloat32(), ret.quat.z.ToFloat32(), ret.quat.w.ToFloat32(),
ret.color.x.ToFloat32(), ret.color.y.ToFloat32(), ret.color.z.ToFloat32(), ret.color.w.ToFloat32(),
ret.tc0.u().ToFloat32(), ret.tc0.v().ToFloat32(),
ret.view.x.ToFloat32(), ret.view.y.ToFloat32(), ret.view.z.ToFloat32());
return ret;
}
#ifdef ARCHITECTURE_x86_64
static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map;
static const JitShader* jit_shader;
@ -62,7 +114,7 @@ void ShaderSetup::Setup() {
MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240));
OutputVertex ShaderSetup::Run(UnitState<false>& state, const InputVertex& input, int num_attributes) {
void ShaderSetup::Run(UnitState<false>& state, const InputVertex& input, int num_attributes) {
auto& config = g_state.regs.vs;
auto& setup = g_state.vs;
@ -89,55 +141,6 @@ OutputVertex ShaderSetup::Run(UnitState<false>& state, const InputVertex& input,
RunInterpreter(setup, state, config.main_offset);
#endif // ARCHITECTURE_x86_64
// Setup output data
OutputVertex ret;
// TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to
// figure out what those circumstances are and enable the remaining outputs then.
unsigned index = 0;
for (unsigned i = 0; i < 7; ++i) {
if (index >= g_state.regs.vs_output_total)
break;
if ((g_state.regs.vs.output_mask & (1 << i)) == 0)
continue;
const auto& output_register_map = g_state.regs.vs_output_attributes[index]; // TODO: Don't hardcode VS here
u32 semantics[4] = {
output_register_map.map_x, output_register_map.map_y,
output_register_map.map_z, output_register_map.map_w
};
for (unsigned comp = 0; comp < 4; ++comp) {
float24* out = ((float24*)&ret) + semantics[comp];
if (semantics[comp] != Regs::VSOutputAttributes::INVALID) {
*out = state.registers.output[i][comp];
} else {
// Zero output so that attributes which aren't output won't have denormals in them,
// which would slow us down later.
memset(out, 0, sizeof(*out));
}
}
index++;
}
// The hardware takes the absolute and saturates vertex colors like this, *before* doing interpolation
for (unsigned i = 0; i < 4; ++i) {
ret.color[i] = float24::FromFloat32(
std::fmin(std::fabs(ret.color[i].ToFloat32()), 1.0f));
}
LOG_TRACE(HW_GPU, "Output vertex: pos(%.2f, %.2f, %.2f, %.2f), quat(%.2f, %.2f, %.2f, %.2f), "
"col(%.2f, %.2f, %.2f, %.2f), tc0(%.2f, %.2f), view(%.2f, %.2f, %.2f)",
ret.pos.x.ToFloat32(), ret.pos.y.ToFloat32(), ret.pos.z.ToFloat32(), ret.pos.w.ToFloat32(),
ret.quat.x.ToFloat32(), ret.quat.y.ToFloat32(), ret.quat.z.ToFloat32(), ret.quat.w.ToFloat32(),
ret.color.x.ToFloat32(), ret.color.y.ToFloat32(), ret.color.z.ToFloat32(), ret.color.w.ToFloat32(),
ret.tc0.u().ToFloat32(), ret.tc0.v().ToFloat32(),
ret.view.x.ToFloat32(), ret.view.y.ToFloat32(), ret.view.z.ToFloat32());
return ret;
}
DebugData<true> ShaderSetup::ProduceDebugInfo(const InputVertex& input, int num_attributes, const Regs::ShaderConfig& config, const ShaderSetup& setup) {

View File

@ -84,6 +84,15 @@ struct OutputVertex {
static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD");
static_assert(sizeof(OutputVertex) == 32 * sizeof(float), "OutputVertex has invalid size");
struct OutputRegisters {
OutputRegisters() = default;
alignas(16) Math::Vec4<float24> value[16];
OutputVertex ToVertex(const Regs::ShaderConfig& config);
};
static_assert(std::is_pod<OutputRegisters>::value, "Structure is not POD");
// Helper structure used to keep track of data useful for inspection of shader emulation
template<bool full_debugging>
struct DebugData;
@ -267,11 +276,12 @@ struct UnitState {
// The registers are accessed by the shader JIT using SSE instructions, and are therefore
// required to be 16-byte aligned.
alignas(16) Math::Vec4<float24> input[16];
alignas(16) Math::Vec4<float24> output[16];
alignas(16) Math::Vec4<float24> temporary[16];
} registers;
static_assert(std::is_pod<Registers>::value, "Structure is not POD");
OutputRegisters output_registers;
bool conditional_code[2];
// Two Address registers and one loop counter
@ -297,7 +307,7 @@ struct UnitState {
static size_t OutputOffset(const DestRegister& reg) {
switch (reg.GetRegisterType()) {
case RegisterType::Output:
return offsetof(UnitState, registers.output) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
return offsetof(UnitState, output_registers.value) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
case RegisterType::Temporary:
return offsetof(UnitState, registers.temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>);
@ -354,9 +364,8 @@ struct ShaderSetup {
* @param state Shader unit state, must be setup per shader and per shader unit
* @param input Input vertex into the shader
* @param num_attributes The number of vertex shader attributes
* @return The output vertex, after having been processed by the vertex shader
*/
OutputVertex Run(UnitState<false>& state, const InputVertex& input, int num_attributes);
void Run(UnitState<false>& state, const InputVertex& input, int num_attributes);
/**
* Produce debug information based on the given shader and input vertex

View File

@ -144,7 +144,7 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
src2[3] = src2[3] * float24::FromFloat32(-1);
}
float24* dest = (instr.common.dest.Value() < 0x10) ? &state.registers.output[instr.common.dest.Value().GetIndex()][0]
float24* dest = (instr.common.dest.Value() < 0x10) ? &state.output_registers.value[instr.common.dest.Value().GetIndex()][0]
: (instr.common.dest.Value() < 0x20) ? &state.registers.temporary[instr.common.dest.Value().GetIndex()][0]
: dummy_vec4_float24;
@ -483,7 +483,7 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
src3[3] = src3[3] * float24::FromFloat32(-1);
}
float24* dest = (instr.mad.dest.Value() < 0x10) ? &state.registers.output[instr.mad.dest.Value().GetIndex()][0]
float24* dest = (instr.mad.dest.Value() < 0x10) ? &state.output_registers.value[instr.mad.dest.Value().GetIndex()][0]
: (instr.mad.dest.Value() < 0x20) ? &state.registers.temporary[instr.mad.dest.Value().GetIndex()][0]
: dummy_vec4_float24;

View File

@ -2,8 +2,8 @@
#include <boost/range/algorithm/fill.hpp>
#include "common/assert.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/logging/log.h"
@ -21,6 +21,8 @@
namespace Pica {
void VertexLoader::Setup(const Pica::Regs& regs) {
ASSERT_MSG(!is_setup, "VertexLoader is not intended to be setup more than once.");
const auto& attribute_config = regs.vertex_attributes;
num_total_attributes = attribute_config.GetNumTotalAttributes();
@ -60,9 +62,13 @@ void VertexLoader::Setup(const Pica::Regs& regs) {
}
}
}
is_setup = true;
}
void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses) {
ASSERT_MSG(is_setup, "A VertexLoader needs to be setup before loading vertices.");
for (int i = 0; i < num_total_attributes; ++i) {
if (vertex_attribute_elements[i] != 0) {
// Load per-vertex data from the loader arrays

View File

@ -1,7 +1,8 @@
#pragma once
#include "common/common_types.h"
#include <array>
#include "common/common_types.h"
#include "video_core/pica.h"
namespace Pica {
@ -11,23 +12,29 @@ class MemoryAccessTracker;
}
namespace Shader {
class InputVertex;
struct InputVertex;
}
class VertexLoader {
public:
VertexLoader() = default;
explicit VertexLoader(const Pica::Regs& regs) {
Setup(regs);
}
void Setup(const Pica::Regs& regs);
void LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input, DebugUtils::MemoryAccessTracker& memory_accesses);
int GetNumTotalAttributes() const { return num_total_attributes; }
private:
u32 vertex_attribute_sources[16];
u32 vertex_attribute_strides[16] = {};
Regs::VertexAttributeFormat vertex_attribute_formats[16] = {};
u32 vertex_attribute_elements[16] = {};
bool vertex_attribute_is_default[16];
int num_total_attributes;
std::array<u32, 16> vertex_attribute_sources;
std::array<u32, 16> vertex_attribute_strides{};
std::array<Regs::VertexAttributeFormat, 16> vertex_attribute_formats;
std::array<u32, 16> vertex_attribute_elements{};
std::array<bool, 16> vertex_attribute_is_default;
int num_total_attributes = 0;
bool is_setup = false;
};
} // namespace Pica