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()); System::Init(emu_window.get());
SCOPE_EXIT({ System::Shutdown(); }); 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) { if (Loader::ResultStatus::Success != load_result) {
LOG_CRITICAL(Frontend, "Failed to load ROM (Error %i)!", load_result); LOG_CRITICAL(Frontend, "Failed to load ROM (Error %i)!", load_result);
return -1; 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 = { 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_A, SDL_SCANCODE_S, SDL_SCANCODE_Z, SDL_SCANCODE_X,
SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_1, SDL_SCANCODE_2, SDL_SCANCODE_Q, SDL_SCANCODE_W, SDL_SCANCODE_1, SDL_SCANCODE_2,
SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B, SDL_SCANCODE_M, SDL_SCANCODE_N, SDL_SCANCODE_B,
SDL_SCANCODE_T, SDL_SCANCODE_G, SDL_SCANCODE_F, SDL_SCANCODE_H, 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_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() { void Config::ReadValues() {
@ -58,6 +62,7 @@ void Config::ReadValues() {
Settings::values.input_mappings[Settings::NativeInput::All[i]] = Settings::values.input_mappings[Settings::NativeInput::All[i]] =
sdl2_config->GetInteger("Controls", Settings::NativeInput::Mapping[i], defaults[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 // Core
Settings::values.frame_skip = sdl2_config->GetInteger("Core", "frame_skip", 0); Settings::values.frame_skip = sdl2_config->GetInteger("Core", "frame_skip", 0);
@ -77,8 +82,9 @@ void Config::ReadValues() {
// Data Storage // Data Storage
Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true); Settings::values.use_virtual_sd = sdl2_config->GetBoolean("Data Storage", "use_virtual_sd", true);
// System Region // System
Settings::values.region_value = sdl2_config->GetInteger("System Region", "region_value", 1); 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 // Miscellaneous
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Info"); Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Info");

View File

@ -23,14 +23,19 @@ pad_l =
pad_r = pad_r =
pad_zl = pad_zl =
pad_zr = pad_zr =
pad_sup =
pad_sdown =
pad_sleft =
pad_sright =
pad_cup = pad_cup =
pad_cdown = pad_cdown =
pad_cleft = pad_cleft =
pad_cright = 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] [Core]
# The applied frameskip amount. Must be a power of two. # The applied frameskip amount. Must be a power of two.
@ -66,7 +71,11 @@ output_engine =
# 1 (default): Yes, 0: No # 1 (default): Yes, 0: No
use_virtual_sd = 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 # The system region that Citra will use during emulation
# 0: Japan, 1: USA (default), 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan # 0: Japan, 1: USA (default), 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
region_value = 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) { void EmuWindow_SDL2::OnKeyEvent(int key, u8 state) {
if (state == SDL_PRESSED) { if (state == SDL_PRESSED) {
KeyPressed({ key, keyboard_id }); KeyMap::PressKey(*this, { key, keyboard_id });
} else if (state == SDL_RELEASED) { } 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() { void EmuWindow_SDL2::ReloadSetKeymaps() {
KeyMap::ClearKeyMapping(keyboard_id);
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { 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(CMAKE_INCLUDE_CURRENT_DIR ON)
set(SRCS set(SRCS
config/controller_config.cpp
config/controller_config_util.cpp
config.cpp config.cpp
debugger/callstack.cpp debugger/callstack.cpp
debugger/disassembler.cpp debugger/disassembler.cpp
@ -11,7 +9,7 @@ set(SRCS
debugger/graphics_breakpoint_observer.cpp debugger/graphics_breakpoint_observer.cpp
debugger/graphics_breakpoints.cpp debugger/graphics_breakpoints.cpp
debugger/graphics_cmdlists.cpp debugger/graphics_cmdlists.cpp
debugger/graphics_framebuffer.cpp debugger/graphics_surface.cpp
debugger/graphics_tracing.cpp debugger/graphics_tracing.cpp
debugger/graphics_vertex_shader.cpp debugger/graphics_vertex_shader.cpp
debugger/profiler.cpp debugger/profiler.cpp
@ -33,8 +31,6 @@ set(SRCS
) )
set(HEADERS set(HEADERS
config/controller_config.h
config/controller_config_util.h
config.h config.h
debugger/callstack.h debugger/callstack.h
debugger/disassembler.h debugger/disassembler.h
@ -43,7 +39,7 @@ set(HEADERS
debugger/graphics_breakpoints.h debugger/graphics_breakpoints.h
debugger/graphics_breakpoints_p.h debugger/graphics_breakpoints_p.h
debugger/graphics_cmdlists.h debugger/graphics_cmdlists.h
debugger/graphics_framebuffer.h debugger/graphics_surface.h
debugger/graphics_tracing.h debugger/graphics_tracing.h
debugger/graphics_vertex_shader.h debugger/graphics_vertex_shader.h
debugger/profiler.h debugger/profiler.h
@ -65,7 +61,6 @@ set(HEADERS
) )
set(UIS set(UIS
config/controller_config.ui
debugger/callstack.ui debugger/callstack.ui
debugger/disassembler.ui debugger/disassembler.ui
debugger/profiler.ui debugger/profiler.ui

View File

@ -235,12 +235,12 @@ void GRenderWindow::closeEvent(QCloseEvent* event) {
void GRenderWindow::keyPressEvent(QKeyEvent* event) void GRenderWindow::keyPressEvent(QKeyEvent* event)
{ {
this->KeyPressed({event->key(), keyboard_id}); KeyMap::PressKey(*this, { event->key(), keyboard_id });
} }
void GRenderWindow::keyReleaseEvent(QKeyEvent* event) void GRenderWindow::keyReleaseEvent(QKeyEvent* event)
{ {
this->KeyReleased({event->key(), keyboard_id}); KeyMap::ReleaseKey(*this, { event->key(), keyboard_id });
} }
void GRenderWindow::mousePressEvent(QMouseEvent *event) void GRenderWindow::mousePressEvent(QMouseEvent *event)
@ -270,8 +270,9 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent *event)
void GRenderWindow::ReloadSetKeymaps() void GRenderWindow::ReloadSetKeymaps()
{ {
KeyMap::ClearKeyMapping(keyboard_id);
for (int i = 0; i < Settings::NativeInput::NUM_INPUTS; ++i) { 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 = { 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_A, Qt::Key_S, Qt::Key_Z, Qt::Key_X,
Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2, Qt::Key_Q, Qt::Key_W, Qt::Key_1, Qt::Key_2,
Qt::Key_M, Qt::Key_N, Qt::Key_B, Qt::Key_M, Qt::Key_N, Qt::Key_B,
Qt::Key_T, Qt::Key_G, Qt::Key_F, Qt::Key_H, 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_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() { void Config::ReadValues() {
@ -36,6 +40,7 @@ void Config::ReadValues() {
Settings::values.input_mappings[Settings::NativeInput::All[i]] = Settings::values.input_mappings[Settings::NativeInput::All[i]] =
qt_config->value(QString::fromStdString(Settings::NativeInput::Mapping[i]), defaults[i]).toInt(); 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->endGroup();
qt_config->beginGroup("Core"); qt_config->beginGroup("Core");
@ -60,7 +65,8 @@ void Config::ReadValues() {
Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool(); Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
qt_config->endGroup(); 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(); Settings::values.region_value = qt_config->value("region_value", 1).toInt();
qt_config->endGroup(); qt_config->endGroup();
@ -125,6 +131,7 @@ void Config::SaveValues() {
qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]), qt_config->setValue(QString::fromStdString(Settings::NativeInput::Mapping[i]),
Settings::values.input_mappings[Settings::NativeInput::All[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->endGroup();
qt_config->beginGroup("Core"); qt_config->beginGroup("Core");
@ -150,7 +157,8 @@ void Config::SaveValues() {
qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd); qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
qt_config->endGroup(); 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->setValue("region_value", Settings::values.region_value);
qt_config->endGroup(); 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; int counter = 0;
for (u32 addr = 0x10000000; addr >= sp; addr -= 4) for (u32 addr = 0x10000000; addr >= sp; addr -= 4)
{ {
if (!Memory::IsValidVirtualAddress(addr))
break;
const u32 ret_addr = Memory::Read32(addr); const u32 ret_addr = Memory::Read32(addr);
const u32 call_addr = ret_addr - 4; //get call address??? const u32 call_addr = ret_addr - 4; //get call address???
if (Memory::GetPointer(call_addr) == nullptr) if (!Memory::IsValidVirtualAddress(call_addr))
break; break;
/* TODO (mattvail) clean me, move to debugger interface */ /* 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) { GPUCommandListModel::GPUCommandListModel(QObject* parent) : QAbstractListModel(parent) {
} }
@ -249,16 +132,16 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
index = 0; index = 0;
} else if (COMMAND_IN_RANGE(command_id, texture1)) { } else if (COMMAND_IN_RANGE(command_id, texture1)) {
index = 1; index = 1;
} else { } else if (COMMAND_IN_RANGE(command_id, texture2)) {
index = 2; index = 2;
} else {
UNREACHABLE_MSG("Unknown texture command");
} }
auto config = Pica::g_state.regs.GetTextures()[index].config; auto config = Pica::g_state.regs.GetTextures()[index].config;
auto format = Pica::g_state.regs.GetTextures()[index].format; auto format = Pica::g_state.regs.GetTextures()[index].format;
auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format); auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format);
// TODO: Instead, emit a signal here to be caught by the main window widget. // TODO: Open a surface debugger
auto main_window = static_cast<QMainWindow*>(parent());
main_window->tabifyDockWidget(this, new TextureInfoDockWidget(info, main_window));
} }
} }

View File

@ -61,25 +61,3 @@ private:
QWidget* command_info_widget; QWidget* command_info_widget;
QPushButton* toggle_tracing; 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()); 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 auto callback = [&](unsigned* num_entries_out,
const std::string& directory, 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; std::string physical_name = directory + DIR_SEP + virtual_name;
if (stop_processing) if (stop_processing)
return false; // Breaks the callback loop. return false; // Breaks the callback loop.
if (deep_scan && FileUtil::IsDirectory(physical_name)) { if (!FileUtil::IsDirectory(physical_name)) {
AddFstEntriesToGameList(physical_name, true); std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
} else { if (!loader)
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)
return true; 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::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); loader->ReadIcon(smdh);
emit EntryReady({ emit EntryReady({
new GameListItemPath(QString::fromStdString(physical_name), smdh), 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)), new GameListItemSize(FileUtil::GetSize(physical_name)),
}); });
} else if (recursion > 0) {
AddFstEntriesToGameList(physical_name, recursion - 1);
} }
return true; return true;
@ -169,7 +156,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, bool d
void GameListWorker::run() void GameListWorker::run()
{ {
stop_processing = false; stop_processing = false;
AddFstEntriesToGameList(dir_path.toStdString(), deep_scan); AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
emit Finished(); emit Finished();
} }

View File

@ -15,52 +15,21 @@
#include "common/string_util.h" #include "common/string_util.h"
#include "common/color.h" #include "common/color.h"
#include "core/loader/loader.h" #include "core/loader/smdh.h"
#include "video_core/utils.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 * Gets game icon from SMDH
* @param sdmh SMDH data * @param sdmh SMDH data
* @param large If true, returns large icon (48x48), otherwise returns small icon (24x24) * @param large If true, returns large icon (48x48), otherwise returns small icon (24x24)
* @return QPixmap game icon * @return QPixmap game icon
*/ */
static QPixmap GetIconFromSMDH(const Loader::SMDH& smdh, bool large) { static QPixmap GetQPixmapFromSMDH(const Loader::SMDH& smdh, bool large) {
u32 size; std::vector<u16> icon_data = smdh.GetIcon(large);
const u8* icon_data; const uchar* data = reinterpret_cast<const uchar*>(icon_data.data());
int size = large ? 48 : 24;
if (large) { QImage icon(data, size, size, QImage::Format::Format_RGB16);
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()));
}
}
return QPixmap::fromImage(icon); return QPixmap::fromImage(icon);
} }
@ -82,8 +51,8 @@ static QPixmap GetDefaultIcon(bool large) {
* @param language title language * @param language title language
* @return QString short title * @return QString short title
*/ */
static QString GetShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) { static QString GetQStringShortTitleFromSMDH(const Loader::SMDH& smdh, Loader::SMDH::TitleLanguage language) {
return QString::fromUtf16(smdh.titles[static_cast<int>(language)].short_title.data()); return QString::fromUtf16(smdh.GetShortTitle(language).data());
} }
class GameListItem : public QStandardItem { class GameListItem : public QStandardItem {
@ -112,7 +81,7 @@ public:
{ {
setData(game_path, FullPathRole); setData(game_path, FullPathRole);
if (!IsValidSMDH(smdh_data)) { if (!Loader::IsValidSMDH(smdh_data)) {
// SMDH is not valid, set a default icon // SMDH is not valid, set a default icon
setData(GetDefaultIcon(true), Qt::DecorationRole); setData(GetDefaultIcon(true), Qt::DecorationRole);
return; return;
@ -122,10 +91,10 @@ public:
memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH)); memcpy(&smdh, smdh_data.data(), sizeof(Loader::SMDH));
// Get icon from SMDH // Get icon from SMDH
setData(GetIconFromSMDH(smdh, true), Qt::DecorationRole); setData(GetQPixmapFromSMDH(smdh, true), Qt::DecorationRole);
// Get title form SMDH // 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 { QVariant data(int role) const override {
@ -212,5 +181,5 @@ private:
bool deep_scan; bool deep_scan;
std::atomic_bool stop_processing; 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.h"
#include "citra_qt/debugger/graphics_breakpoints.h" #include "citra_qt/debugger/graphics_breakpoints.h"
#include "citra_qt/debugger/graphics_cmdlists.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_tracing.h"
#include "citra_qt/debugger/graphics_vertex_shader.h" #include "citra_qt/debugger/graphics_vertex_shader.h"
#include "citra_qt/debugger/profiler.h" #include "citra_qt/debugger/profiler.h"
@ -101,10 +101,6 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget); addDockWidget(Qt::RightDockWidgetArea, graphicsBreakpointsWidget);
graphicsBreakpointsWidget->hide(); 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); auto graphicsVertexShaderWidget = new GraphicsVertexShaderWidget(Pica::g_debug_context, this);
addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget); addDockWidget(Qt::RightDockWidgetArea, graphicsVertexShaderWidget);
graphicsVertexShaderWidget->hide(); graphicsVertexShaderWidget->hide();
@ -113,7 +109,12 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr)
addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget); addDockWidget(Qt::RightDockWidgetArea, graphicsTracingWidget);
graphicsTracingWidget->hide(); 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")); QMenu* debug_menu = ui.menu_View->addMenu(tr("Debugging"));
debug_menu->addAction(graphicsSurfaceViewerAction);
debug_menu->addSeparator();
debug_menu->addAction(profilerWidget->toggleViewAction()); debug_menu->addAction(profilerWidget->toggleViewAction());
#if MICROPROFILE_ENABLED #if MICROPROFILE_ENABLED
debug_menu->addAction(microProfileDialog->toggleViewAction()); 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(graphicsWidget->toggleViewAction());
debug_menu->addAction(graphicsCommandsWidget->toggleViewAction()); debug_menu->addAction(graphicsCommandsWidget->toggleViewAction());
debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction()); debug_menu->addAction(graphicsBreakpointsWidget->toggleViewAction());
debug_menu->addAction(graphicsFramebufferWidget->toggleViewAction());
debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction()); debug_menu->addAction(graphicsVertexShaderWidget->toggleViewAction());
debug_menu->addAction(graphicsTracingWidget->toggleViewAction()); debug_menu->addAction(graphicsTracingWidget->toggleViewAction());
@ -272,7 +272,15 @@ bool GMainWindow::InitializeSystem() {
} }
bool GMainWindow::LoadROM(const std::string& filename) { 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) { if (Loader::ResultStatus::Success != result) {
LOG_CRITICAL(Frontend, "Failed to load ROM!"); LOG_CRITICAL(Frontend, "Failed to load ROM!");
System::Shutdown(); 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() { bool GMainWindow::ConfirmClose() {
if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) if (emu_thread == nullptr || !UISettings::values.confirm_before_closing)
return true; return true;

View File

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

View File

@ -72,18 +72,24 @@ inline u64 _rotr64(u64 x, unsigned int shift){
} }
#else // _MSC_VER #else // _MSC_VER
#if (_MSC_VER < 1900)
#if (_MSC_VER < 1900)
// Function Cross-Compatibility // Function Cross-Compatibility
#define snprintf _snprintf #define snprintf _snprintf
#endif #endif
// Locale Cross-Compatibility // Locale Cross-Compatibility
#define locale_t _locale_t #define locale_t _locale_t
extern "C" { extern "C" {
__declspec(dllimport) void __stdcall DebugBreak(void); __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 #endif // _MSC_VER ndef
// Generic function to get last error message. // Generic function to get last error message.

View File

@ -11,12 +11,28 @@
#include "emu_window.h" #include "emu_window.h"
#include "video_core/video_core.h" #include "video_core/video_core.h"
void EmuWindow::KeyPressed(KeyMap::HostDeviceKey key) { void EmuWindow::ButtonPressed(Service::HID::PadState pad) {
pad_state.hex |= KeyMap::GetPadKey(key).hex; pad_state.hex |= pad.hex;
} }
void EmuWindow::KeyReleased(KeyMap::HostDeviceKey key) { void EmuWindow::ButtonReleased(Service::HID::PadState pad) {
pad_state.hex &= ~KeyMap::GetPadKey(key).hex; 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" #include "core/hle/service/hid/hid.h"
namespace KeyMap {
struct HostDeviceKey;
}
/** /**
* Abstraction class used to provide an interface between emulation code and the frontend * Abstraction class used to provide an interface between emulation code and the frontend
* (e.g. SDL, QGLWidget, GLFW, etc...). * (e.g. SDL, QGLWidget, GLFW, etc...).
@ -76,11 +72,27 @@ public:
virtual void ReloadSetKeymaps() = 0; 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) * 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); 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 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. * @todo Fix this function to be thread-safe.
* @return PadState object indicating the current pad state * @return PadState object indicating the current pad state
*/ */
@ -109,6 +122,16 @@ public:
return pad_state; 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). * 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. * @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; pad_state.hex = 0;
touch_x = 0; touch_x = 0;
touch_y = 0; touch_y = 0;
circle_pad_x = 0;
circle_pad_y = 0;
touch_pressed = false; touch_pressed = false;
} }
virtual ~EmuWindow() {} virtual ~EmuWindow() {}
@ -260,6 +285,9 @@ private:
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320) 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) 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. * 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()); LOG_TRACE(Common_Filesystem, "directory %s", directory.c_str());
@ -472,7 +472,7 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
continue; continue;
unsigned ret_entries = 0; unsigned ret_entries = 0;
if (!callback(&ret_entries, directory, virtual_name)) { if (!callback(&ret_entries, directory, virtual_name, recursion)) {
callback_error = true; callback_error = true;
break; break;
} }
@ -486,30 +486,34 @@ bool ForeachDirectoryEntry(unsigned* num_entries_out, const std::string &directo
closedir(dirp); closedir(dirp);
#endif #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 // num_entries_out is allowed to be specified nullptr, in which case we shouldn't try to set it
if (num_entries_out != nullptr) if (num_entries_out != nullptr)
*num_entries_out = found_entries; *num_entries_out = found_entries;
return true; 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 auto callback = [&parent_entry](unsigned* num_entries_out,
const std::string& directory, const std::string& directory,
const std::string& virtual_name) -> bool { const std::string& virtual_name,
unsigned int recursion) -> bool {
FSTEntry entry; FSTEntry entry;
entry.virtualName = virtual_name; entry.virtualName = virtual_name;
entry.physicalName = directory + DIR_SEP + virtual_name; entry.physicalName = directory + DIR_SEP + virtual_name;
if (IsDirectory(entry.physicalName)) { if (IsDirectory(entry.physicalName)) {
entry.isDirectory = true; entry.isDirectory = true;
// is a directory, lets go inside // is a directory, lets go inside if we didn't recurse to often
entry.size = ScanDirectoryTree(entry.physicalName, entry); if (recursion > 0) {
entry.size = ScanDirectoryTree(entry.physicalName, entry, recursion - 1);
*num_entries_out += (int)entry.size; *num_entries_out += (int)entry.size;
} else {
entry.size = 0;
}
} else { // is a file } else { // is a file
entry.isDirectory = false; entry.isDirectory = false;
entry.size = GetSize(entry.physicalName); entry.size = GetSize(entry.physicalName);
@ -522,23 +526,27 @@ unsigned ScanDirectoryTree(const std::string &directory, FSTEntry& parent_entry)
}; };
unsigned num_entries; 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 static auto callback = [](unsigned* num_entries_out,
const std::string& directory, 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; 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); return Delete(new_path);
}; };
if (!ForeachDirectoryEntry(nullptr, directory, callback)) if (!ForeachDirectoryEntry(nullptr, directory, callback, recursion))
return false; return false;
// Delete the outermost directory // 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 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 directory the path to the enclosing directory
* @param virtual_name the entry name, without any preceding directory info * @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 * @return whether handling the entry succeeded
*/ */
using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out, using DirectoryEntryCallable = std::function<bool(unsigned* num_entries_out,
const std::string& directory, 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. * 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 num_entries_out assigned by the function with the number of iterated directory entries, can be null
* @param directory the directory to scan * @param directory the directory to scan
* @param callback The callback which will be called for each entry * @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 * @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. * Scans the directory tree, storing the results.
* @param directory the parent directory to start scanning from * @param directory the parent directory to start scanning from
* @param parent_entry FSTEntry where the filesystem tree results will be stored. * @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 * @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. // 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 // Returns the current directory
std::string GetCurrentDir(); std::string GetCurrentDir();

View File

@ -2,24 +2,138 @@
// Licensed under GPLv2 or any later version // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "key_map.h"
#include <map> #include <map>
#include "common/emu_window.h"
#include "common/key_map.h"
namespace KeyMap { 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 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() { int NewDeviceId() {
return next_device_id++; return next_device_id++;
} }
void SetKeyMapping(HostDeviceKey key, Service::HID::PadState padState) { void SetKeyMapping(HostDeviceKey key, KeyTarget target) {
key_map[key].hex = padState.hex; key_map[key] = target;
} }
Service::HID::PadState GetPadKey(HostDeviceKey key) { void ClearKeyMapping(int device_id) {
return key_map[key]; 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 #pragma once
#include <array>
#include <tuple> #include <tuple>
#include "core/hle/service/hid/hid.h" #include "core/hle/service/hid/hid.h"
class EmuWindow;
namespace KeyMap { 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. * 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. * Generates a new device id, which uniquely identifies a host device within KeyMap.
*/ */
int NewDeviceId(); 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_s.cpp
hle/service/cfg/cfg_u.cpp hle/service/cfg/cfg_u.cpp
hle/service/csnd_snd.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/dsp_dsp.cpp
hle/service/err_f.cpp hle/service/err_f.cpp
hle/service/frd/frd.cpp hle/service/frd/frd.cpp
@ -121,6 +124,7 @@ set(SRCS
loader/elf.cpp loader/elf.cpp
loader/loader.cpp loader/loader.cpp
loader/ncch.cpp loader/ncch.cpp
loader/smdh.cpp
tracer/recorder.cpp tracer/recorder.cpp
memory.cpp memory.cpp
settings.cpp settings.cpp
@ -205,7 +209,10 @@ set(HEADERS
hle/service/cfg/cfg_s.h hle/service/cfg/cfg_s.h
hle/service/cfg/cfg_u.h hle/service/cfg/cfg_u.h
hle/service/csnd_snd.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/dsp_dsp.h
hle/service/err_f.h hle/service/err_f.h
hle/service/frd/frd.h hle/service/frd/frd.h
@ -256,6 +263,7 @@ set(HEADERS
loader/elf.h loader/elf.h
loader/loader.h loader/loader.h
loader/ncch.h loader/ncch.h
loader/smdh.h
tracer/recorder.h tracer/recorder.h
tracer/citrace.h tracer/citrace.h
memory.h memory.h

View File

@ -422,6 +422,10 @@ ARMDecodeStatus DecodeARMInstruction(u32 instr, s32* idx) {
n = arm_instruction[i].attribute_value; n = arm_instruction[i].attribute_value;
base = 0; base = 0;
// 3DS has no VFP3 support
if (arm_instruction[i].version == ARMVFP3)
continue;
while (n) { while (n) {
if (arm_instruction[i].content[base + 1] == 31 && arm_instruction[i].content[base] == 0) { if (arm_instruction[i].content[base + 1] == 31 && arm_instruction[i].content[base] == 0) {
// clrex // 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 // Unpack a single-precision float. Note that this returns the magnitude
// of the single-precision float mantissa with the 1. if necessary, // of the single-precision float mantissa with the 1. if necessary,
// aligned to bit 30. // 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->sign = vfp_single_packed_sign(val) >> 16,
s->exponent = vfp_single_packed_exponent(val); 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. // If flush-to-zero mode is enabled, turn the denormal into zero.
// On a VFPv2 architecture, the sign of the zero is always positive. // 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->sign = 0;
s->exponent = 0; s->exponent = 0;
s->significand = 0; s->significand = 0;
*fpscr |= FPSCR_IDC; exceptions |= FPSCR_IDC;
} }
return exceptions;
} }
// Re-pack a single-precision float. This assumes that the float is // 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 // Double-precision
struct vfp_double { 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 // Unpack a double-precision float. Note that this returns the magnitude
// of the double-precision float mantissa with the 1. if necessary, // of the double-precision float mantissa with the 1. if necessary,
// aligned to bit 62. // 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->sign = vfp_double_packed_sign(val) >> 48;
s->exponent = vfp_double_packed_exponent(val); 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. // If flush-to-zero mode is enabled, turn the denormal into zero.
// On a VFPv2 architecture, the sign of the zero is always positive. // 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->sign = 0;
s->exponent = 0; s->exponent = 0;
s->significand = 0; s->significand = 0;
*fpscr |= FPSCR_IDC; exceptions |= FPSCR_IDC;
} }
return exceptions;
} }
// Re-pack a double-precision float. This assumes that the float is // 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_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_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); 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; u64 significand, incr;
int exponent, shift, underflow; int exponent, shift, underflow;
u32 rmode; u32 rmode;
u32 exceptions = 0;
vfp_double_dump("pack: in", vd); 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__); LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__);
vfp_double vdm, vdd, *vdp; vfp_double vdm, vdd, *vdp;
int ret, tm; 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); tm = vfp_double_type(&vdm);
if (tm & (VFP_NAN|VFP_INFINITY)) { if (tm & (VFP_NAN|VFP_INFINITY)) {
@ -369,7 +371,8 @@ sqrt_invalid:
} }
vdd.significand = vfp_shiftright64jamming(vdd.significand, 1); 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; u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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); 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 else
vsd.exponent = vdm.exponent - (1023 - 127); 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: pack_nan:
vfp_put_float(state, vfp_single_pack(&vsd), sd); 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) static u32 vfp_double_fuito(ARMul_State* state, int dd, int unused, int dm, u32 fpscr)
{ {
struct vfp_double vdm; struct vfp_double vdm;
u32 exceptions = 0;
u32 m = vfp_get_float(state, dm); u32 m = vfp_get_float(state, dm);
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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.exponent = 1023 + 63 - 1;
vdm.significand = (u64)m; 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) static u32 vfp_double_fsito(ARMul_State* state, int dd, int unused, int dm, u32 fpscr)
{ {
struct vfp_double vdm; struct vfp_double vdm;
u32 exceptions = 0;
u32 m = vfp_get_float(state, dm); u32 m = vfp_get_float(state, dm);
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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.exponent = 1023 + 63 - 1;
vdm.significand = vdm.sign ? (~m + 1) : m; 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) 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; int tm;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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? * 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) { if (vdm.exponent >= 1023 + 32) {
d = vdm.sign ? 0 : 0xffffffff; d = vdm.sign ? 0 : 0xffffffff;
exceptions = FPSCR_IOC; exceptions = FPSCR_IOC;
} else if (vdm.exponent >= 1023 - 1) { } else if (vdm.exponent >= 1023) {
int shift = 1023 + 63 - vdm.exponent; int shift = 1023 + 63 - vdm.exponent;
u64 rem, incr = 0; u64 rem, incr = 0;
@ -595,12 +603,20 @@ static u32 vfp_double_ftoui(ARMul_State* state, int sd, int unused, int dm, u32
} else { } else {
d = 0; d = 0;
if (vdm.exponent | vdm.significand) { 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; exceptions |= FPSCR_IXC;
if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0) }
} else if (rmode == FPSCR_ROUND_PLUSINF && vdm.sign == 0) {
d = 1; d = 1;
else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign) { exceptions |= FPSCR_IXC;
d = 0; } else if (rmode == FPSCR_ROUND_MINUSINF) {
exceptions |= FPSCR_IOC; 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) static u32 vfp_double_ftouiz(ARMul_State* state, int sd, int unused, int dm, u32 fpscr)
{ {
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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) 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; int tm;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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); 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) { if (tm & VFP_NAN) {
d = 0; d = 0;
exceptions |= FPSCR_IOC; exceptions |= FPSCR_IOC;
} else if (vdm.exponent >= 1023 + 32) { } else if (vdm.exponent >= 1023 + 31) {
d = 0x7fffffff; d = 0x7fffffff;
if (vdm.sign) if (vdm.sign)
d = ~d; d = ~d;
exceptions |= FPSCR_IOC; exceptions |= FPSCR_IOC;
} else if (vdm.exponent >= 1023 - 1) { } else if (vdm.exponent >= 1023) {
int shift = 1023 + 63 - vdm.exponent; /* 58 */ int shift = 1023 + 63 - vdm.exponent; /* 58 */
u64 rem, incr = 0; 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; d = 0;
if (vdm.exponent | vdm.significand) { if (vdm.exponent | vdm.significand) {
exceptions |= FPSCR_IXC; 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; d = 1;
else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign) } else if (rmode == FPSCR_ROUND_MINUSINF && vdm.sign) {
d = -1; 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) static u32 vfp_double_ftosiz(ARMul_State* state, int dd, int unused, int dm, u32 fpscr)
{ {
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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[] = { 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) 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; 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) if (vdn.exponent == 0 && vdn.significand)
vfp_double_normalise_denormal(&vdn); 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) if (vdm.exponent == 0 && vdm.significand)
vfp_double_normalise_denormal(&vdm); 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) if (negate & NEG_MULTIPLY)
vdp.sign = vfp_sign_negate(vdp.sign); 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) if (vdn.exponent == 0 && vdn.significand != 0)
vfp_double_normalise_denormal(&vdn); 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); 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) static u32 vfp_double_fmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
{ {
struct vfp_double vdd, vdn, vdm; struct vfp_double vdd, vdn, vdm;
u32 exceptions; u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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) if (vdn.exponent == 0 && vdn.significand)
vfp_double_normalise_denormal(&vdn); 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) if (vdm.exponent == 0 && vdm.significand)
vfp_double_normalise_denormal(&vdm); vfp_double_normalise_denormal(&vdm);
exceptions = vfp_double_multiply(&vdd, &vdn, &vdm, fpscr); exceptions |= vfp_double_multiply(&vdd, &vdn, &vdm, fpscr);
return vfp_double_normaliseround(state, dd, &vdd, fpscr, exceptions, "fmul");
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) static u32 vfp_double_fnmul(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
{ {
struct vfp_double vdd, vdn, vdm; struct vfp_double vdd, vdn, vdm;
u32 exceptions; u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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) if (vdn.exponent == 0 && vdn.significand)
vfp_double_normalise_denormal(&vdn); 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) if (vdm.exponent == 0 && vdm.significand)
vfp_double_normalise_denormal(&vdm); 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); 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) static u32 vfp_double_fadd(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
{ {
struct vfp_double vdd, vdn, vdm; struct vfp_double vdd, vdn, vdm;
u32 exceptions; u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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) if (vdn.exponent == 0 && vdn.significand)
vfp_double_normalise_denormal(&vdn); 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) if (vdm.exponent == 0 && vdm.significand)
vfp_double_normalise_denormal(&vdm); 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) static u32 vfp_double_fsub(ARMul_State* state, int dd, int dn, int dm, u32 fpscr)
{ {
struct vfp_double vdd, vdn, vdm; struct vfp_double vdd, vdn, vdm;
u32 exceptions; u32 exceptions = 0;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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) if (vdn.exponent == 0 && vdn.significand)
vfp_double_normalise_denormal(&vdn); 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) if (vdm.exponent == 0 && vdm.significand)
vfp_double_normalise_denormal(&vdm); 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); 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; int tm, tn;
LOG_TRACE(Core_ARM11, "In %s", __FUNCTION__); 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);
vfp_double_unpack(&vdm, vfp_get_double(state, dm), &fpscr); exceptions |= vfp_double_unpack(&vdm, vfp_get_double(state, dm), fpscr);
vdd.sign = vdn.sign ^ vdm.sign; 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); 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: vdn_nan:
exceptions = vfp_propagate_nan(&vdd, &vdn, &vdm, fpscr); exceptions |= vfp_propagate_nan(&vdd, &vdn, &vdm, fpscr);
pack: pack:
vfp_put_double(state, vfp_double_pack(&vdd), dd); vfp_put_double(state, vfp_double_pack(&vdd), dd);
return exceptions; return exceptions;
vdm_nan: vdm_nan:
exceptions = vfp_propagate_nan(&vdd, &vdm, &vdn, fpscr); exceptions |= vfp_propagate_nan(&vdd, &vdm, &vdn, fpscr);
goto pack; goto pack;
zero: zero:
@ -1149,7 +1180,7 @@ zero:
goto pack; goto pack;
divzero: divzero:
exceptions = FPSCR_DZC; exceptions |= FPSCR_DZC;
infinity: infinity:
vdd.exponent = 2047; vdd.exponent = 2047;
vdd.significand = 0; vdd.significand = 0;
@ -1157,7 +1188,8 @@ infinity:
invalid: invalid:
vfp_put_double(state, vfp_double_pack(&vfp_double_default_qnan), dd); vfp_put_double(state, vfp_double_pack(&vfp_double_default_qnan), dd);
return FPSCR_IOC; exceptions |= FPSCR_IOC;
return exceptions;
} }
static struct op fops[] = { 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; u32 significand, incr, rmode;
int exponent, shift, underflow; int exponent, shift, underflow;
u32 exceptions = 0;
vfp_single_dump("pack: in", vs); 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; struct vfp_single vsm, vsd, *vsp;
int ret, tm; int ret, tm;
u32 exceptions = 0;
vfp_single_unpack(&vsm, m, &fpscr); exceptions |= vfp_single_unpack(&vsm, m, fpscr);
tm = vfp_single_type(&vsm); tm = vfp_single_type(&vsm);
if (tm & (VFP_NAN|VFP_INFINITY)) { if (tm & (VFP_NAN|VFP_INFINITY)) {
vsp = &vsd; vsp = &vsd;
@ -408,7 +410,8 @@ sqrt_invalid:
} }
vsd.significand = vfp_shiftright32jamming(vsd.significand, 1); 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; int tm;
u32 exceptions = 0; u32 exceptions = 0;
vfp_single_unpack(&vsm, m, &fpscr); exceptions |= vfp_single_unpack(&vsm, m, fpscr);
tm = vfp_single_type(&vsm); 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 we have a signalling NaN, signal invalid operation.
*/ */
if (tm == VFP_SNAN) if (tm == VFP_SNAN)
exceptions = FPSCR_IOC; exceptions |= FPSCR_IOC;
if (tm & VFP_DENORMAL) if (tm & VFP_DENORMAL)
vfp_single_normalise_denormal(&vsm); 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 else
vdd.exponent = vsm.exponent + (1023 - 127); 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: pack_nan:
vfp_put_double(state, vfp_double_pack(&vdd), dd); 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) static u32 vfp_single_fuito(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
{ {
struct vfp_single vs; struct vfp_single vs;
u32 exceptions = 0;
vs.sign = 0; vs.sign = 0;
vs.exponent = 127 + 31 - 1; vs.exponent = 127 + 31 - 1;
vs.significand = (u32)m; 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) static u32 vfp_single_fsito(ARMul_State* state, int sd, int unused, s32 m, u32 fpscr)
{ {
struct vfp_single vs; struct vfp_single vs;
u32 exceptions = 0;
vs.sign = (m & 0x80000000) >> 16; vs.sign = (m & 0x80000000) >> 16;
vs.exponent = 127 + 31 - 1; vs.exponent = 127 + 31 - 1;
vs.significand = vs.sign ? -m : m; 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) 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 rmode = fpscr & FPSCR_RMODE_MASK;
int tm; int tm;
vfp_single_unpack(&vsm, m, &fpscr); exceptions |= vfp_single_unpack(&vsm, m, fpscr);
vfp_single_dump("VSM", &vsm); 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) { if (vsm.exponent >= 127 + 32) {
d = vsm.sign ? 0 : 0xffffffff; d = vsm.sign ? 0 : 0xffffffff;
exceptions = FPSCR_IOC; exceptions |= FPSCR_IOC;
} else if (vsm.exponent >= 127) { } else if (vsm.exponent >= 127) {
int shift = 127 + 31 - vsm.exponent; int shift = 127 + 31 - vsm.exponent;
u32 rem, incr = 0; 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 * 2^0 <= m < 2^32-2^8
*/ */
d = (vsm.significand << 1) >> shift; 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) { if (rmode == FPSCR_ROUND_NEAREST) {
incr = 0x80000000; incr = 0x80000000;
@ -619,12 +631,20 @@ static u32 vfp_single_ftoui(ARMul_State* state, int sd, int unused, s32 m, u32 f
} else { } else {
d = 0; d = 0;
if (vsm.exponent | vsm.significand) { 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; exceptions |= FPSCR_IXC;
if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0) }
} else if (rmode == FPSCR_ROUND_PLUSINF && vsm.sign == 0) {
d = 1; d = 1;
else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign) { exceptions |= FPSCR_IXC;
d = 0; } else if (rmode == FPSCR_ROUND_MINUSINF) {
exceptions |= FPSCR_IOC; 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) 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) 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 rmode = fpscr & FPSCR_RMODE_MASK;
int tm; int tm;
vfp_single_unpack(&vsm, m, &fpscr); exceptions |= vfp_single_unpack(&vsm, m, fpscr);
vfp_single_dump("VSM", &vsm); 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) { if (tm & VFP_NAN) {
d = 0; d = 0;
exceptions |= FPSCR_IOC; exceptions |= FPSCR_IOC;
} else if (vsm.exponent >= 127 + 32) { } else if (vsm.exponent >= 127 + 31) {
/* /*
* m >= 2^31-2^7: invalid * 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 */ /* 2^0 <= m <= 2^31-2^7 */
d = (vsm.significand << 1) >> shift; d = (vsm.significand << 1) >> shift;
rem = vsm.significand << (33 - shift); rem = (vsm.significand << 1) << (32 - shift);
if (rmode == FPSCR_ROUND_NEAREST) { if (rmode == FPSCR_ROUND_NEAREST) {
incr = 0x80000000; incr = 0x80000000;
@ -701,10 +721,14 @@ static u32 vfp_single_ftosi(ARMul_State* state, int sd, int unused, s32 m, u32 f
d = 0; d = 0;
if (vsm.exponent | vsm.significand) { if (vsm.exponent | vsm.significand) {
exceptions |= FPSCR_IXC; 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; d = 1;
else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign) } else if (rmode == FPSCR_ROUND_MINUSINF && vsm.sign) {
d = -1; 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) 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[] = { static struct op fops_ext[] = {
@ -774,7 +798,7 @@ vfp_single_fadd_nonnumber(struct vfp_single *vsd, struct vfp_single *vsn,
/* /*
* different signs -> invalid * different signs -> invalid
*/ */
exceptions = FPSCR_IOC; exceptions |= FPSCR_IOC;
vsp = &vfp_single_default_qnan; vsp = &vfp_single_default_qnan;
} else { } 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_multiply_accumulate(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr, u32 negate, const char *func)
{ {
vfp_single vsd, vsp, vsn, vsm; vfp_single vsd, vsp, vsn, vsm;
u32 exceptions; u32 exceptions = 0;
s32 v; s32 v;
v = vfp_get_float(state, sn); v = vfp_get_float(state, sn);
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, v); 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) if (vsn.exponent == 0 && vsn.significand)
vfp_single_normalise_denormal(&vsn); vfp_single_normalise_denormal(&vsn);
vfp_single_unpack(&vsm, m, &fpscr); exceptions |= vfp_single_unpack(&vsm, m, fpscr);
if (vsm.exponent == 0 && vsm.significand) if (vsm.exponent == 0 && vsm.significand)
vfp_single_normalise_denormal(&vsm); 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) if (negate & NEG_MULTIPLY)
vsp.sign = vfp_sign_negate(vsp.sign); vsp.sign = vfp_sign_negate(vsp.sign);
v = vfp_get_float(state, sd); v = vfp_get_float(state, sd);
LOG_TRACE(Core_ARM11, "s%u = %08x", sd, v); 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) if (vsn.exponent == 0 && vsn.significand != 0)
vfp_single_normalise_denormal(&vsn); 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); 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) 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); 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) static u32 vfp_single_fmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
{ {
struct vfp_single vsd, vsn, vsm; struct vfp_single vsd, vsn, vsm;
u32 exceptions; u32 exceptions = 0;
s32 n = vfp_get_float(state, sn); s32 n = vfp_get_float(state, sn);
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n); 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) if (vsn.exponent == 0 && vsn.significand)
vfp_single_normalise_denormal(&vsn); vfp_single_normalise_denormal(&vsn);
vfp_single_unpack(&vsm, m, &fpscr); exceptions |= vfp_single_unpack(&vsm, m, fpscr);
if (vsm.exponent == 0 && vsm.significand) if (vsm.exponent == 0 && vsm.significand)
vfp_single_normalise_denormal(&vsm); vfp_single_normalise_denormal(&vsm);
exceptions = vfp_single_multiply(&vsd, &vsn, &vsm, fpscr); exceptions |= vfp_single_multiply(&vsd, &vsn, &vsm, fpscr);
return vfp_single_normaliseround(state, sd, &vsd, fpscr, exceptions, "fmul");
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) static u32 vfp_single_fnmul(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
{ {
struct vfp_single vsd, vsn, vsm; struct vfp_single vsd, vsn, vsm;
u32 exceptions; u32 exceptions = 0;
s32 n = vfp_get_float(state, sn); s32 n = vfp_get_float(state, sn);
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n); 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) if (vsn.exponent == 0 && vsn.significand)
vfp_single_normalise_denormal(&vsn); vfp_single_normalise_denormal(&vsn);
vfp_single_unpack(&vsm, m, &fpscr); exceptions |= vfp_single_unpack(&vsm, m, fpscr);
if (vsm.exponent == 0 && vsm.significand) if (vsm.exponent == 0 && vsm.significand)
vfp_single_normalise_denormal(&vsm); 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); 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) static u32 vfp_single_fadd(ARMul_State* state, int sd, int sn, s32 m, u32 fpscr)
{ {
struct vfp_single vsd, vsn, vsm; struct vfp_single vsd, vsn, vsm;
u32 exceptions; u32 exceptions = 0;
s32 n = vfp_get_float(state, sn); s32 n = vfp_get_float(state, sn);
LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n); 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. * Unpack and normalise denormals.
*/ */
vfp_single_unpack(&vsn, n, &fpscr); exceptions |= vfp_single_unpack(&vsn, n, fpscr);
if (vsn.exponent == 0 && vsn.significand) if (vsn.exponent == 0 && vsn.significand)
vfp_single_normalise_denormal(&vsn); vfp_single_normalise_denormal(&vsn);
vfp_single_unpack(&vsm, m, &fpscr); exceptions |= vfp_single_unpack(&vsm, m, fpscr);
if (vsm.exponent == 0 && vsm.significand) if (vsm.exponent == 0 && vsm.significand)
vfp_single_normalise_denormal(&vsm); 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); LOG_TRACE(Core_ARM11, "s%u = %08x", sn, n);
vfp_single_unpack(&vsn, n, &fpscr); exceptions |= vfp_single_unpack(&vsn, n, fpscr);
vfp_single_unpack(&vsm, m, &fpscr); exceptions |= vfp_single_unpack(&vsm, m, fpscr);
vsd.sign = vsn.sign ^ vsm.sign; 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) if ((vsd.significand & 0x3f) == 0)
vsd.significand |= ((u64)vsm.significand * vsd.significand != (u64)vsn.significand << 32); 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: vsn_nan:
exceptions = vfp_propagate_nan(&vsd, &vsn, &vsm, fpscr); exceptions |= vfp_propagate_nan(&vsd, &vsn, &vsm, fpscr);
pack: pack:
vfp_put_float(state, vfp_single_pack(&vsd), sd); vfp_put_float(state, vfp_single_pack(&vsd), sd);
return exceptions; return exceptions;
vsm_nan: vsm_nan:
exceptions = vfp_propagate_nan(&vsd, &vsm, &vsn, fpscr); exceptions |= vfp_propagate_nan(&vsd, &vsm, &vsn, fpscr);
goto pack; goto pack;
zero: zero:
@ -1180,7 +1213,7 @@ zero:
goto pack; goto pack;
divzero: divzero:
exceptions = FPSCR_DZC; exceptions |= FPSCR_DZC;
infinity: infinity:
vsd.exponent = 255; vsd.exponent = 255;
vsd.significand = 0; vsd.significand = 0;
@ -1188,7 +1221,8 @@ infinity:
invalid: invalid:
vfp_put_float(state, vfp_single_pack(&vfp_single_default_qnan), sd); vfp_put_float(state, vfp_single_pack(&vfp_single_default_qnan), sd);
return FPSCR_IOC; exceptions |= FPSCR_IOC;
return exceptions;
} }
static struct op fops[] = { static struct op fops[] = {

View File

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

View File

@ -646,7 +646,7 @@ static void ReadMemory() {
u8* data = Memory::GetPointer(addr); u8* data = Memory::GetPointer(addr);
if (!data) { if (!data) {
return SendReply("E0"); return SendReply("E00");
} }
MemToGdbHex(reply, data, len); 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. // The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory.
// Create the SharedMemory that will hold the framebuffer data // Create the SharedMemory that will hold the framebuffer data
Service::APT::CaptureBufferInfo capture_info; 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; using Kernel::MemoryPermission;
// Allocate a heap block of the required size for this applet. // 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 // Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result; Service::APT::MessageParameter result;
result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished); result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
result.data = nullptr; result.buffer.clear();
result.buffer_size = 0;
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application); result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id); result.sender_id = static_cast<u32>(id);
result.object = framebuffer_memory; 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): Set the expected fields in the response buffer before resending it to the application.
// TODO(Subv): Reverse the parameter format for the Mii Selector // TODO(Subv): Reverse the parameter format for the Mii Selector
if(parameter.buffer_size >= sizeof(u32)) { memcpy(&config, parameter.buffer.data(), parameter.buffer.size());
// TODO: defaults return no error, but garbage in other unknown fields
memset(parameter.data, 0, sizeof(u32)); // 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 // Let the application know that we're closing
Service::APT::MessageParameter message; Service::APT::MessageParameter message;
message.buffer_size = parameter.buffer_size; message.buffer.resize(sizeof(MiiResult));
message.data = parameter.data; std::memcpy(message.buffer.data(), &result, message.buffer.size());
message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed); message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application); message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id); message.sender_id = static_cast<u32>(id);

View File

@ -24,7 +24,7 @@ struct MiiConfig {
u8 unk_004; u8 unk_004;
INSERT_PADDING_BYTES(3); INSERT_PADDING_BYTES(3);
u16 unk_008; u16 unk_008;
INSERT_PADDING_BYTES(0x8C - 0xA); INSERT_PADDING_BYTES(0x82);
u8 unk_08C; u8 unk_08C;
INSERT_PADDING_BYTES(3); INSERT_PADDING_BYTES(3);
u16 unk_090; u16 unk_090;
@ -75,6 +75,8 @@ public:
/// Whether this applet is currently running instead of the host application or not. /// Whether this applet is currently running instead of the host application or not.
bool started; 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. // The LibAppJustStarted message contains a buffer with the size of the framebuffer shared memory.
// Create the SharedMemory that will hold the framebuffer data // Create the SharedMemory that will hold the framebuffer data
Service::APT::CaptureBufferInfo capture_info; 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; using Kernel::MemoryPermission;
// Allocate a heap block of the required size for this applet. // 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 // Send the response message with the newly created SharedMemory
Service::APT::MessageParameter result; Service::APT::MessageParameter result;
result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished); result.signal = static_cast<u32>(Service::APT::SignalType::LibAppFinished);
result.data = nullptr; result.buffer.clear();
result.buffer_size = 0;
result.destination_id = static_cast<u32>(Service::APT::AppletId::Application); result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
result.sender_id = static_cast<u32>(id); result.sender_id = static_cast<u32>(id);
result.object = framebuffer_memory; result.object = framebuffer_memory;
@ -61,9 +60,9 @@ ResultCode SoftwareKeyboard::ReceiveParameter(Service::APT::MessageParameter con
} }
ResultCode SoftwareKeyboard::StartImpl(Service::APT::AppletStartupParameter const& parameter) { 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); text_memory = boost::static_pointer_cast<Kernel::SharedMemory, Kernel::Object>(parameter.object);
// TODO(Subv): Verify if this is the correct behavior // 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]; auto info = bottom_screen->framebuffer_info[bottom_screen->index];
// TODO(Subv): Draw the HLE keyboard, for now just zero-fill the framebuffer // 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); GSP_GPU::SetBufferSwap(1, info);
} }
@ -107,8 +106,8 @@ void SoftwareKeyboard::DrawScreenKeyboard() {
void SoftwareKeyboard::Finalize() { void SoftwareKeyboard::Finalize() {
// Let the application know that we're closing // Let the application know that we're closing
Service::APT::MessageParameter message; Service::APT::MessageParameter message;
message.buffer_size = sizeof(SoftwareKeyboardConfig); message.buffer.resize(sizeof(SoftwareKeyboardConfig));
message.data = reinterpret_cast<u8*>(&config); std::memcpy(message.buffer.data(), &config, message.buffer.size());
message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed); message.signal = static_cast<u32>(Service::APT::SignalType::LibAppClosed);
message.destination_id = static_cast<u32>(Service::APT::AppletId::Application); message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
message.sender_id = static_cast<u32>(id); 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; 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); LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point);
// TODO: Verify error // TODO: Verify error
return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel, return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::Kernel,

View File

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

View File

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

View File

@ -14,6 +14,7 @@
#include "core/hle/service/apt/apt_u.h" #include "core/hle/service/apt/apt_u.h"
#include "core/hle/service/apt/bcfnt/bcfnt.h" #include "core/hle/service/apt/bcfnt/bcfnt.h"
#include "core/hle/service/fs/archive.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/event.h"
#include "core/hle/kernel/mutex.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 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 /// Parameter data to be returned in the next call to Glance/ReceiveParameter
static MessageParameter next_parameter; static MessageParameter next_parameter;
@ -176,12 +180,12 @@ void SendParameter(Service::Interface* self) {
} }
MessageParameter param; MessageParameter param;
param.buffer_size = buffer_size;
param.destination_id = dst_app_id; param.destination_id = dst_app_id;
param.sender_id = src_app_id; param.sender_id = src_app_id;
param.object = Kernel::g_handle_table.GetGeneric(handle); param.object = Kernel::g_handle_table.GetGeneric(handle);
param.signal = signal_type; 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; 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[1] = RESULT_SUCCESS.raw; // No error
cmd_buff[2] = next_parameter.sender_id; cmd_buff[2] = next_parameter.sender_id;
cmd_buff[3] = next_parameter.signal; // Signal type 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[5] = 0x10;
cmd_buff[6] = 0; cmd_buff[6] = 0;
if (next_parameter.object != nullptr) if (next_parameter.object != nullptr)
cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom(); 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; cmd_buff[8] = buffer;
if (next_parameter.data) Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size());
memcpy(Memory::GetPointer(buffer), next_parameter.data, std::min(buffer_size, next_parameter.buffer_size));
LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, 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[1] = RESULT_SUCCESS.raw; // No error
cmd_buff[2] = next_parameter.sender_id; cmd_buff[2] = next_parameter.sender_id;
cmd_buff[3] = next_parameter.signal; // Signal type 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[5] = 0x10;
cmd_buff[6] = 0; cmd_buff[6] = 0;
if (next_parameter.object != nullptr) if (next_parameter.object != nullptr)
cmd_buff[6] = Kernel::g_handle_table.Create(next_parameter.object).MoveFrom(); 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; cmd_buff[8] = buffer;
if (next_parameter.data) Memory::WriteBlock(buffer, next_parameter.buffer.data(), std::min(static_cast<size_t>(buffer_size), next_parameter.buffer.size()));
memcpy(Memory::GetPointer(buffer), next_parameter.data, std::min(buffer_size, next_parameter.buffer_size));
LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08X", app_id, 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 title_info4 = cmd_buff[4];
u32 flags = cmd_buff[5]; u32 flags = cmd_buff[5];
if (flags & 0x00000100) {
unknown_ns_state_field = 1;
}
cmd_buff[1] = RESULT_SUCCESS.raw; // No error 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," 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; return;
} }
size_t buffer_size = cmd_buff[2];
VAddr buffer_addr = cmd_buff[6];
AppletStartupParameter parameter; AppletStartupParameter parameter;
parameter.buffer_size = cmd_buff[2];
parameter.object = Kernel::g_handle_table.GetGeneric(cmd_buff[4]); 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; 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) { void GetAppletInfo(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
auto app_id = static_cast<AppletId>(cmd_buff[1]); 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; 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() { void Init() {
AddService(new APT_A_Interface); AddService(new APT_A_Interface);
AddService(new APT_S_Interface); AddService(new APT_S_Interface);
@ -441,6 +492,7 @@ void Init() {
lock = Kernel::Mutex::Create(false, "APT_U:Lock"); lock = Kernel::Mutex::Create(false, "APT_U:Lock");
cpu_percent = 0; cpu_percent = 0;
unknown_ns_state_field = 0;
// TODO(bunnei): Check if these are created in Initialize or on APT process startup. // 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"); notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification");

View File

@ -20,16 +20,14 @@ struct MessageParameter {
u32 sender_id = 0; u32 sender_id = 0;
u32 destination_id = 0; u32 destination_id = 0;
u32 signal = 0; u32 signal = 0;
u32 buffer_size = 0;
Kernel::SharedPtr<Kernel::Object> object = nullptr; Kernel::SharedPtr<Kernel::Object> object = nullptr;
u8* data = nullptr; std::vector<u8> buffer;
}; };
/// Holds information about the parameters used in StartLibraryApplet /// Holds information about the parameters used in StartLibraryApplet
struct AppletStartupParameter { struct AppletStartupParameter {
u32 buffer_size = 0;
Kernel::SharedPtr<Kernel::Object> object = nullptr; 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. /// 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); 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 /// Initialize the APT service
void Init(); void Init();

View File

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

View File

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

View File

@ -29,7 +29,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"}, {0x00120040, nullptr, "SetHomeMenuAppletIdForDebug"},
{0x00130000, nullptr, "GetPreparationState"}, {0x00130000, nullptr, "GetPreparationState"},
{0x00140040, nullptr, "SetPreparationState"}, {0x00140040, nullptr, "SetPreparationState"},
{0x00150140, nullptr, "PrepareToStartApplication"}, {0x00150140, PrepareToStartApplication, "PrepareToStartApplication"},
{0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"}, {0x00160040, PreloadLibraryApplet, "PreloadLibraryApplet"},
{0x00170040, nullptr, "FinishPreloadingLibraryApplet"}, {0x00170040, nullptr, "FinishPreloadingLibraryApplet"},
{0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"}, {0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"},
@ -92,9 +92,11 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00510080, GetStartupArgument, "GetStartupArgument"}, {0x00510080, GetStartupArgument, "GetStartupArgument"},
{0x00520104, nullptr, "Wrap1"}, {0x00520104, nullptr, "Wrap1"},
{0x00530104, nullptr, "Unwrap1"}, {0x00530104, nullptr, "Unwrap1"},
{0x00550040, SetNSStateField, "SetNSStateField?"},
{0x00560000, GetNSStateField, "GetNSStateField?"},
{0x00580002, nullptr, "GetProgramID"}, {0x00580002, nullptr, "GetProgramID"},
{0x01010000, nullptr, "CheckNew3DSApp"}, {0x01010000, CheckNew3DSApp, "CheckNew3DSApp"},
{0x01020000, nullptr, "CheckNew3DS"} {0x01020000, CheckNew3DS, "CheckNew3DS"}
}; };
APT_U_Interface::APT_U_Interface() { 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"); 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 { struct ConsoleModelInfo {
u8 model; ///< The console model (3DS, 2DS, etc) u8 model; ///< The console model (3DS, 2DS, etc)
u8 unknown[3]; ///< Unknown data 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 u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE;
static const ConsoleModelInfo CONSOLE_MODEL = { NINTENDO_3DS_XL, { 0, 0, 0 } }; static const ConsoleModelInfo CONSOLE_MODEL = { NINTENDO_3DS_XL, { 0, 0, 0 } };
static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN; static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN;
static const char CONSOLE_USERNAME[0x14] = "CITRA"; static const UsernameBlock CONSOLE_USERNAME_BLOCK = { u"CITRA", 0, 0 };
/// This will be initialized in Init, and will be used when creating the block static const BirthdayBlock PROFILE_BIRTHDAY = { 3, 25 }; // March 25th, 2014
static UsernameBlock CONSOLE_USERNAME_BLOCK;
/// TODO(Subv): Find out what this actually is /// TODO(Subv): Find out what this actually is
static const u8 SOUND_OUTPUT_MODE = 2; static const u8 SOUND_OUTPUT_MODE = 2;
static const u8 UNITED_STATES_COUNTRY_ID = 49; static const u8 UNITED_STATES_COUNTRY_ID = 49;
@ -191,28 +196,32 @@ void GetConfigInfoBlk2(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
u32 size = cmd_buff[1]; u32 size = cmd_buff[1];
u32 block_id = cmd_buff[2]; 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 cmd_buff[1] = -1; // TODO(Subv): Find the right error code
return; 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) { void GetConfigInfoBlk8(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
u32 size = cmd_buff[1]; u32 size = cmd_buff[1];
u32 block_id = cmd_buff[2]; 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 cmd_buff[1] = -1; // TODO(Subv): Find the right error code
return; 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) { void UpdateConfigNANDSavegame(Service::Interface* self) {
@ -329,32 +338,22 @@ ResultCode FormatConfig() {
res = CreateConfigInfoBlk(0x00050005, sizeof(STEREO_CAMERA_SETTINGS), 0xE, STEREO_CAMERA_SETTINGS.data()); res = CreateConfigInfoBlk(0x00050005, sizeof(STEREO_CAMERA_SETTINGS), 0xE, STEREO_CAMERA_SETTINGS.data());
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x00070001, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE); res = CreateConfigInfoBlk(0x00070001, sizeof(SOUND_OUTPUT_MODE), 0xE, &SOUND_OUTPUT_MODE);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x00090001, sizeof(CONSOLE_UNIQUE_ID), 0xE, &CONSOLE_UNIQUE_ID); res = CreateConfigInfoBlk(0x00090001, sizeof(CONSOLE_UNIQUE_ID), 0xE, &CONSOLE_UNIQUE_ID);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, &CONSOLE_USERNAME_BLOCK); res = CreateConfigInfoBlk(0x000A0000, sizeof(CONSOLE_USERNAME_BLOCK), 0xE, &CONSOLE_USERNAME_BLOCK);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
// 0x000A0000 - Profile username res = CreateConfigInfoBlk(0x000A0001, sizeof(PROFILE_BIRTHDAY), 0xE, &PROFILE_BIRTHDAY);
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);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000A0002, sizeof(CONSOLE_LANGUAGE), 0xE, &CONSOLE_LANGUAGE); res = CreateConfigInfoBlk(0x000A0002, sizeof(CONSOLE_LANGUAGE), 0xE, &CONSOLE_LANGUAGE);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO); res = CreateConfigInfoBlk(0x000B0000, sizeof(COUNTRY_INFO), 0xE, &COUNTRY_INFO);
if (!res.IsSuccess()) return res; if (!res.IsSuccess()) return res;
@ -435,17 +434,6 @@ void Init() {
return; 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(); 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 // Licensed under GPLv2 or any later version
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "common/common_types.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/hle/hle.h" #include "core/hle/result.h"
#include "core/hle/service/dlp_srvr.h" #include "core/hle/service/dlp/dlp_srvr.h"
//////////////////////////////////////////////////////////////////////////////////////////////////// namespace Service {
// Namespace DLP_SRVR namespace DLP {
namespace DLP_SRVR { static void unk_0x000E0040(Interface* self) {
static void unk_0x000E0040(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = RESULT_SUCCESS.raw; cmd_buff[1] = RESULT_SUCCESS.raw;
@ -23,14 +22,13 @@ static void unk_0x000E0040(Service::Interface* self) {
const Interface::FunctionInfo FunctionTable[] = { const Interface::FunctionInfo FunctionTable[] = {
{0x00010183, nullptr, "Initialize"}, {0x00010183, nullptr, "Initialize"},
{0x00020000, nullptr, "Finalize"}, {0x00020000, nullptr, "Finalize"},
{0x000800C0, nullptr, "SendWirelessRebootPassphrase"},
{0x000E0040, unk_0x000E0040, "unk_0x000E0040"}, {0x000E0040, unk_0x000E0040, "unk_0x000E0040"},
}; };
//////////////////////////////////////////////////////////////////////////////////////////////////// DLP_SRVR_Interface::DLP_SRVR_Interface() {
// Interface class
Interface::Interface() {
Register(FunctionTable); Register(FunctionTable);
} }
} // namespace } // namespace DLP
} // namespace Service

View File

@ -6,18 +6,17 @@
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
//////////////////////////////////////////////////////////////////////////////////////////////////// namespace Service {
// Namespace DLP_SRVR namespace DLP {
namespace DLP_SRVR { class DLP_SRVR_Interface final : public Interface {
class Interface : public Service::Interface {
public: public:
Interface(); DLP_SRVR_Interface();
std::string GetPortName() const override { std::string GetPortName() const override {
return "dlp:SRVR"; 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 // TODO(bunnei): Implement real DSP firmware loading
ASSERT(Memory::GetPointer(buffer) != nullptr); ASSERT(Memory::IsValidVirtualAddress(buffer));
ASSERT(size > 0x37C);
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. // 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", 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); size, prog_mask, data_mask, buffer);
@ -285,7 +288,7 @@ static void WriteProcessPipe(Service::Interface* self) {
return; 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); std::vector<u8> message(size);
for (u32 i = 0; i < size; i++) { 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); 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[0] = IPC::MakeHeader(0x10, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw; // No error 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); 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) { if (DSP::HLE::GetPipeReadableSize(pipe) >= size) {
std::vector<u8> response = DSP::HLE::PipeRead(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)); 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 cmd_buff[1] = RESULT_SUCCESS.raw; // No error
@ -39,8 +39,7 @@ void GetFriendKeyList(Service::Interface* self) {
FriendKey zero_key = {}; FriendKey zero_key = {};
for (u32 i = 0; i < frd_count; ++i) { for (u32 i = 0; i < frd_count; ++i) {
Memory::WriteBlock(frd_key_addr + i * sizeof(FriendKey), Memory::WriteBlock(frd_key_addr + i * sizeof(FriendKey), &zero_key, sizeof(FriendKey));
reinterpret_cast<const u8*>(&zero_key), sizeof(FriendKey));
} }
cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[1] = RESULT_SUCCESS.raw; // No error
@ -58,8 +57,7 @@ void GetFriendProfile(Service::Interface* self) {
Profile zero_profile = {}; Profile zero_profile = {};
for (u32 i = 0; i < count; ++i) { for (u32 i = 0; i < count; ++i) {
Memory::WriteBlock(profiles_addr + i * sizeof(Profile), Memory::WriteBlock(profiles_addr + i * sizeof(Profile), &zero_profile, sizeof(Profile));
reinterpret_cast<const u8*>(&zero_profile), sizeof(Profile));
} }
cmd_buff[1] = RESULT_SUCCESS.raw; // No error cmd_buff[1] = RESULT_SUCCESS.raw; // No error
@ -88,7 +86,7 @@ void GetMyFriendKey(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = RESULT_SUCCESS.raw; // No error 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"); LOG_WARNING(Service_FRD, "(STUBBED) called");
} }

View File

@ -108,13 +108,14 @@ ResultVal<bool> File::SyncRequest() {
offset, length, backend->GetSize()); 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()) { if (read.Failed()) {
cmd_buff[1] = read.Code().raw; cmd_buff[1] = read.Code().raw;
return read.Code(); return read.Code();
} }
Memory::WriteBlock(address, data.data(), *read);
cmd_buff[2] = static_cast<u32>(*read); cmd_buff[2] = static_cast<u32>(*read);
Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(address), length);
break; 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", 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); 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()) { if (written.Failed()) {
cmd_buff[1] = written.Code().raw; cmd_buff[1] = written.Code().raw;
return written.Code(); return written.Code();
@ -216,12 +219,14 @@ ResultVal<bool> Directory::SyncRequest() {
{ {
u32 count = cmd_buff[1]; u32 count = cmd_buff[1];
u32 address = cmd_buff[3]; 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", LOG_TRACE(Service_FS, "Read %s %s: count=%d",
GetTypeName().c_str(), GetName().c_str(), count); GetTypeName().c_str(), GetName().c_str(), count);
// Number of entries actually read // 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; break;
} }
@ -456,11 +461,12 @@ ResultCode CreateExtSaveData(MediaType media_type, u32 high, u32 low, VAddr icon
if (result.IsError()) if (result.IsError())
return result; return result;
u8* smdh_icon = Memory::GetPointer(icon_buffer); if (!Memory::IsValidVirtualAddress(icon_buffer))
if (!smdh_icon)
return ResultCode(-1); // TODO(Subv): Find the right error code 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; return RESULT_SUCCESS;
} }

View File

@ -44,7 +44,7 @@ Kernel::SharedPtr<Kernel::SharedMemory> g_shared_memory;
u32 g_thread_id = 0; u32 g_thread_id = 0;
static bool gpu_right_acquired = false; static bool gpu_right_acquired = false;
static bool first_initialization = true;
/// Gets a pointer to a thread command buffer in GSP shared memory /// Gets a pointer to a thread command buffer in GSP shared memory
static inline u8* GetCommandBuffer(u32 thread_id) { static inline u8* GetCommandBuffer(u32 thread_id) {
return g_shared_memory->GetPointer(0x800 + (thread_id * sizeof(CommandBuffer))); 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); 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 * 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 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 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 * @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 // This magic number is verified to be done by the gsp module
const u32 max_size_in_bytes = 0x80; 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; return ERR_GSP_REGS_MISALIGNED;
} else { } else {
while (size_in_bytes > 0) { while (size_in_bytes > 0) {
HW::Write<u32>(base_address + REGS_BEGIN, *data); WriteSingleHWReg(base_address, Memory::Read32(data_vaddr));
size_in_bytes -= 4; size_in_bytes -= 4;
++data; data_vaddr += 4;
base_address += 4; base_address += 4;
} }
return RESULT_SUCCESS; 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 * @param masks A pointer to the masks
* @return RESULT_SUCCESS if the parameters are valid, error code otherwise * @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 // This magic number is verified to be done by the gsp module
const u32 max_size_in_bytes = 0x80; 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; u32 reg_value;
HW::Read<u32>(reg_value, reg_address); HW::Read<u32>(reg_value, reg_address);
// Update the current value of the register only for set mask bits u32 data = Memory::Read32(data_vaddr);
reg_value = (reg_value & ~*masks) | (*data | *masks); 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; size_in_bytes -= 4;
++data; data_vaddr += 4;
++masks; masks_vaddr += 4;
base_address += 4; base_address += 4;
} }
return RESULT_SUCCESS; return RESULT_SUCCESS;
@ -164,8 +179,7 @@ static void WriteHWRegs(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
u32 reg_addr = cmd_buff[1]; u32 reg_addr = cmd_buff[1];
u32 size = cmd_buff[2]; u32 size = cmd_buff[2];
VAddr src = cmd_buff[4];
u32* src = (u32*)Memory::GetPointer(cmd_buff[4]);
cmd_buff[1] = WriteHWRegs(reg_addr, size, src).raw; 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 reg_addr = cmd_buff[1];
u32 size = cmd_buff[2]; u32 size = cmd_buff[2];
u32* src_data = (u32*)Memory::GetPointer(cmd_buff[4]); VAddr src_data = cmd_buff[4];
u32* mask_data = (u32*)Memory::GetPointer(cmd_buff[6]); VAddr mask_data = cmd_buff[6];
cmd_buff[1] = WriteHWRegsWithMask(reg_addr, size, src_data, mask_data).raw; cmd_buff[1] = WriteHWRegsWithMask(reg_addr, size, src_data, mask_data).raw;
} }
@ -210,13 +224,16 @@ static void ReadHWRegs(Service::Interface* self) {
return; return;
} }
u32* dst = (u32*)Memory::GetPointer(cmd_buff[0x41]); VAddr dst_vaddr = cmd_buff[0x41];
while (size > 0) { 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; size -= 4;
++dst; dst_vaddr += 4;
reg_addr += 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_left = Memory::VirtualToPhysicalAddress(info.address_left);
PAddr phys_address_right = Memory::VirtualToPhysicalAddress(info.address_right); PAddr phys_address_right = Memory::VirtualToPhysicalAddress(info.address_right);
if (info.active_fb == 0) { if (info.active_fb == 0) {
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left1)), WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left1)),
4, &phys_address_left); phys_address_left);
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right1)), WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right1)),
4, &phys_address_right); phys_address_right);
} else { } else {
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left2)), WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_left2)),
4, &phys_address_left); phys_address_left);
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right2)), WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].address_right2)),
4, &phys_address_right); phys_address_right);
} }
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].stride)), WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].stride)),
4, &info.stride); info.stride);
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].color_format)), WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].color_format)),
4, &info.format); info.format);
WriteHWRegs(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].active_fb)), WriteSingleHWReg(base_address + 4 * static_cast<u32>(GPU_REG_INDEX(framebuffer_config[screen_id].active_fb)),
4, &info.shown_fb); info.shown_fb);
if (Pica::g_debug_context) if (Pica::g_debug_context)
Pica::g_debug_context->OnEvent(Pica::DebugContext::Event::BufferSwapped, nullptr); 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]; u32 flags = cmd_buff[1];
g_interrupt_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[3]); 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!"); ASSERT_MSG((g_interrupt_event != nullptr), "handle is not valid!");
g_interrupt_event->name = "GSP_GPU::interrupt_event"; g_interrupt_event->name = "GSP_GPU::interrupt_event";
using Kernel::MemoryPermission; if (first_initialization) {
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();
// This specific code is required for a successful initialization, rather than 0 // 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; ErrorSummary::Success, ErrorLevel::Success).raw;
} else {
cmd_buff[1] = RESULT_SUCCESS.raw;
}
cmd_buff[2] = g_thread_id++; // Thread ID 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? 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) { static void UnregisterInterruptRelayQueue(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer(); u32* cmd_buff = Kernel::GetCommandBuffer();
g_shared_memory = nullptr; g_thread_id = 0;
g_interrupt_event = nullptr; g_interrupt_event = nullptr;
cmd_buff[1] = RESULT_SUCCESS.raw; 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), Memory::RasterizerFlushAndInvalidateRegion(Memory::VirtualToPhysicalAddress(command.dma_request.dest_address),
command.dma_request.size); command.dma_request.size);
memcpy(Memory::GetPointer(command.dma_request.dest_address), // TODO(Subv): These memory accesses should not go through the application's memory mapping.
Memory::GetPointer(command.dma_request.source_address), // They should go through the GSP module's memory mapping.
command.dma_request.size); Memory::CopyBlock(command.dma_request.dest_address, command.dma_request.source_address, command.dma_request.size);
SignalInterrupt(InterruptId::DMA); SignalInterrupt(InterruptId::DMA);
break; break;
} }
@ -701,10 +719,15 @@ Interface::Interface() {
Register(FunctionTable); Register(FunctionTable);
g_interrupt_event = nullptr; 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; g_thread_id = 0;
gpu_right_acquired = false; gpu_right_acquired = false;
first_initialization = true;
} }
Interface::~Interface() { Interface::~Interface() {

View File

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

View File

@ -3,7 +3,7 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/settings.h"
#include "core/file_sys/file_backend.h" #include "core/file_sys/file_backend.h"
#include "core/hle/service/fs/archive.h" #include "core/hle/service/fs/archive.h"
#include "core/hle/service/ptm/ptm.h" #include "core/hle/service/ptm/ptm.h"
@ -89,6 +89,20 @@ void IsLegacyPowerOff(Service::Interface* self) {
LOG_WARNING(Service_PTM, "(STUBBED) called"); 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() { void Init() {
AddService(new PTM_Play_Interface); AddService(new PTM_Play_Interface);
AddService(new PTM_Sysm_Interface); AddService(new PTM_Sysm_Interface);

View File

@ -88,6 +88,14 @@ void GetTotalStepCount(Interface* self);
*/ */
void IsLegacyPowerOff(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 /// Initialize the PTM service
void Init(); void Init();

View File

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

View File

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

View File

@ -373,14 +373,18 @@ static void Bind(Service::Interface* self) {
u32* cmd_buffer = Kernel::GetCommandBuffer(); u32* cmd_buffer = Kernel::GetCommandBuffer();
u32 socket_handle = cmd_buffer[1]; u32 socket_handle = cmd_buffer[1];
u32 len = cmd_buffer[2]; 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 cmd_buffer[1] = -1; // TODO(Subv): Correct code
return; 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)); 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); result = TranslateError(GET_ERRNO);
} else { } else {
CTRSockAddr ctr_addr = CTRSockAddr::FromPlatform(addr); 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); cmd_buffer[0] = IPC::MakeHeader(4, 2, 2);
@ -547,20 +551,31 @@ static void SendTo(Service::Interface* self) {
u32 flags = cmd_buffer[3]; u32 flags = cmd_buffer[3];
u32 addr_len = cmd_buffer[4]; u32 addr_len = cmd_buffer[4];
u8* input_buff = Memory::GetPointer(cmd_buffer[8]); VAddr input_buff_address = cmd_buffer[8];
CTRSockAddr* ctr_dest_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[10])); if (!Memory::IsValidVirtualAddress(input_buff_address)) {
if (ctr_dest_addr == nullptr) {
cmd_buffer[1] = -1; // TODO(Subv): Find the right error code cmd_buffer[1] = -1; // TODO(Subv): Find the right error code
return; 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; int ret = -1;
if (addr_len > 0) { if (addr_len > 0) {
sockaddr dest_addr = CTRSockAddr::ToPlatform(*ctr_dest_addr); sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr);
ret = ::sendto(socket_handle, (const char*)input_buff, len, flags, &dest_addr, sizeof(dest_addr)); ret = ::sendto(socket_handle, reinterpret_cast<const char*>(input_buff.data()), len, flags, &dest_addr, sizeof(dest_addr));
} else { } 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; int result = 0;
@ -591,14 +606,24 @@ static void RecvFrom(Service::Interface* self) {
std::memcpy(&buffer_parameters, &cmd_buffer[64], sizeof(buffer_parameters)); 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; sockaddr src_addr;
socklen_t src_addr_len = sizeof(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) { 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)); CTRSockAddr ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
*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; int result = 0;
@ -606,6 +631,9 @@ static void RecvFrom(Service::Interface* self) {
if (ret == SOCKET_ERROR_VALUE) { if (ret == SOCKET_ERROR_VALUE) {
result = TranslateError(GET_ERRNO); result = TranslateError(GET_ERRNO);
total_received = 0; 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; cmd_buffer[1] = result;
@ -617,18 +645,28 @@ static void Poll(Service::Interface* self) {
u32* cmd_buffer = Kernel::GetCommandBuffer(); u32* cmd_buffer = Kernel::GetCommandBuffer();
u32 nfds = cmd_buffer[1]; u32 nfds = cmd_buffer[1];
int timeout = cmd_buffer[2]; 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) // The 3ds_pollfd and the pollfd structures may be different (Windows/Linux have different sizes)
// so we have to copy the data // so we have to copy the data
std::vector<pollfd> platform_pollfd(nfds); 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); const int ret = ::poll(platform_pollfd.data(), nfds, timeout);
// Now update the output pollfd structure // 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; int result = 0;
if (ret == SOCKET_ERROR_VALUE) if (ret == SOCKET_ERROR_VALUE)
@ -643,14 +681,16 @@ static void GetSockName(Service::Interface* self) {
u32 socket_handle = cmd_buffer[1]; u32 socket_handle = cmd_buffer[1];
socklen_t ctr_len = cmd_buffer[2]; 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; sockaddr dest_addr;
socklen_t dest_addr_len = sizeof(dest_addr); socklen_t dest_addr_len = sizeof(dest_addr);
int ret = ::getsockname(socket_handle, &dest_addr, &dest_addr_len); int ret = ::getsockname(socket_handle, &dest_addr, &dest_addr_len);
if (ctr_dest_addr != nullptr) { if (ctr_dest_addr_addr != 0 && Memory::IsValidVirtualAddress(ctr_dest_addr_addr)) {
*ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr); CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
Memory::WriteBlock(ctr_dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr));
} else { } else {
cmd_buffer[1] = -1; // TODO(Subv): Verify error cmd_buffer[1] = -1; // TODO(Subv): Verify error
return; return;
@ -682,14 +722,16 @@ static void GetPeerName(Service::Interface* self) {
u32 socket_handle = cmd_buffer[1]; u32 socket_handle = cmd_buffer[1];
socklen_t len = cmd_buffer[2]; 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; sockaddr dest_addr;
socklen_t dest_addr_len = sizeof(dest_addr); socklen_t dest_addr_len = sizeof(dest_addr);
int ret = ::getpeername(socket_handle, &dest_addr, &dest_addr_len); int ret = ::getpeername(socket_handle, &dest_addr, &dest_addr_len);
if (ctr_dest_addr != nullptr) { if (ctr_dest_addr_addr != 0 && Memory::IsValidVirtualAddress(ctr_dest_addr_addr)) {
*ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr); CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
Memory::WriteBlock(ctr_dest_addr_addr, &ctr_dest_addr, sizeof(ctr_dest_addr));
} else { } else {
cmd_buffer[1] = -1; cmd_buffer[1] = -1;
return; return;
@ -711,13 +753,17 @@ static void Connect(Service::Interface* self) {
u32 socket_handle = cmd_buffer[1]; u32 socket_handle = cmd_buffer[1];
socklen_t len = cmd_buffer[2]; socklen_t len = cmd_buffer[2];
CTRSockAddr* ctr_input_addr = reinterpret_cast<CTRSockAddr*>(Memory::GetPointer(cmd_buffer[6])); // Memory address of the ctr_input_addr structure
if (ctr_input_addr == nullptr) { VAddr ctr_input_addr_addr = cmd_buffer[6];
if (!Memory::IsValidVirtualAddress(ctr_input_addr_addr)) {
cmd_buffer[1] = -1; // TODO(Subv): Verify error cmd_buffer[1] = -1; // TODO(Subv): Verify error
return; 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 ret = ::connect(socket_handle, &input_addr, sizeof(input_addr));
int result = 0; int result = 0;
if (ret != 0) if (ret != 0)

View File

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

View File

@ -6,6 +6,7 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/scope_exit.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "common/symbols.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) { if (wait_thread) {
// Actually wait the current thread on each object if we decided to wait... // 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/file_sys/archive_romfs.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/resource_limit.h"
#include "core/hle/service/fs/archive.h"
#include "core/loader/3dsx.h" #include "core/loader/3dsx.h"
#include "core/memory.h" #include "core/memory.h"
@ -263,6 +264,8 @@ ResultStatus AppLoader_THREEDSX::Load() {
Kernel::g_current_process->Run(48, Kernel::DEFAULT_STACK_SIZE); 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; is_loaded = true;
return ResultStatus::Success; return ResultStatus::Success;
} }

View File

@ -27,6 +27,14 @@ public:
*/ */
static FileType IdentifyType(FileUtil::IOFile& file); 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 * Load the bootable file
* @return ResultStatus result of function * @return ResultStatus result of function

View File

@ -27,6 +27,14 @@ public:
*/ */
static FileType IdentifyType(FileUtil::IOFile& file); 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 * Load the bootable file
* @return ResultStatus result of function * @return ResultStatus result of function

View File

@ -8,9 +8,7 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/string_util.h" #include "common/string_util.h"
#include "core/file_sys/archive_romfs.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/service/fs/archive.h"
#include "core/loader/3dsx.h" #include "core/loader/3dsx.h"
#include "core/loader/elf.h" #include "core/loader/elf.h"
#include "core/loader/ncch.h" #include "core/loader/ncch.h"
@ -67,6 +65,9 @@ FileType GuessFromExtension(const std::string& extension_) {
if (extension == ".3dsx") if (extension == ".3dsx")
return FileType::THREEDSX; return FileType::THREEDSX;
if (extension == ".cia")
return FileType::CIA;
return FileType::Unknown; return FileType::Unknown;
} }
@ -90,7 +91,15 @@ const char* GetFileTypeString(FileType type) {
return "unknown"; 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) { const std::string& filename, const std::string& filepath) {
switch (type) { 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); return std::make_unique<AppLoader_NCCH>(std::move(file), filepath);
default: 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"); FileUtil::IOFile file(filename, "rb");
if (!file.IsOpen()) { if (!file.IsOpen()) {
LOG_ERROR(Loader, "Failed to load file %s", filename.c_str()); LOG_ERROR(Loader, "Failed to load file %s", filename.c_str());
return ResultStatus::Error; return nullptr;
} }
std::string filename_filename, filename_extension; 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)); 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); return GetFileLoader(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;
} }
} // namespace Loader } // namespace Loader

View File

@ -10,10 +10,8 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/file_util.h" #include "common/file_util.h"
#include "common/swap.h"
namespace Kernel { namespace Kernel {
struct AddressMapping; 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; 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 /// Interface for loading an application
class AppLoader : NonCopyable { class AppLoader : NonCopyable {
public: public:
AppLoader(FileUtil::IOFile&& file) : file(std::move(file)) { } AppLoader(FileUtil::IOFile&& file) : file(std::move(file)) { }
virtual ~AppLoader() { } virtual ~AppLoader() { }
/**
* Returns the type of this file
* @return FileType corresponding to the loaded file
*/
virtual FileType GetFileType() = 0;
/** /**
* Load the application * Load the application
* @return ResultStatus result of function * @return ResultStatus result of function
@ -197,20 +156,10 @@ protected:
extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings; extern const std::initializer_list<Kernel::AddressMapping> default_address_mappings;
/** /**
* Get a loader for a file with a specific type * Identifies a bootable file and return a suitable loader
* @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
* @param filename String filename of bootable file * @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 } // namespace

View File

@ -10,8 +10,10 @@
#include "common/string_util.h" #include "common/string_util.h"
#include "common/swap.h" #include "common/swap.h"
#include "core/file_sys/archive_romfs.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/resource_limit.h"
#include "core/hle/service/fs/archive.h"
#include "core/loader/ncch.h" #include "core/loader/ncch.h"
#include "core/memory.h" #include "core/memory.h"
@ -303,7 +305,12 @@ ResultStatus AppLoader_NCCH::Load() {
is_loaded = true; // Set state to loaded 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) { ResultStatus AppLoader_NCCH::ReadCode(std::vector<u8>& buffer) {

View File

@ -173,6 +173,14 @@ public:
*/ */
static FileType IdentifyType(FileUtil::IOFile& file); 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 * Load the application
* @return ResultStatus result of function * @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* GetPointer(const VAddr vaddr) {
u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS]; u8* page_pointer = current_page_table->pointers[vaddr >> PAGE_BITS];
if (page_pointer) { if (page_pointer) {
@ -261,6 +281,7 @@ u8* GetPointer(const VAddr vaddr) {
} }
u8* GetPhysicalPointer(PAddr address) { u8* GetPhysicalPointer(PAddr address) {
// TODO(Subv): This call should not go through the application's memory mapping.
return GetPointer(PhysicalToVirtualAddress(address)); return GetPointer(PhysicalToVirtualAddress(address));
} }
@ -343,6 +364,59 @@ u64 Read64(const VAddr addr) {
return Read<u64_le>(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) { void Write8(const VAddr addr, const u8 data) {
Write<u8>(addr, data); Write<u8>(addr, data);
} }
@ -359,9 +433,165 @@ void Write64(const VAddr addr, const u64 data) {
Write<u64_le>(addr, data); Write<u64_le>(addr, data);
} }
void WriteBlock(const VAddr addr, const u8* data, const size_t size) { void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size) {
for (u32 offset = 0; offset < size; offset++) { size_t remaining_size = size;
Write8(addr + offset, data[offset]); 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, 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); u8 Read8(VAddr addr);
u16 Read16(VAddr addr); u16 Read16(VAddr addr);
u32 Read32(VAddr addr); u32 Read32(VAddr addr);
@ -120,7 +123,10 @@ void Write16(VAddr addr, u16 data);
void Write32(VAddr addr, u32 data); void Write32(VAddr addr, u32 data);
void Write64(VAddr addr, u64 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); u8* GetPointer(VAddr virtual_address);

View File

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

View File

@ -18,15 +18,21 @@ class MMIORegion {
public: public:
virtual ~MMIORegion() = default; virtual ~MMIORegion() = default;
virtual bool IsValidAddress(VAddr addr) = 0;
virtual u8 Read8(VAddr addr) = 0; virtual u8 Read8(VAddr addr) = 0;
virtual u16 Read16(VAddr addr) = 0; virtual u16 Read16(VAddr addr) = 0;
virtual u32 Read32(VAddr addr) = 0; virtual u32 Read32(VAddr addr) = 0;
virtual u64 Read64(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 Write8(VAddr addr, u8 data) = 0;
virtual void Write16(VAddr addr, u16 data) = 0; virtual void Write16(VAddr addr, u16 data) = 0;
virtual void Write32(VAddr addr, u32 data) = 0; virtual void Write32(VAddr addr, u32 data) = 0;
virtual void Write64(VAddr addr, u64 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>; using MMIORegionPointer = std::shared_ptr<MMIORegion>;

View File

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

View File

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

View File

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

View File

@ -380,6 +380,17 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
SyncCombinerColor(); SyncCombinerColor();
break; 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 // Fragment lighting specular 0 color
case PICA_REG_INDEX_WORKAROUND(lighting.light[0].specular_0, 0x140 + 0 * 0x10): case PICA_REG_INDEX_WORKAROUND(lighting.light[0].specular_0, 0x140 + 0 * 0x10):
SyncLightSpecular0(0); SyncLightSpecular0(0);
@ -518,6 +529,70 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
SyncLightPosition(7); SyncLightPosition(7);
break; 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) // Fragment lighting global ambient color (emission + ambient * ambient)
case PICA_REG_INDEX_WORKAROUND(lighting.global_ambient, 0x1c0): case PICA_REG_INDEX_WORKAROUND(lighting.global_ambient, 0x1c0):
SyncGlobalAmbient(); SyncGlobalAmbient();
@ -896,6 +971,8 @@ void RasterizerOpenGL::SetShader() {
SyncLightDiffuse(light_index); SyncLightDiffuse(light_index);
SyncLightAmbient(light_index); SyncLightAmbient(light_index);
SyncLightPosition(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; 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); unsigned num = regs.lighting.light_enable.GetNum(light_index);
const auto& light = regs.lighting.light[num]; const auto& light = regs.lighting.light[num];
state.lighting.light[light_index].num = num; state.lighting.light[light_index].num = num;
state.lighting.light[light_index].directional = light.directional != 0; state.lighting.light[light_index].directional = light.config.directional != 0;
state.lighting.light[light_index].two_sided_diffuse = light.two_sided_diffuse != 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_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.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.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_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.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.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_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.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.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_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.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.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_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.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.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_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.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.type = regs.lighting.lut_input.rb.Value();
state.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb); state.lighting.lut_rb.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.rb);
state.lighting.config = regs.lighting.config; state.lighting.config = regs.lighting.config0.config;
state.lighting.fresnel_selector = regs.lighting.fresnel_selector; state.lighting.fresnel_selector = regs.lighting.config0.fresnel_selector;
state.lighting.bump_mode = regs.lighting.bump_mode; state.lighting.bump_mode = regs.lighting.config0.bump_mode;
state.lighting.bump_selector = regs.lighting.bump_selector; state.lighting.bump_selector = regs.lighting.config0.bump_selector;
state.lighting.bump_renorm = regs.lighting.disable_bump_renorm == 0; state.lighting.bump_renorm = regs.lighting.config0.disable_bump_renorm == 0;
state.lighting.clamp_highlights = regs.lighting.clamp_highlights != 0; state.lighting.clamp_highlights = regs.lighting.config0.clamp_highlights != 0;
return res; return res;
} }
@ -184,8 +182,6 @@ union PicaShaderConfig {
bool directional; bool directional;
bool two_sided_diffuse; bool two_sided_diffuse;
bool dist_atten_enable; bool dist_atten_enable;
GLfloat dist_atten_scale;
GLfloat dist_atten_bias;
} light[8]; } light[8];
bool enable; bool enable;
@ -316,6 +312,8 @@ private:
alignas(16) GLvec3 diffuse; alignas(16) GLvec3 diffuse;
alignas(16) GLvec3 ambient; alignas(16) GLvec3 ambient;
alignas(16) GLvec3 position; 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 /// Uniform structure for the Uniform Buffer Object, all members must be 16-byte aligned
@ -330,7 +328,7 @@ private:
LightSrc light_src[8]; 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"); 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 /// 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 /// Syncs the specified light's position to match the PICA register
void SyncLightPosition(int light_index); 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; OpenGLState state;
RasterizerCacheOpenGL res_cache; RasterizerCacheOpenGL res_cache;

View File

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

View File

@ -450,7 +450,7 @@ static const char* GetType(GLenum type) {
#undef RET #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) { const GLchar* message, const void* user_param) {
Log::Level level; Log::Level level;
switch (severity) { switch (severity) {

View File

@ -30,6 +30,58 @@ namespace Pica {
namespace Shader { 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 #ifdef ARCHITECTURE_x86_64
static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map; static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map;
static const JitShader* jit_shader; static const JitShader* jit_shader;
@ -62,7 +114,7 @@ void ShaderSetup::Setup() {
MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240)); 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& config = g_state.regs.vs;
auto& setup = g_state.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); RunInterpreter(setup, state, config.main_offset);
#endif // ARCHITECTURE_x86_64 #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) { 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(std::is_pod<OutputVertex>::value, "Structure is not POD");
static_assert(sizeof(OutputVertex) == 32 * sizeof(float), "OutputVertex has invalid size"); 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 // Helper structure used to keep track of data useful for inspection of shader emulation
template<bool full_debugging> template<bool full_debugging>
struct DebugData; struct DebugData;
@ -267,11 +276,12 @@ struct UnitState {
// The registers are accessed by the shader JIT using SSE instructions, and are therefore // The registers are accessed by the shader JIT using SSE instructions, and are therefore
// required to be 16-byte aligned. // required to be 16-byte aligned.
alignas(16) Math::Vec4<float24> input[16]; alignas(16) Math::Vec4<float24> input[16];
alignas(16) Math::Vec4<float24> output[16];
alignas(16) Math::Vec4<float24> temporary[16]; alignas(16) Math::Vec4<float24> temporary[16];
} registers; } registers;
static_assert(std::is_pod<Registers>::value, "Structure is not POD"); static_assert(std::is_pod<Registers>::value, "Structure is not POD");
OutputRegisters output_registers;
bool conditional_code[2]; bool conditional_code[2];
// Two Address registers and one loop counter // Two Address registers and one loop counter
@ -297,7 +307,7 @@ struct UnitState {
static size_t OutputOffset(const DestRegister& reg) { static size_t OutputOffset(const DestRegister& reg) {
switch (reg.GetRegisterType()) { switch (reg.GetRegisterType()) {
case RegisterType::Output: 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: case RegisterType::Temporary:
return offsetof(UnitState, registers.temporary) + reg.GetIndex()*sizeof(Math::Vec4<float24>); 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 state Shader unit state, must be setup per shader and per shader unit
* @param input Input vertex into the shader * @param input Input vertex into the shader
* @param num_attributes The number of vertex shader attributes * @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 * 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); 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] : (instr.common.dest.Value() < 0x20) ? &state.registers.temporary[instr.common.dest.Value().GetIndex()][0]
: dummy_vec4_float24; : dummy_vec4_float24;
@ -483,7 +483,7 @@ void RunInterpreter(const ShaderSetup& setup, UnitState<Debug>& state, unsigned
src3[3] = src3[3] * float24::FromFloat32(-1); 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] : (instr.mad.dest.Value() < 0x20) ? &state.registers.temporary[instr.mad.dest.Value().GetIndex()][0]
: dummy_vec4_float24; : dummy_vec4_float24;

View File

@ -2,8 +2,8 @@
#include <boost/range/algorithm/fill.hpp> #include <boost/range/algorithm/fill.hpp>
#include "common/assert.h"
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h"
#include "common/bit_field.h" #include "common/bit_field.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@ -21,6 +21,8 @@
namespace Pica { namespace Pica {
void VertexLoader::Setup(const Pica::Regs& regs) { 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; const auto& attribute_config = regs.vertex_attributes;
num_total_attributes = attribute_config.GetNumTotalAttributes(); 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) { 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) { for (int i = 0; i < num_total_attributes; ++i) {
if (vertex_attribute_elements[i] != 0) { if (vertex_attribute_elements[i] != 0) {
// Load per-vertex data from the loader arrays // Load per-vertex data from the loader arrays

View File

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