summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt4
-rw-r--r--src/citra/CMakeLists.txt2
-rw-r--r--src/citra/citra.cpp3
-rw-r--r--src/citra/config.cpp10
-rw-r--r--src/citra/default_ini.h4
-rw-r--r--src/citra/emu_window/emu_window_sdl2.cpp4
-rw-r--r--src/citra_qt/CMakeLists.txt9
-rw-r--r--src/citra_qt/bootmanager.cpp3
-rw-r--r--src/citra_qt/configuration/config.cpp20
-rw-r--r--src/citra_qt/configuration/configure_debug.ui7
-rw-r--r--src/citra_qt/configuration/configure_general.cpp9
-rw-r--r--src/citra_qt/configuration/configure_general.ui28
-rw-r--r--src/citra_qt/debugger/wait_tree.h1
-rw-r--r--src/citra_qt/main.cpp21
-rw-r--r--src/citra_qt/main.h1
-rw-r--r--src/citra_qt/ui_settings.h7
-rw-r--r--src/common/break_points.cpp1
-rw-r--r--src/common/logging/backend.cpp4
-rw-r--r--src/common/logging/log.h2
-rw-r--r--src/common/logging/text_formatter.cpp1
-rw-r--r--src/core/CMakeLists.txt9
-rw-r--r--src/core/core.cpp10
-rw-r--r--src/core/hle/function_wrappers.h38
-rw-r--r--src/core/hle/ipc.h3
-rw-r--r--src/core/hle/ipc_helpers.h149
-rw-r--r--src/core/hle/kernel/client_session.cpp29
-rw-r--r--src/core/hle/kernel/client_session.h4
-rw-r--r--src/core/hle/kernel/errors.h5
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp111
-rw-r--r--src/core/hle/kernel/hle_ipc.h93
-rw-r--r--src/core/hle/kernel/memory.cpp6
-rw-r--r--src/core/hle/kernel/process.cpp2
-rw-r--r--src/core/hle/kernel/server_port.cpp12
-rw-r--r--src/core/hle/kernel/server_port.h11
-rw-r--r--src/core/hle/kernel/server_session.cpp24
-rw-r--r--src/core/hle/kernel/server_session.h16
-rw-r--r--src/core/hle/kernel/shared_memory.cpp2
-rw-r--r--src/core/hle/kernel/thread.cpp4
-rw-r--r--src/core/hle/kernel/timer.cpp2
-rw-r--r--src/core/hle/result.h7
-rw-r--r--src/core/hle/romfs.cpp102
-rw-r--r--src/core/hle/romfs.h22
-rw-r--r--src/core/hle/service/apt/apt.cpp168
-rw-r--r--src/core/hle/service/apt/bcfnt/bcfnt.cpp6
-rw-r--r--src/core/hle/service/boss/boss_p.cpp3
-rw-r--r--src/core/hle/service/cam/cam.cpp6
-rw-r--r--src/core/hle/service/cecd/cecd.cpp8
-rw-r--r--src/core/hle/service/cfg/cfg.cpp4
-rw-r--r--src/core/hle/service/csnd_snd.cpp4
-rw-r--r--src/core/hle/service/dsp_dsp.cpp2
-rw-r--r--src/core/hle/service/frd/frd.cpp43
-rw-r--r--src/core/hle/service/frd/frd.h13
-rw-r--r--src/core/hle/service/frd/frd_u.cpp2
-rw-r--r--src/core/hle/service/fs/archive.cpp4
-rw-r--r--src/core/hle/service/fs/fs_user.cpp6
-rw-r--r--src/core/hle/service/gsp_gpu.cpp15
-rw-r--r--src/core/hle/service/hid/hid.cpp12
-rw-r--r--src/core/hle/service/ir/ir_rst.cpp4
-rw-r--r--src/core/hle/service/ir/ir_user.cpp6
-rw-r--r--src/core/hle/service/mic_u.cpp2
-rw-r--r--src/core/hle/service/nfc/nfc.cpp4
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp127
-rw-r--r--src/core/hle/service/nwm/nwm_uds.h7
-rw-r--r--src/core/hle/service/nwm/uds_beacon.h3
-rw-r--r--src/core/hle/service/nwm/uds_data.cpp278
-rw-r--r--src/core/hle/service/nwm/uds_data.h78
-rw-r--r--src/core/hle/service/ptm/ptm.cpp2
-rw-r--r--src/core/hle/service/service.cpp106
-rw-r--r--src/core/hle/service/service.h150
-rw-r--r--src/core/hle/service/sm/sm.cpp14
-rw-r--r--src/core/hle/service/sm/sm.h10
-rw-r--r--src/core/hle/service/sm/srv.cpp164
-rw-r--r--src/core/hle/service/sm/srv.h26
-rw-r--r--src/core/hle/service/y2r_u.cpp6
-rw-r--r--src/core/hle/svc.cpp156
-rw-r--r--src/core/hw/aes/key.h2
-rw-r--r--src/core/hw/gpu.cpp41
-rw-r--r--src/core/hw/gpu.h2
-rw-r--r--src/core/loader/ncch.cpp6
-rw-r--r--src/core/memory.cpp153
-rw-r--r--src/core/memory.h38
-rw-r--r--src/core/settings.h3
-rw-r--r--src/core/telemetry_session.cpp86
-rw-r--r--src/network/CMakeLists.txt18
-rw-r--r--src/network/network.cpp50
-rw-r--r--src/network/network.h25
-rw-r--r--src/network/packet.cpp225
-rw-r--r--src/network/packet.h162
-rw-r--r--src/network/room.cpp454
-rw-r--r--src/network/room.h84
-rw-r--r--src/network/room_member.cpp382
-rw-r--r--src/network/room_member.h131
-rw-r--r--src/tests/CMakeLists.txt5
-rw-r--r--src/tests/core/hle/kernel/hle_ipc.cpp216
-rw-r--r--src/video_core/pica_state.h20
-rw-r--r--src/video_core/regs_lighting.h67
-rw-r--r--src/video_core/regs_texturing.h8
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp215
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h16
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp216
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.h5
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp46
-rw-r--r--src/video_core/renderer_opengl/gl_state.h29
-rw-r--r--src/video_core/renderer_opengl/pica_to_gl.h14
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp15
-rw-r--r--src/video_core/shader/shader_jit_x64_compiler.cpp26
-rw-r--r--src/video_core/swrasterizer/rasterizer.cpp23
-rw-r--r--src/video_core/swrasterizer/texturing.cpp19
-rw-r--r--src/web_service/CMakeLists.txt14
-rw-r--r--src/web_service/telemetry_json.cpp87
-rw-r--r--src/web_service/telemetry_json.h54
-rw-r--r--src/web_service/web_backend.cpp52
-rw-r--r--src/web_service/web_backend.h31
113 files changed, 4670 insertions, 611 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index a45439481..e11940f59 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -5,6 +5,7 @@ add_subdirectory(common)
add_subdirectory(core)
add_subdirectory(video_core)
add_subdirectory(audio_core)
+add_subdirectory(network)
add_subdirectory(input_common)
add_subdirectory(tests)
if (ENABLE_SDL2)
@@ -13,3 +14,6 @@ endif()
if (ENABLE_QT)
add_subdirectory(citra_qt)
endif()
+if (ENABLE_WEB_SERVICE)
+ add_subdirectory(web_service)
+endif()
diff --git a/src/citra/CMakeLists.txt b/src/citra/CMakeLists.txt
index d72d2b5f4..a885f22f8 100644
--- a/src/citra/CMakeLists.txt
+++ b/src/citra/CMakeLists.txt
@@ -16,7 +16,7 @@ set(HEADERS
create_directory_groups(${SRCS} ${HEADERS})
add_executable(citra ${SRCS} ${HEADERS})
-target_link_libraries(citra PRIVATE common core input_common)
+target_link_libraries(citra PRIVATE common core input_common network)
target_link_libraries(citra PRIVATE inih glad)
if (MSVC)
target_link_libraries(citra PRIVATE getopt)
diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp
index dd357ff72..14574e56c 100644
--- a/src/citra/citra.cpp
+++ b/src/citra/citra.cpp
@@ -18,7 +18,10 @@
#endif
#ifdef _WIN32
+// windows.h needs to be included before shellapi.h
#include <windows.h>
+
+#include <shellapi.h>
#endif
#include "citra/config.h"
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index f08b4069c..69247b166 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -88,9 +88,9 @@ void Config::ReadValues() {
Settings::values.toggle_framelimit =
sdl2_config->GetBoolean("Renderer", "toggle_framelimit", true);
- Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 1.0);
- Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 1.0);
- Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 1.0);
+ Settings::values.bg_red = (float)sdl2_config->GetReal("Renderer", "bg_red", 0.0);
+ Settings::values.bg_green = (float)sdl2_config->GetReal("Renderer", "bg_green", 0.0);
+ Settings::values.bg_blue = (float)sdl2_config->GetReal("Renderer", "bg_blue", 0.0);
// Layout
Settings::values.layout_option =
@@ -151,6 +151,10 @@ void Config::ReadValues() {
Settings::values.use_gdbstub = sdl2_config->GetBoolean("Debugging", "use_gdbstub", false);
Settings::values.gdbstub_port =
static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
+
+ // Web Service
+ Settings::values.telemetry_endpoint_url = sdl2_config->Get(
+ "WebService", "telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry");
}
void Config::Reload() {
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index d8a8fe44f..a12498e0f 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -168,5 +168,9 @@ log_filter = *:Info
# Port for listening to GDB connections.
use_gdbstub=false
gdbstub_port=24689
+
+[WebService]
+# Endpoint URL for submitting telemetry data
+telemetry_endpoint_url =
)";
}
diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp
index 47aadd60c..b0f808399 100644
--- a/src/citra/emu_window/emu_window_sdl2.cpp
+++ b/src/citra/emu_window/emu_window_sdl2.cpp
@@ -16,6 +16,7 @@
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
+#include "network/network.h"
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
@@ -58,6 +59,7 @@ void EmuWindow_SDL2::OnResize() {
EmuWindow_SDL2::EmuWindow_SDL2() {
InputCommon::Init();
+ Network::Init();
motion_emu = std::make_unique<Motion::MotionEmu>(*this);
@@ -116,6 +118,8 @@ EmuWindow_SDL2::~EmuWindow_SDL2() {
SDL_GL_DeleteContext(gl_context);
SDL_Quit();
motion_emu = nullptr;
+
+ Network::Shutdown();
InputCommon::Shutdown();
}
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 4841cbf05..f364b2284 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -1,4 +1,5 @@
set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
@@ -75,6 +76,8 @@ set(UIS
main.ui
)
+file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*)
+
create_directory_groups(${SRCS} ${HEADERS} ${UIS})
if (Qt5_FOUND)
@@ -86,12 +89,12 @@ endif()
if (APPLE)
set(MACOSX_ICON "../../dist/citra.icns")
set_source_files_properties(${MACOSX_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
- add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${MACOSX_ICON})
+ add_executable(citra-qt MACOSX_BUNDLE ${SRCS} ${HEADERS} ${UI_HDRS} ${THEMES} ${MACOSX_ICON})
set_target_properties(citra-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist)
else()
- add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})
+ add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS} ${THEMES})
endif()
-target_link_libraries(citra-qt PRIVATE audio_core common core input_common video_core)
+target_link_libraries(citra-qt PRIVATE audio_core common core input_common network video_core)
target_link_libraries(citra-qt PRIVATE Boost::boost glad nihstro-headers Qt5::OpenGL Qt5::Widgets)
target_link_libraries(citra-qt PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index a8a4aed8b..30554890f 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -17,6 +17,7 @@
#include "core/settings.h"
#include "input_common/keyboard.h"
#include "input_common/main.h"
+#include "network/network.h"
EmuThread::EmuThread(GRenderWindow* render_window)
: exec_step(false), running(false), stop_run(false), render_window(render_window) {}
@@ -110,10 +111,12 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
setWindowTitle(QString::fromStdString(window_title));
InputCommon::Init();
+ Network::Init();
}
GRenderWindow::~GRenderWindow() {
InputCommon::Shutdown();
+ Network::Shutdown();
}
void GRenderWindow::moveContext() {
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp
index 2b99447ec..75abb4ce6 100644
--- a/src/citra_qt/configuration/config.cpp
+++ b/src/citra_qt/configuration/config.cpp
@@ -70,9 +70,9 @@ void Config::ReadValues() {
Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool();
Settings::values.toggle_framelimit = qt_config->value("toggle_framelimit", true).toBool();
- Settings::values.bg_red = qt_config->value("bg_red", 1.0).toFloat();
- Settings::values.bg_green = qt_config->value("bg_green", 1.0).toFloat();
- Settings::values.bg_blue = qt_config->value("bg_blue", 1.0).toFloat();
+ Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat();
+ Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat();
+ Settings::values.bg_blue = qt_config->value("bg_blue", 0.0).toFloat();
qt_config->endGroup();
qt_config->beginGroup("Layout");
@@ -133,7 +133,15 @@ void Config::ReadValues() {
Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
qt_config->endGroup();
+ qt_config->beginGroup("WebService");
+ Settings::values.telemetry_endpoint_url =
+ qt_config->value("telemetry_endpoint_url", "https://services.citra-emu.org/api/telemetry")
+ .toString()
+ .toStdString();
+ qt_config->endGroup();
+
qt_config->beginGroup("UI");
+ UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
qt_config->beginGroup("UILayout");
UISettings::values.geometry = qt_config->value("geometry").toByteArray();
@@ -268,7 +276,13 @@ void Config::SaveValues() {
qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
qt_config->endGroup();
+ qt_config->beginGroup("WebService");
+ qt_config->setValue("telemetry_endpoint_url",
+ QString::fromStdString(Settings::values.telemetry_endpoint_url));
+ qt_config->endGroup();
+
qt_config->beginGroup("UI");
+ qt_config->setValue("theme", UISettings::values.theme);
qt_config->beginGroup("UILayout");
qt_config->setValue("geometry", UISettings::values.geometry);
diff --git a/src/citra_qt/configuration/configure_debug.ui b/src/citra_qt/configuration/configure_debug.ui
index bbbb0e3f4..96638ebdb 100644
--- a/src/citra_qt/configuration/configure_debug.ui
+++ b/src/citra_qt/configuration/configure_debug.ui
@@ -23,6 +23,13 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
+ <widget class="QLabel">
+ <property name="text">
+ <string>The GDB Stub only works correctly when the CPU JIT is off.</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QCheckBox" name="toggle_gdbstub">
diff --git a/src/citra_qt/configuration/configure_general.cpp b/src/citra_qt/configuration/configure_general.cpp
index a21176c34..939379717 100644
--- a/src/citra_qt/configuration/configure_general.cpp
+++ b/src/citra_qt/configuration/configure_general.cpp
@@ -12,6 +12,11 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
: QWidget(parent), ui(new Ui::ConfigureGeneral) {
ui->setupUi(this);
+
+ for (auto theme : UISettings::themes) {
+ ui->theme_combobox->addItem(theme.first, theme.second);
+ }
+
this->setConfiguration();
ui->toggle_cpu_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn());
@@ -26,11 +31,15 @@ void ConfigureGeneral::setConfiguration() {
// The first item is "auto-select" with actual value -1, so plus one here will do the trick
ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1);
+
+ ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
}
void ConfigureGeneral::applyConfiguration() {
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
+ UISettings::values.theme =
+ ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
Settings::values.region_value = ui->region_combobox->currentIndex() - 1;
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
Settings::Apply();
diff --git a/src/citra_qt/configuration/configure_general.ui b/src/citra_qt/configuration/configure_general.ui
index c739605a4..eedf2cbb0 100644
--- a/src/citra_qt/configuration/configure_general.ui
+++ b/src/citra_qt/configuration/configure_general.ui
@@ -132,6 +132,34 @@
</widget>
</item>
<item>
+ <widget class="QGroupBox" name="theme_group_box">
+ <property name="title">
+ <string>Theme</string>
+ </property>
+ <layout class="QHBoxLayout" name="theme_qhbox_layout">
+ <item>
+ <layout class="QVBoxLayout" name="theme_qvbox_layout">
+ <item>
+ <layout class="QHBoxLayout" name="theme_qhbox_layout_2">
+ <item>
+ <widget class="QLabel" name="theme_label">
+ <property name="text">
+ <string>Theme:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="theme_combobox">
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<widget class="QGroupBox" name="groupBox_3">
<property name="title">
<string>Hotkeys</string>
diff --git a/src/citra_qt/debugger/wait_tree.h b/src/citra_qt/debugger/wait_tree.h
index 06ef58ea7..2b38712b9 100644
--- a/src/citra_qt/debugger/wait_tree.h
+++ b/src/citra_qt/debugger/wait_tree.h
@@ -18,7 +18,6 @@ class WaitObject;
class Event;
class Mutex;
class Semaphore;
-class Session;
class Thread;
class Timer;
}
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 4f5b2ddab..02bfdca3d 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -71,6 +71,8 @@ GMainWindow::GMainWindow() : config(new Config()), emu_thread(nullptr) {
game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan);
+ UpdateUITheme();
+
QStringList args = QApplication::arguments();
if (args.length() >= 2) {
BootGame(args[1]);
@@ -606,6 +608,7 @@ void GMainWindow::OnConfigure() {
auto result = configureDialog.exec();
if (result == QDialog::Accepted) {
configureDialog.applyConfiguration();
+ UpdateUITheme();
config->Save();
}
}
@@ -791,6 +794,24 @@ void GMainWindow::filterBarSetChecked(bool state) {
emit(OnToggleFilterBar());
}
+void GMainWindow::UpdateUITheme() {
+ if (UISettings::values.theme != UISettings::themes[0].second) {
+ QString theme_uri(":" + UISettings::values.theme + "/style.qss");
+ QFile f(theme_uri);
+ if (!f.exists()) {
+ LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
+ } else {
+ f.open(QFile::ReadOnly | QFile::Text);
+ QTextStream ts(&f);
+ qApp->setStyleSheet(ts.readAll());
+ GMainWindow::setStyleSheet(ts.readAll());
+ }
+ } else {
+ qApp->setStyleSheet("");
+ GMainWindow::setStyleSheet("");
+ }
+}
+
#ifdef main
#undef main
#endif
diff --git a/src/citra_qt/main.h b/src/citra_qt/main.h
index 952a50974..360de2ced 100644
--- a/src/citra_qt/main.h
+++ b/src/citra_qt/main.h
@@ -42,6 +42,7 @@ class GMainWindow : public QMainWindow {
public:
void filterBarSetChecked(bool state);
+ void UpdateUITheme();
GMainWindow();
~GMainWindow();
diff --git a/src/citra_qt/ui_settings.h b/src/citra_qt/ui_settings.h
index bc37f81c5..025c73f84 100644
--- a/src/citra_qt/ui_settings.h
+++ b/src/citra_qt/ui_settings.h
@@ -4,6 +4,7 @@
#pragma once
+#include <array>
#include <vector>
#include <QByteArray>
#include <QString>
@@ -14,6 +15,10 @@ namespace UISettings {
using ContextualShortcut = std::pair<QString, int>;
using Shortcut = std::pair<QString, ContextualShortcut>;
+static const std::array<std::pair<QString, QString>, 2> themes = {
+ {std::make_pair(QString("Default"), QString("default")),
+ std::make_pair(QString("Dark"), QString("qdarkstyle"))}};
+
struct Values {
QByteArray geometry;
QByteArray state;
@@ -39,6 +44,8 @@ struct Values {
bool gamedir_deepscan;
QStringList recent_files;
+ QString theme;
+
// Shortcut name <Shortcut, context>
std::vector<Shortcut> shortcuts;
};
diff --git a/src/common/break_points.cpp b/src/common/break_points.cpp
index 03a19acba..fa367a4ca 100644
--- a/src/common/break_points.cpp
+++ b/src/common/break_points.cpp
@@ -5,7 +5,6 @@
#include <algorithm>
#include <sstream>
#include "common/break_points.h"
-#include "common/logging/log.h"
bool BreakPoints::IsAddressBreakPoint(u32 iAddress) const {
auto cond = [&iAddress](const TBreakPoint& bp) { return bp.iAddress == iAddress; };
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 42f6a9918..4b83eeb28 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -72,7 +72,9 @@ namespace Log {
SUB(Audio, DSP) \
SUB(Audio, Sink) \
CLS(Input) \
- CLS(Loader)
+ CLS(Network) \
+ CLS(Loader) \
+ CLS(WebService)
// GetClassName is a macro defined by Windows.h, grrr...
const char* GetLogClassName(Class log_class) {
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 1b905f66c..fe4dfed69 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -90,6 +90,8 @@ enum class Class : ClassType {
Audio_Sink, ///< Emulator audio output backend
Loader, ///< ROM loader
Input, ///< Input emulation
+ Network, ///< Network emulation
+ WebService, ///< Interface to Citra Web Services
Count ///< Total number of logging classes
};
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index 9d423766f..f71e748d1 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -6,7 +6,6 @@
#include <cstdio>
#ifdef _WIN32
-#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 6e602b0c5..360f407f3 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -60,6 +60,7 @@ set(SRCS
hle/kernel/timer.cpp
hle/kernel/vm_manager.cpp
hle/kernel/wait_object.cpp
+ hle/romfs.cpp
hle/service/ac/ac.cpp
hle/service/ac/ac_i.cpp
hle/service/ac/ac_u.cpp
@@ -144,6 +145,7 @@ set(SRCS
hle/service/nwm/nwm_tst.cpp
hle/service/nwm/nwm_uds.cpp
hle/service/nwm/uds_beacon.cpp
+ hle/service/nwm/uds_data.cpp
hle/service/pm_app.cpp
hle/service/ptm/ptm.cpp
hle/service/ptm/ptm_gets.cpp
@@ -257,6 +259,7 @@ set(HEADERS
hle/kernel/vm_manager.h
hle/kernel/wait_object.h
hle/result.h
+ hle/romfs.h
hle/service/ac/ac.h
hle/service/ac/ac_i.h
hle/service/ac/ac_u.h
@@ -341,6 +344,7 @@ set(HEADERS
hle/service/nwm/nwm_tst.h
hle/service/nwm/nwm_uds.h
hle/service/nwm/uds_beacon.h
+ hle/service/nwm/uds_data.h
hle/service/pm_app.h
hle/service/ptm/ptm.h
hle/service/ptm/ptm_gets.h
@@ -385,4 +389,7 @@ set(HEADERS
create_directory_groups(${SRCS} ${HEADERS})
add_library(core STATIC ${SRCS} ${HEADERS})
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
-target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic)
+target_link_libraries(core PUBLIC Boost::boost PRIVATE cryptopp dynarmic fmt)
+if (ENABLE_WEB_SERVICE)
+ target_link_libraries(core PUBLIC json-headers web_service)
+endif()
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 5429bcb26..d08f18623 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -168,6 +168,16 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
}
void System::Shutdown() {
+ // Log last frame performance stats
+ auto perf_results = GetAndResetPerfStats();
+ Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_EmulationSpeed",
+ perf_results.emulation_speed * 100.0);
+ Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Framerate",
+ perf_results.game_fps);
+ Telemetry().AddField(Telemetry::FieldType::Performance, "Shutdown_Frametime",
+ perf_results.frametime * 1000.0);
+
+ // Shutdown emulation session
GDBStub::Shutdown();
AudioCore::Shutdown();
VideoCore::Shutdown();
diff --git a/src/core/hle/function_wrappers.h b/src/core/hle/function_wrappers.h
index 18b6e7017..5e6002f4e 100644
--- a/src/core/hle/function_wrappers.h
+++ b/src/core/hle/function_wrappers.h
@@ -16,9 +16,6 @@ namespace HLE {
#define PARAM(n) Core::CPU().GetReg(n)
-/// An invalid result code that is meant to be overwritten when a thread resumes from waiting
-static const ResultCode RESULT_INVALID(0xDEADC0DE);
-
/**
* HLE a function return from the current ARM11 userland process
* @param res Result to return
@@ -68,10 +65,18 @@ void Wrap() {
(PARAM(3) != 0), (((s64)PARAM(4) << 32) | PARAM(0)))
.raw;
- if (retval != RESULT_INVALID.raw) {
- Core::CPU().SetReg(1, (u32)param_1);
- FuncReturn(retval);
- }
+ Core::CPU().SetReg(1, (u32)param_1);
+ FuncReturn(retval);
+}
+
+template <ResultCode func(s32*, u32*, s32, u32)>
+void Wrap() {
+ s32 param_1 = 0;
+ u32 retval =
+ func(&param_1, (Kernel::Handle*)Memory::GetPointer(PARAM(1)), (s32)PARAM(2), PARAM(3)).raw;
+
+ Core::CPU().SetReg(1, (u32)param_1);
+ FuncReturn(retval);
}
template <ResultCode func(u32, u32, u32, u32, s64)>
@@ -92,9 +97,7 @@ template <ResultCode func(u32, s64)>
void Wrap() {
s32 retval = func(PARAM(0), (((s64)PARAM(3) << 32) | PARAM(2))).raw;
- if (retval != RESULT_INVALID.raw) {
- FuncReturn(retval);
- }
+ FuncReturn(retval);
}
template <ResultCode func(MemoryInfo*, PageInfo*, u32)>
@@ -226,9 +229,18 @@ void Wrap() {
u32 retval = func(&param_1, &param_2,
reinterpret_cast<const char*>(Memory::GetPointer(PARAM(2))), PARAM(3))
.raw;
- // The first out parameter is moved into R2 and the second is moved into R1.
- Core::CPU().SetReg(1, param_2);
- Core::CPU().SetReg(2, param_1);
+ Core::CPU().SetReg(1, param_1);
+ Core::CPU().SetReg(2, param_2);
+ FuncReturn(retval);
+}
+
+template <ResultCode func(Kernel::Handle*, Kernel::Handle*)>
+void Wrap() {
+ Kernel::Handle param_1 = 0;
+ Kernel::Handle param_2 = 0;
+ u32 retval = func(&param_1, &param_2).raw;
+ Core::CPU().SetReg(1, param_1);
+ Core::CPU().SetReg(2, param_2);
FuncReturn(retval);
}
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index 303ca090d..f7f96125a 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -44,6 +44,9 @@ inline u32* GetStaticBuffers(const int offset = 0) {
namespace IPC {
+/// Size of the command buffer area, in 32-bit words.
+constexpr size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32);
+
// These errors are commonly returned by invalid IPC translations, so alias them here for
// convenience.
// TODO(yuriks): These will probably go away once translation is implemented inside the kernel.
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index d7348c09d..f0d89cffe 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -4,19 +4,28 @@
#pragma once
+#include <array>
+#include <tuple>
+#include <type_traits>
+#include <utility>
#include "core/hle/ipc.h"
#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
namespace IPC {
class RequestHelperBase {
protected:
+ Kernel::HLERequestContext* context = nullptr;
u32* cmdbuf;
ptrdiff_t index = 1;
Header header;
public:
+ RequestHelperBase(Kernel::HLERequestContext& context, Header desired_header)
+ : context(&context), cmdbuf(context.CommandBuffer()), header(desired_header) {}
+
RequestHelperBase(u32* command_buffer, Header command_header)
: cmdbuf(command_buffer), header(command_header) {}
@@ -51,12 +60,27 @@ public:
class RequestBuilder : public RequestHelperBase {
public:
+ RequestBuilder(Kernel::HLERequestContext& context, Header command_header)
+ : RequestHelperBase(context, command_header) {
+ // From this point we will start overwriting the existing command buffer, so it's safe to
+ // release all previous incoming Object pointers since they won't be usable anymore.
+ context.ClearIncomingObjects();
+ cmdbuf[0] = header.raw;
+ }
+
+ RequestBuilder(Kernel::HLERequestContext& context, u16 command_id, unsigned normal_params_size,
+ unsigned translate_params_size)
+ : RequestBuilder(
+ context, Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {}
+
RequestBuilder(u32* command_buffer, Header command_header)
: RequestHelperBase(command_buffer, command_header) {
cmdbuf[0] = header.raw;
}
+
explicit RequestBuilder(u32* command_buffer, u32 command_header)
: RequestBuilder(command_buffer, Header{command_header}) {}
+
RequestBuilder(u32* command_buffer, u16 command_id, unsigned normal_params_size,
unsigned translate_params_size)
: RequestBuilder(command_buffer,
@@ -88,6 +112,9 @@ public:
template <typename... H>
void PushMoveHandles(H... handles);
+ template <typename... O>
+ void PushObjects(Kernel::SharedPtr<O>... pointers);
+
void PushCurrentPIDHandle();
void PushStaticBuffer(VAddr buffer_vaddr, u32 size, u8 buffer_id);
@@ -153,6 +180,11 @@ inline void RequestBuilder::PushMoveHandles(H... handles) {
Push(static_cast<Kernel::Handle>(handles)...);
}
+template <typename... O>
+inline void RequestBuilder::PushObjects(Kernel::SharedPtr<O>... pointers) {
+ PushMoveHandles(context->AddOutgoingHandle(std::move(pointers))...);
+}
+
inline void RequestBuilder::PushCurrentPIDHandle() {
Push(CallingPidDesc());
Push(u32(0));
@@ -171,10 +203,21 @@ inline void RequestBuilder::PushMappedBuffer(VAddr buffer_vaddr, u32 size,
class RequestParser : public RequestHelperBase {
public:
+ RequestParser(Kernel::HLERequestContext& context, Header desired_header)
+ : RequestHelperBase(context, desired_header) {}
+
+ RequestParser(Kernel::HLERequestContext& context, u16 command_id, unsigned normal_params_size,
+ unsigned translate_params_size)
+ : RequestParser(context,
+ Header{MakeHeader(command_id, normal_params_size, translate_params_size)}) {
+ }
+
RequestParser(u32* command_buffer, Header command_header)
: RequestHelperBase(command_buffer, command_header) {}
+
explicit RequestParser(u32* command_buffer, u32 command_header)
: RequestParser(command_buffer, Header{command_header}) {}
+
RequestParser(u32* command_buffer, u16 command_id, unsigned normal_params_size,
unsigned translate_params_size)
: RequestParser(command_buffer,
@@ -186,7 +229,10 @@ public:
ValidateHeader();
Header builderHeader{
MakeHeader(header.command_id, normal_params_size, translate_params_size)};
- return {cmdbuf, builderHeader};
+ if (context != nullptr)
+ return {*context, builderHeader};
+ else
+ return {cmdbuf, builderHeader};
}
template <typename T>
@@ -198,10 +244,52 @@ public:
template <typename First, typename... Other>
void Pop(First& first_value, Other&... other_values);
+ /// Equivalent to calling `PopHandles<1>()[0]`.
Kernel::Handle PopHandle();
+ /**
+ * Pops a descriptor containing `N` handles. The handles are returned as an array. The
+ * descriptor must contain exactly `N` handles, it is not permitted to, for example, call
+ * PopHandles<1>() twice to read a multi-handle descriptor with 2 handles, or to make a single
+ * PopHandles<2>() call to read 2 single-handle descriptors.
+ */
+ template <unsigned int N>
+ std::array<Kernel::Handle, N> PopHandles();
+
+ /// Convenience wrapper around PopHandles() which assigns the handles to the passed references.
template <typename... H>
- void PopHandles(H&... handles);
+ void PopHandles(H&... handles) {
+ std::tie(handles...) = PopHandles<sizeof...(H)>();
+ }
+
+ /// Equivalent to calling `PopGenericObjects<1>()[0]`.
+ Kernel::SharedPtr<Kernel::Object> PopGenericObject();
+
+ /// Equivalent to calling `std::get<0>(PopObjects<T>())`.
+ template <typename T>
+ Kernel::SharedPtr<T> PopObject();
+
+ /**
+ * Pop a descriptor containing `N` handles and resolves them to Kernel::Object pointers. If a
+ * handle is invalid, null is returned for that object instead. The same caveats from
+ * PopHandles() apply regarding `N` matching the number of handles in the descriptor.
+ */
+ template <unsigned int N>
+ std::array<Kernel::SharedPtr<Kernel::Object>, N> PopGenericObjects();
+
+ /**
+ * Resolves handles to Kernel::Objects as in PopGenericsObjects(), but then also casts them to
+ * the passed `T` types, while verifying that the cast is valid. If the type of an object does
+ * not match, null is returned instead.
+ */
+ template <typename... T>
+ std::tuple<Kernel::SharedPtr<T>...> PopObjects();
+
+ /// Convenience wrapper around PopObjects() which assigns the handles to the passed references.
+ template <typename... T>
+ void PopObjects(Kernel::SharedPtr<T>&... pointers) {
+ std::tie(pointers...) = PopObjects<T...>();
+ }
/**
* @brief Pops the static buffer vaddr
@@ -313,15 +401,54 @@ inline Kernel::Handle RequestParser::PopHandle() {
return Pop<Kernel::Handle>();
}
-template <typename... H>
-void RequestParser::PopHandles(H&... handles) {
- const u32 handle_descriptor = Pop<u32>();
- const int handles_number = sizeof...(H);
- DEBUG_ASSERT_MSG(IsHandleDescriptor(handle_descriptor),
- "Tried to pop handle(s) but the descriptor is not a handle descriptor");
- DEBUG_ASSERT_MSG(handles_number == HandleNumberFromDesc(handle_descriptor),
- "Number of handles doesn't match the descriptor");
- Pop(static_cast<Kernel::Handle&>(handles)...);
+template <unsigned int N>
+std::array<Kernel::Handle, N> RequestParser::PopHandles() {
+ u32 handle_descriptor = Pop<u32>();
+ ASSERT_MSG(IsHandleDescriptor(handle_descriptor),
+ "Tried to pop handle(s) but the descriptor is not a handle descriptor");
+ ASSERT_MSG(N == HandleNumberFromDesc(handle_descriptor),
+ "Number of handles doesn't match the descriptor");
+
+ std::array<Kernel::Handle, N> handles{};
+ for (Kernel::Handle& handle : handles) {
+ handle = Pop<Kernel::Handle>();
+ }
+ return handles;
+}
+
+inline Kernel::SharedPtr<Kernel::Object> RequestParser::PopGenericObject() {
+ Kernel::Handle handle = PopHandle();
+ return context->GetIncomingHandle(handle);
+}
+
+template <typename T>
+Kernel::SharedPtr<T> RequestParser::PopObject() {
+ return Kernel::DynamicObjectCast<T>(PopGenericObject());
+}
+
+template <unsigned int N>
+inline std::array<Kernel::SharedPtr<Kernel::Object>, N> RequestParser::PopGenericObjects() {
+ std::array<Kernel::Handle, N> handles = PopHandles<N>();
+ std::array<Kernel::SharedPtr<Kernel::Object>, N> pointers;
+ for (int i = 0; i < N; ++i) {
+ pointers[i] = context->GetIncomingHandle(handles[i]);
+ }
+ return pointers;
+}
+
+namespace detail {
+template <typename... T, size_t... I>
+std::tuple<Kernel::SharedPtr<T>...> PopObjectsHelper(
+ std::array<Kernel::SharedPtr<Kernel::Object>, sizeof...(T)>&& pointers,
+ std::index_sequence<I...>) {
+ return std::make_tuple(Kernel::DynamicObjectCast<T>(std::move(pointers[I]))...);
+}
+} // namespace detail
+
+template <typename... T>
+inline std::tuple<Kernel::SharedPtr<T>...> RequestParser::PopObjects() {
+ return detail::PopObjectsHelper<T...>(PopGenericObjects<sizeof...(T)>(),
+ std::index_sequence_for<T...>{});
}
inline VAddr RequestParser::PopStaticBuffer(size_t* data_size, bool useStaticBuffersToGetVaddr) {
diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index 6bc49ff64..646a5cc64 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -8,6 +8,8 @@
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/server_session.h"
+#include "core/hle/kernel/session.h"
+#include "core/hle/kernel/thread.h"
namespace Kernel {
@@ -16,23 +18,34 @@ ClientSession::~ClientSession() {
// This destructor will be called automatically when the last ClientSession handle is closed by
// the emulated application.
- if (parent->server) {
- if (parent->server->hle_handler)
- parent->server->hle_handler->ClientDisconnected(parent->server);
+ // Local references to ServerSession and SessionRequestHandler are necessary to guarantee they
+ // will be kept alive until after ClientDisconnected() returns.
+ SharedPtr<ServerSession> server = parent->server;
+ if (server) {
+ std::shared_ptr<SessionRequestHandler> hle_handler = server->hle_handler;
+ if (hle_handler)
+ hle_handler->ClientDisconnected(server);
// TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set
// their WaitSynchronization result to 0xC920181A.
+
+ // Clean up the list of client threads with pending requests, they are unneeded now that the
+ // client endpoint is closed.
+ server->pending_requesting_threads.clear();
+ server->currently_handling = nullptr;
}
parent->client = nullptr;
}
-ResultCode ClientSession::SendSyncRequest() {
- // Signal the server session that new data is available
- if (parent->server)
- return parent->server->HandleSyncRequest();
+ResultCode ClientSession::SendSyncRequest(SharedPtr<Thread> thread) {
+ // Keep ServerSession alive until we're done working with it.
+ SharedPtr<ServerSession> server = parent->server;
+ if (server == nullptr)
+ return ERR_SESSION_CLOSED_BY_REMOTE;
- return ERR_SESSION_CLOSED_BY_REMOTE;
+ // Signal the server session that new data is available
+ return server->HandleSyncRequest(std::move(thread));
}
} // namespace
diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h
index 2de379c09..daf521529 100644
--- a/src/core/hle/kernel/client_session.h
+++ b/src/core/hle/kernel/client_session.h
@@ -14,6 +14,7 @@ namespace Kernel {
class ServerSession;
class Session;
+class Thread;
class ClientSession final : public Object {
public:
@@ -34,9 +35,10 @@ public:
/**
* Sends an SyncRequest from the current emulated thread.
+ * @param thread Thread that initiated the request.
* @return ResultCode of the operation.
*/
- ResultCode SendSyncRequest();
+ ResultCode SendSyncRequest(SharedPtr<Thread> thread);
std::string name; ///< Name of client port (optional)
diff --git a/src/core/hle/kernel/errors.h b/src/core/hle/kernel/errors.h
index b3b60e7df..64aa61460 100644
--- a/src/core/hle/kernel/errors.h
+++ b/src/core/hle/kernel/errors.h
@@ -13,6 +13,7 @@ enum {
OutOfHandles = 19,
SessionClosedByRemote = 26,
PortNameTooLong = 30,
+ NoPendingSessions = 35,
WrongPermission = 46,
InvalidBufferDescriptor = 48,
MaxConnectionsReached = 52,
@@ -94,5 +95,9 @@ constexpr ResultCode ERR_OUT_OF_RANGE_KERNEL(ErrorDescription::OutOfRange, Error
ErrorLevel::Permanent); // 0xD8E007FD
constexpr ResultCode RESULT_TIMEOUT(ErrorDescription::Timeout, ErrorModule::OS,
ErrorSummary::StatusChanged, ErrorLevel::Info);
+/// Returned when Accept() is called on a port with no sessions to be accepted.
+constexpr ResultCode ERR_NO_PENDING_SESSIONS(ErrCodes::NoPendingSessions, ErrorModule::OS,
+ ErrorSummary::WouldBlock,
+ ErrorLevel::Permanent); // 0xD8401823
} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 0922b3f47..5ebe2eca4 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -5,8 +5,10 @@
#include <boost/range/algorithm_ext/erase.hpp>
#include "common/assert.h"
#include "common/common_types.h"
+#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/kernel/server_session.h"
namespace Kernel {
@@ -21,4 +23,113 @@ void SessionRequestHandler::ClientDisconnected(SharedPtr<ServerSession> server_s
boost::range::remove_erase(connected_sessions, server_session);
}
+HLERequestContext::HLERequestContext(SharedPtr<ServerSession> session)
+ : session(std::move(session)) {
+ cmd_buf[0] = 0;
+}
+
+HLERequestContext::~HLERequestContext() = default;
+
+SharedPtr<Object> HLERequestContext::GetIncomingHandle(u32 id_from_cmdbuf) const {
+ ASSERT(id_from_cmdbuf < request_handles.size());
+ return request_handles[id_from_cmdbuf];
+}
+
+u32 HLERequestContext::AddOutgoingHandle(SharedPtr<Object> object) {
+ request_handles.push_back(std::move(object));
+ return request_handles.size() - 1;
+}
+
+void HLERequestContext::ClearIncomingObjects() {
+ request_handles.clear();
+}
+
+ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf,
+ Process& src_process,
+ HandleTable& src_table) {
+ IPC::Header header{src_cmdbuf[0]};
+
+ size_t untranslated_size = 1u + header.normal_params_size;
+ size_t command_size = untranslated_size + header.translate_params_size;
+ ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH); // TODO(yuriks): Return error
+
+ std::copy_n(src_cmdbuf, untranslated_size, cmd_buf.begin());
+
+ size_t i = untranslated_size;
+ while (i < command_size) {
+ u32 descriptor = cmd_buf[i] = src_cmdbuf[i];
+ i += 1;
+
+ switch (IPC::GetDescriptorType(descriptor)) {
+ case IPC::DescriptorType::CopyHandle:
+ case IPC::DescriptorType::MoveHandle: {
+ u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
+ ASSERT(i + num_handles <= command_size); // TODO(yuriks): Return error
+ for (u32 j = 0; j < num_handles; ++j) {
+ Handle handle = src_cmdbuf[i];
+ SharedPtr<Object> object = nullptr;
+ if (handle != 0) {
+ object = src_table.GetGeneric(handle);
+ ASSERT(object != nullptr); // TODO(yuriks): Return error
+ if (descriptor == IPC::DescriptorType::MoveHandle) {
+ src_table.Close(handle);
+ }
+ }
+
+ cmd_buf[i++] = AddOutgoingHandle(std::move(object));
+ }
+ break;
+ }
+ case IPC::DescriptorType::CallingPid: {
+ cmd_buf[i++] = src_process.process_id;
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unsupported handle translation: 0x%08X", descriptor);
+ }
+ }
+
+ return RESULT_SUCCESS;
+}
+
+ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process,
+ HandleTable& dst_table) const {
+ IPC::Header header{cmd_buf[0]};
+
+ size_t untranslated_size = 1u + header.normal_params_size;
+ size_t command_size = untranslated_size + header.translate_params_size;
+ ASSERT(command_size <= IPC::COMMAND_BUFFER_LENGTH);
+
+ std::copy_n(cmd_buf.begin(), untranslated_size, dst_cmdbuf);
+
+ size_t i = untranslated_size;
+ while (i < command_size) {
+ u32 descriptor = dst_cmdbuf[i] = cmd_buf[i];
+ i += 1;
+
+ switch (IPC::GetDescriptorType(descriptor)) {
+ case IPC::DescriptorType::CopyHandle:
+ case IPC::DescriptorType::MoveHandle: {
+ // HLE services don't use handles, so we treat both CopyHandle and MoveHandle equally
+ u32 num_handles = IPC::HandleNumberFromDesc(descriptor);
+ ASSERT(i + num_handles <= command_size);
+ for (u32 j = 0; j < num_handles; ++j) {
+ SharedPtr<Object> object = GetIncomingHandle(cmd_buf[i]);
+ Handle handle = 0;
+ if (object != nullptr) {
+ // TODO(yuriks): Figure out the proper error handling for if this fails
+ handle = dst_table.Create(object).Unwrap();
+ }
+ dst_cmdbuf[i++] = handle;
+ }
+ break;
+ }
+ default:
+ UNIMPLEMENTED_MSG("Unsupported handle translation: 0x%08X", descriptor);
+ }
+ }
+
+ return RESULT_SUCCESS;
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h
index 14f682f44..35795fc1d 100644
--- a/src/core/hle/kernel/hle_ipc.h
+++ b/src/core/hle/kernel/hle_ipc.h
@@ -4,13 +4,24 @@
#pragma once
+#include <array>
#include <memory>
#include <vector>
+#include <boost/container/small_vector.hpp>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/ipc.h"
#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/server_session.h"
+
+namespace Service {
+class ServiceFrameworkBase;
+}
namespace Kernel {
-class ServerSession;
+class HandleTable;
+class Process;
/**
* Interface implemented by HLE Session handlers.
@@ -19,6 +30,8 @@ class ServerSession;
*/
class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> {
public:
+ virtual ~SessionRequestHandler() = default;
+
/**
* Handles a sync request from the emulated application.
* @param server_session The ServerSession that was triggered for this sync request,
@@ -27,27 +40,97 @@ public:
* this request (ServerSession, Originator thread, Translated command buffer, etc).
* @returns ResultCode the result code of the translate operation.
*/
- virtual void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) = 0;
+ virtual void HandleSyncRequest(SharedPtr<ServerSession> server_session) = 0;
/**
* Signals that a client has just connected to this HLE handler and keeps the
* associated ServerSession alive for the duration of the connection.
* @param server_session Owning pointer to the ServerSession associated with the connection.
*/
- void ClientConnected(Kernel::SharedPtr<Kernel::ServerSession> server_session);
+ void ClientConnected(SharedPtr<ServerSession> server_session);
/**
* Signals that a client has just disconnected from this HLE handler and releases the
* associated ServerSession.
* @param server_session ServerSession associated with the connection.
*/
- void ClientDisconnected(Kernel::SharedPtr<Kernel::ServerSession> server_session);
+ void ClientDisconnected(SharedPtr<ServerSession> server_session);
protected:
/// List of sessions that are connected to this handler.
/// A ServerSession whose server endpoint is an HLE implementation is kept alive by this list
// for the duration of the connection.
- std::vector<Kernel::SharedPtr<Kernel::ServerSession>> connected_sessions;
+ std::vector<SharedPtr<ServerSession>> connected_sessions;
+};
+
+/**
+ * Class containing information about an in-flight IPC request being handled by an HLE service
+ * implementation. Services should avoid using old global APIs (e.g. Kernel::GetCommandBuffer()) and
+ * when possible use the APIs in this class to service the request.
+ *
+ * HLE handle protocol
+ * ===================
+ *
+ * To avoid needing HLE services to keep a separate handle table, or having to directly modify the
+ * requester's table, a tweaked protocol is used to receive and send handles in requests. The kernel
+ * will decode the incoming handles into object pointers and insert a id in the buffer where the
+ * handle would normally be. The service then calls GetIncomingHandle() with that id to get the
+ * pointer to the object. Similarly, instead of inserting a handle into the command buffer, the
+ * service calls AddOutgoingHandle() and stores the returned id where the handle would normally go.
+ *
+ * The end result is similar to just giving services their own real handle tables, but since these
+ * ids are local to a specific context, it avoids requiring services to manage handles for objects
+ * across multiple calls and ensuring that unneeded handles are cleaned up.
+ */
+class HLERequestContext {
+public:
+ HLERequestContext(SharedPtr<ServerSession> session);
+ ~HLERequestContext();
+
+ /// Returns a pointer to the IPC command buffer for this request.
+ u32* CommandBuffer() {
+ return cmd_buf.data();
+ }
+
+ /**
+ * Returns the session through which this request was made. This can be used as a map key to
+ * access per-client data on services.
+ */
+ SharedPtr<ServerSession> Session() const {
+ return session;
+ }
+
+ /**
+ * Resolves a object id from the request command buffer into a pointer to an object. See the
+ * "HLE handle protocol" section in the class documentation for more details.
+ */
+ SharedPtr<Object> GetIncomingHandle(u32 id_from_cmdbuf) const;
+
+ /**
+ * Adds an outgoing object to the response, returning the id which should be used to reference
+ * it. See the "HLE handle protocol" section in the class documentation for more details.
+ */
+ u32 AddOutgoingHandle(SharedPtr<Object> object);
+
+ /**
+ * Discards all Objects from the context, invalidating all ids. This may be called after reading
+ * out all incoming objects, so that the buffer memory can be re-used for outgoing handles, but
+ * this is not required.
+ */
+ void ClearIncomingObjects();
+
+ /// Populates this context with data from the requesting process/thread.
+ ResultCode PopulateFromIncomingCommandBuffer(const u32_le* src_cmdbuf, Process& src_process,
+ HandleTable& src_table);
+ /// Writes data from this context back to the requesting process/thread.
+ ResultCode WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, Process& dst_process,
+ HandleTable& dst_table) const;
+
+private:
+ std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf;
+ SharedPtr<ServerSession> session;
+ // TODO(yuriks): Check common usage of this and optimize size accordingly
+ boost::container::small_vector<SharedPtr<Object>, 8> request_handles;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp
index 804f23b1c..496d07cb5 100644
--- a/src/core/hle/kernel/memory.cpp
+++ b/src/core/hle/kernel/memory.cpp
@@ -166,7 +166,7 @@ void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mappin
auto vma = address_space
.MapBackingMemory(mapping.address, target_pointer + offset_into_region,
mapping.size, memory_state)
- .MoveFrom();
+ .Unwrap();
address_space.Reprotect(vma,
mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite);
}
@@ -176,14 +176,14 @@ void MapSharedPages(VMManager& address_space) {
.MapBackingMemory(Memory::CONFIG_MEMORY_VADDR,
reinterpret_cast<u8*>(&ConfigMem::config_mem),
Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared)
- .MoveFrom();
+ .Unwrap();
address_space.Reprotect(cfg_mem_vma, VMAPermission::Read);
auto shared_page_vma = address_space
.MapBackingMemory(Memory::SHARED_PAGE_VADDR,
reinterpret_cast<u8*>(&SharedPage::shared_page),
Memory::SHARED_PAGE_SIZE, MemoryState::Shared)
- .MoveFrom();
+ .Unwrap();
address_space.Reprotect(shared_page_vma, VMAPermission::Read);
}
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 1c31ec950..522ad2333 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -151,6 +151,8 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) {
}
VAddr Process::GetLinearHeapAreaAddress() const {
+ // Starting from system version 8.0.0 a new linear heap layout is supported to allow usage of
+ // the extra RAM in the n3DS.
return kernel_version < 0x22C ? Memory::LINEAR_HEAP_VADDR : Memory::NEW_LINEAR_HEAP_VADDR;
}
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index 4d20c39a1..49a9cdfa3 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -5,8 +5,10 @@
#include <tuple>
#include "common/assert.h"
#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/server_port.h"
+#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
@@ -14,6 +16,16 @@ namespace Kernel {
ServerPort::ServerPort() {}
ServerPort::~ServerPort() {}
+ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
+ if (pending_sessions.empty()) {
+ return ERR_NO_PENDING_SESSIONS;
+ }
+
+ auto session = std::move(pending_sessions.back());
+ pending_sessions.pop_back();
+ return MakeResult(std::move(session));
+}
+
bool ServerPort::ShouldWait(Thread* thread) const {
// If there are no pending sessions, we wait until a new one is added.
return pending_sessions.size() == 0;
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index f1419cd46..6fe7c7f2f 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -14,6 +14,7 @@
namespace Kernel {
class ClientPort;
+class ServerSession;
class SessionRequestHandler;
class ServerPort final : public WaitObject {
@@ -41,6 +42,12 @@ public:
}
/**
+ * Accepts a pending incoming connection on this port. If there are no pending sessions, will
+ * return ERR_NO_PENDING_SESSIONS.
+ */
+ ResultVal<SharedPtr<ServerSession>> Accept();
+
+ /**
* Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
* will inherit a reference to this handler.
*/
@@ -50,8 +57,8 @@ public:
std::string name; ///< Name of port (optional)
- std::vector<SharedPtr<WaitObject>>
- pending_sessions; ///< ServerSessions waiting to be accepted by the port
+ /// ServerSessions waiting to be accepted by the port
+ std::vector<SharedPtr<ServerSession>> pending_sessions;
/// This session's HLE request handler template (optional)
/// ServerSessions created from this port inherit a reference to this handler.
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 2dc709bc9..337896abf 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -32,22 +32,29 @@ ResultVal<SharedPtr<ServerSession>> ServerSession::Create(std::string name) {
SharedPtr<ServerSession> server_session(new ServerSession);
server_session->name = std::move(name);
- server_session->signaled = false;
server_session->parent = nullptr;
return MakeResult(std::move(server_session));
}
bool ServerSession::ShouldWait(Thread* thread) const {
- return !signaled;
+ // Closed sessions should never wait, an error will be returned from svcReplyAndReceive.
+ if (parent->client == nullptr)
+ return false;
+ // Wait if we have no pending requests, or if we're currently handling a request.
+ return pending_requesting_threads.empty() || currently_handling != nullptr;
}
void ServerSession::Acquire(Thread* thread) {
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
- signaled = false;
+ // We are now handling a request, pop it from the stack.
+ // TODO(Subv): What happens if the client endpoint is closed before any requests are made?
+ ASSERT(!pending_requesting_threads.empty());
+ currently_handling = pending_requesting_threads.back();
+ pending_requesting_threads.pop_back();
}
-ResultCode ServerSession::HandleSyncRequest() {
+ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) {
// The ServerSession received a sync request, this means that there's new data available
// from its ClientSession, so wake up any threads that may be waiting on a svcReplyAndReceive or
// similar.
@@ -60,18 +67,21 @@ ResultCode ServerSession::HandleSyncRequest() {
return result;
hle_handler->HandleSyncRequest(SharedPtr<ServerSession>(this));
// TODO(Subv): Translate the response command buffer.
+ } else {
+ // Add the thread to the list of threads that have issued a sync request with this
+ // server.
+ pending_requesting_threads.push_back(std::move(thread));
}
// If this ServerSession does not have an HLE implementation, just wake up the threads waiting
// on it.
- signaled = true;
WakeupAllWaitingThreads();
return RESULT_SUCCESS;
}
ServerSession::SessionPair ServerSession::CreateSessionPair(const std::string& name,
SharedPtr<ClientPort> port) {
- auto server_session = ServerSession::Create(name + "_Server").MoveFrom();
+ auto server_session = ServerSession::Create(name + "_Server").Unwrap();
SharedPtr<ClientSession> client_session(new ClientSession);
client_session->name = name + "_Client";
@@ -90,4 +100,4 @@ ResultCode TranslateHLERequest(ServerSession* server_session) {
// TODO(Subv): Implement this function once multiple concurrent processes are supported.
return RESULT_SUCCESS;
}
-}
+} // namespace Kernel
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index 28f365b9e..f4360ddf3 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -9,7 +9,6 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/session.h"
#include "core/hle/kernel/wait_object.h"
#include "core/hle/result.h"
#include "core/memory.h"
@@ -19,6 +18,7 @@ namespace Kernel {
class ClientSession;
class ClientPort;
class ServerSession;
+class Session;
class SessionRequestHandler;
class Thread;
@@ -67,20 +67,30 @@ public:
/**
* Handle a sync request from the emulated application.
+ * @param thread Thread that initiated the request.
* @returns ResultCode from the operation.
*/
- ResultCode HandleSyncRequest();
+ ResultCode HandleSyncRequest(SharedPtr<Thread> thread);
bool ShouldWait(Thread* thread) const override;
void Acquire(Thread* thread) override;
std::string name; ///< The name of this session (optional)
- bool signaled; ///< Whether there's new data available to this ServerSession
std::shared_ptr<Session> parent; ///< The parent session, which links to the client endpoint.
std::shared_ptr<SessionRequestHandler>
hle_handler; ///< This session's HLE request handler (optional)
+ /// List of threads that are pending a response after a sync request. This list is processed in
+ /// a LIFO manner, thus, the last request will be dispatched first.
+ /// TODO(Subv): Verify if this is indeed processed in LIFO using a hardware test.
+ std::vector<SharedPtr<Thread>> pending_requesting_threads;
+
+ /// Thread whose request is currently being handled. A request is considered "handled" when a
+ /// response is sent via svcReplyAndReceive.
+ /// TODO(Subv): Find a better name for this.
+ SharedPtr<Thread> currently_handling;
+
private:
ServerSession();
~ServerSession() override;
diff --git a/src/core/hle/kernel/shared_memory.cpp b/src/core/hle/kernel/shared_memory.cpp
index 922e5ab58..a7b66142f 100644
--- a/src/core/hle/kernel/shared_memory.cpp
+++ b/src/core/hle/kernel/shared_memory.cpp
@@ -149,7 +149,7 @@ ResultCode SharedMemory::Map(Process* target_process, VAddr address, MemoryPermi
if (base_address == 0 && target_address == 0) {
// Calculate the address at which to map the memory block.
- target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address);
+ target_address = Memory::PhysicalToVirtualAddress(linear_heap_phys_address).value();
}
// Map the memory block into the target process
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 75ce626f8..f5f2eb2f7 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -389,7 +389,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
thread->wait_objects.clear();
thread->wait_address = 0;
thread->name = std::move(name);
- thread->callback_handle = wakeup_callback_handle_table.Create(thread).MoveFrom();
+ thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap();
thread->owner_process = g_current_process;
// Find the next available TLS index, and mark it as used
@@ -484,7 +484,7 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) {
auto thread_res = Thread::Create("main", entry_point, priority, 0, THREADPROCESSORID_0,
Memory::HEAP_VADDR_END);
- SharedPtr<Thread> thread = thread_res.MoveFrom();
+ SharedPtr<Thread> thread = std::move(thread_res).Unwrap();
thread->context.fpscr =
FPSCR_DEFAULT_NAN | FPSCR_FLUSH_TO_ZERO | FPSCR_ROUND_TOZERO | FPSCR_IXC; // 0x03C00010
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index 6f2cf3b02..d7ec93672 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -30,7 +30,7 @@ SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) {
timer->name = std::move(name);
timer->initial_delay = 0;
timer->interval_delay = 0;
- timer->callback_handle = timer_callback_handle_table.Create(timer).MoveFrom();
+ timer->callback_handle = timer_callback_handle_table.Create(timer).Unwrap();
return timer;
}
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 5f2cdbb96..47b6e2b23 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -388,13 +388,14 @@ public:
}
/// Asserts that the result succeeded and returns a reference to it.
- T& Unwrap() {
+ T& Unwrap() & {
ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
return **this;
}
- T&& MoveFrom() {
- return std::move(Unwrap());
+ T&& Unwrap() && {
+ ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal");
+ return std::move(**this);
}
private:
diff --git a/src/core/hle/romfs.cpp b/src/core/hle/romfs.cpp
new file mode 100644
index 000000000..3157df71d
--- /dev/null
+++ b/src/core/hle/romfs.cpp
@@ -0,0 +1,102 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include "common/swap.h"
+#include "core/hle/romfs.h"
+
+namespace RomFS {
+
+struct Header {
+ u32_le header_length;
+ u32_le dir_hash_table_offset;
+ u32_le dir_hash_table_length;
+ u32_le dir_table_offset;
+ u32_le dir_table_length;
+ u32_le file_hash_table_offset;
+ u32_le file_hash_table_length;
+ u32_le file_table_offset;
+ u32_le file_table_length;
+ u32_le data_offset;
+};
+
+static_assert(sizeof(Header) == 0x28, "Header has incorrect size");
+
+struct DirectoryMetadata {
+ u32_le parent_dir_offset;
+ u32_le next_dir_offset;
+ u32_le first_child_dir_offset;
+ u32_le first_file_offset;
+ u32_le same_hash_next_dir_offset;
+ u32_le name_length; // in bytes
+ // followed by directory name
+};
+
+static_assert(sizeof(DirectoryMetadata) == 0x18, "DirectoryMetadata has incorrect size");
+
+struct FileMetadata {
+ u32_le parent_dir_offset;
+ u32_le next_file_offset;
+ u64_le data_offset;
+ u64_le data_length;
+ u32_le same_hash_next_file_offset;
+ u32_le name_length; // in bytes
+ // followed by file name
+};
+
+static_assert(sizeof(FileMetadata) == 0x20, "FileMetadata has incorrect size");
+
+static bool MatchName(const u8* buffer, u32 name_length, const std::u16string& name) {
+ std::vector<char16_t> name_buffer(name_length / sizeof(char16_t));
+ std::memcpy(name_buffer.data(), buffer, name_length);
+ return name == std::u16string(name_buffer.begin(), name_buffer.end());
+}
+
+const u8* GetFilePointer(const u8* romfs, const std::vector<std::u16string>& path) {
+ constexpr u32 INVALID_FIELD = 0xFFFFFFFF;
+
+ // Split path into directory names and file name
+ std::vector<std::u16string> dir_names = path;
+ dir_names.pop_back();
+ const std::u16string& file_name = path.back();
+
+ Header header;
+ std::memcpy(&header, romfs, sizeof(header));
+
+ // Find directories of each level
+ DirectoryMetadata dir;
+ const u8* current_dir = romfs + header.dir_table_offset;
+ std::memcpy(&dir, current_dir, sizeof(dir));
+ for (const std::u16string& dir_name : dir_names) {
+ u32 child_dir_offset;
+ child_dir_offset = dir.first_child_dir_offset;
+ while (true) {
+ if (child_dir_offset == INVALID_FIELD) {
+ return nullptr;
+ }
+ const u8* current_child_dir = romfs + header.dir_table_offset + child_dir_offset;
+ std::memcpy(&dir, current_child_dir, sizeof(dir));
+ if (MatchName(current_child_dir + sizeof(dir), dir.name_length, dir_name)) {
+ current_dir = current_child_dir;
+ break;
+ }
+ child_dir_offset = dir.next_dir_offset;
+ }
+ }
+
+ // Find the file
+ FileMetadata file;
+ u32 file_offset = dir.first_file_offset;
+ while (file_offset != INVALID_FIELD) {
+ const u8* current_file = romfs + header.file_table_offset + file_offset;
+ std::memcpy(&file, current_file, sizeof(file));
+ if (MatchName(current_file + sizeof(file), file.name_length, file_name)) {
+ return romfs + header.data_offset + file.data_offset;
+ }
+ file_offset = file.next_file_offset;
+ }
+ return nullptr;
+}
+
+} // namespace RomFS
diff --git a/src/core/hle/romfs.h b/src/core/hle/romfs.h
new file mode 100644
index 000000000..ee9f29760
--- /dev/null
+++ b/src/core/hle/romfs.h
@@ -0,0 +1,22 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include <vector>
+#include "common/common_types.h"
+
+namespace RomFS {
+
+/**
+ * Gets the pointer to a file in a RomFS image.
+ * @param romfs The pointer to the RomFS image
+ * @param path A vector containing the directory names and file name of the path to the file
+ * @return the pointer to the file
+ * @todo reimplement this with a full RomFS manager
+ */
+const u8* GetFilePointer(const u8* romfs, const std::vector<std::u16string>& path);
+
+} // namespace RomFS
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index 4c587e3c8..5c44b43bb 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -6,11 +6,13 @@
#include "common/file_util.h"
#include "common/logging/log.h"
#include "core/core.h"
+#include "core/file_sys/file_backend.h"
#include "core/hle/applets/applet.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/shared_memory.h"
+#include "core/hle/romfs.h"
#include "core/hle/service/apt/apt.h"
#include "core/hle/service/apt/apt_a.h"
#include "core/hle/service/apt/apt_s.h"
@@ -27,6 +29,7 @@ namespace APT {
/// Handle to shared memory region designated to for shared system font
static Kernel::SharedPtr<Kernel::SharedMemory> shared_font_mem;
+static bool shared_font_loaded = false;
static bool shared_font_relocated = false;
static Kernel::SharedPtr<Kernel::Mutex> lock;
@@ -55,8 +58,8 @@ void Initialize(Service::Interface* self) {
u32 flags = rp.Pop<u32>();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 3);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).MoveFrom(),
- Kernel::g_handle_table.Create(parameter_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).Unwrap(),
+ Kernel::g_handle_table.Create(parameter_event).Unwrap());
// TODO(bunnei): Check if these events are cleared every time Initialize is called.
notification_event->Clear();
@@ -71,7 +74,7 @@ void Initialize(Service::Interface* self) {
void GetSharedFont(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
- if (!shared_font_mem) {
+ if (!shared_font_loaded) {
LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds");
rb.Push<u32>(-1); // TODO: Find the right error code
rb.Skip(1 + 2, true);
@@ -82,7 +85,7 @@ void GetSharedFont(Service::Interface* self) {
// The shared font has to be relocated to the new address before being passed to the
// application.
VAddr target_address =
- Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address);
+ Memory::PhysicalToVirtualAddress(shared_font_mem->linear_heap_phys_address).value();
if (!shared_font_relocated) {
BCFNT::RelocateSharedFont(shared_font_mem, target_address);
shared_font_relocated = true;
@@ -93,7 +96,7 @@ void GetSharedFont(Service::Interface* self) {
// allocated, the real APT service calculates this address by scanning the entire address space
// (using svcQueryMemory) and searches for an allocation of the same size as the Shared Font.
rb.Push(target_address);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(shared_font_mem).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(shared_font_mem).Unwrap());
}
void NotifyToWait(Service::Interface* self) {
@@ -115,7 +118,7 @@ void GetLockHandle(Service::Interface* self) {
rb.Push(RESULT_SUCCESS); // No error
rb.Push(applet_attributes); // Applet Attributes, this value is passed to Enable.
rb.Push<u32>(0); // Least significant bit = power button state
- Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).MoveFrom();
+ Kernel::Handle handle_copy = Kernel::g_handle_table.Create(lock).Unwrap();
rb.PushCopyHandles(handle_copy);
LOG_WARNING(Service_APT, "(STUBBED) called handle=0x%08X applet_attributes=0x%08X", handle_copy,
@@ -231,7 +234,7 @@ void ReceiveParameter(Service::Interface* self) {
rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size
rb.PushMoveHandles((next_parameter.object != nullptr)
- ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom()
+ ? Kernel::g_handle_table.Create(next_parameter.object).Unwrap()
: 0);
rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0);
@@ -261,7 +264,7 @@ void GlanceParameter(Service::Interface* self) {
rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size
rb.PushCopyHandles((next_parameter.object != nullptr)
- ? Kernel::g_handle_table.Create(next_parameter.object).MoveFrom()
+ ? Kernel::g_handle_table.Create(next_parameter.object).Unwrap()
: 0);
rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0);
@@ -644,36 +647,146 @@ void CheckNew3DS(Service::Interface* self) {
LOG_WARNING(Service_APT, "(STUBBED) called");
}
-void Init() {
- AddService(new APT_A_Interface);
- AddService(new APT_S_Interface);
- AddService(new APT_U_Interface);
-
- HLE::Applets::Init();
-
- // Load the shared system font (if available).
+static u32 DecompressLZ11(const u8* in, u8* out) {
+ u32_le decompressed_size;
+ memcpy(&decompressed_size, in, sizeof(u32));
+ in += 4;
+
+ u8 type = decompressed_size & 0xFF;
+ ASSERT(type == 0x11);
+ decompressed_size >>= 8;
+
+ u32 current_out_size = 0;
+ u8 flags = 0, mask = 1;
+ while (current_out_size < decompressed_size) {
+ if (mask == 1) {
+ flags = *(in++);
+ mask = 0x80;
+ } else {
+ mask >>= 1;
+ }
+
+ if (flags & mask) {
+ u8 byte1 = *(in++);
+ u32 length = byte1 >> 4;
+ u32 offset;
+ if (length == 0) {
+ u8 byte2 = *(in++);
+ u8 byte3 = *(in++);
+ length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11;
+ offset = (((byte2 & 0x0F) << 8) | byte3) + 0x1;
+ } else if (length == 1) {
+ u8 byte2 = *(in++);
+ u8 byte3 = *(in++);
+ u8 byte4 = *(in++);
+ length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111;
+ offset = (((byte3 & 0x0F) << 8) | byte4) + 0x1;
+ } else {
+ u8 byte2 = *(in++);
+ length = (byte1 >> 4) + 0x1;
+ offset = (((byte1 & 0x0F) << 8) | byte2) + 0x1;
+ }
+
+ for (u32 i = 0; i < length; i++) {
+ *out = *(out - offset);
+ ++out;
+ }
+
+ current_out_size += length;
+ } else {
+ *(out++) = *(in++);
+ current_out_size++;
+ }
+ }
+ return decompressed_size;
+}
+
+static bool LoadSharedFont() {
+ // TODO (wwylele): load different font archive for region CHN/KOR/TWN
+ const u64_le shared_font_archive_id_low = 0x0004009b00014002;
+ const u64_le shared_font_archive_id_high = 0x00000001ffffff00;
+ std::vector<u8> shared_font_archive_id(16);
+ std::memcpy(&shared_font_archive_id[0], &shared_font_archive_id_low, sizeof(u64));
+ std::memcpy(&shared_font_archive_id[8], &shared_font_archive_id_high, sizeof(u64));
+ FileSys::Path archive_path(shared_font_archive_id);
+ auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::NCCH, archive_path);
+ if (archive_result.Failed())
+ return false;
+
+ std::vector<u8> romfs_path(20, 0); // 20-byte all zero path for opening RomFS
+ FileSys::Path file_path(romfs_path);
+ FileSys::Mode open_mode = {};
+ open_mode.read_flag.Assign(1);
+ auto file_result = Service::FS::OpenFileFromArchive(*archive_result, file_path, open_mode);
+ if (file_result.Failed())
+ return false;
+
+ auto romfs = std::move(file_result).Unwrap();
+ std::vector<u8> romfs_buffer(romfs->backend->GetSize());
+ romfs->backend->Read(0, romfs_buffer.size(), romfs_buffer.data());
+ romfs->backend->Close();
+
+ const u8* font_file = RomFS::GetFilePointer(romfs_buffer.data(), {u"cbf_std.bcfnt.lz"});
+ if (font_file == nullptr)
+ return false;
+
+ struct {
+ u32_le status;
+ u32_le region;
+ u32_le decompressed_size;
+ INSERT_PADDING_WORDS(0x1D);
+ } shared_font_header{};
+ static_assert(sizeof(shared_font_header) == 0x80, "shared_font_header has incorrect size");
+
+ shared_font_header.status = 2; // successfully loaded
+ shared_font_header.region = 1; // region JPN/EUR/USA
+ shared_font_header.decompressed_size =
+ DecompressLZ11(font_file, shared_font_mem->GetPointer(0x80));
+ std::memcpy(shared_font_mem->GetPointer(), &shared_font_header, sizeof(shared_font_header));
+ *shared_font_mem->GetPointer(0x83) = 'U'; // Change the magic from "CFNT" to "CFNU"
+
+ return true;
+}
+
+static bool LoadLegacySharedFont() {
+ // This is the legacy method to load shared font.
// The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header
// generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided
// a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file
// "shared_font.bin" in the Citra "sysdata" directory.
-
std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT;
FileUtil::CreateFullPath(filepath); // Create path if not already created
FileUtil::IOFile file(filepath, "rb");
-
if (file.IsOpen()) {
- // Create shared font memory object
- using Kernel::MemoryPermission;
- shared_font_mem =
- Kernel::SharedMemory::Create(nullptr, 0x332000, // 3272 KB
- MemoryPermission::ReadWrite, MemoryPermission::Read, 0,
- Kernel::MemoryRegion::SYSTEM, "APT:SharedFont");
- // Read shared font data
file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize());
+ return true;
+ }
+
+ return false;
+}
+
+void Init() {
+ AddService(new APT_A_Interface);
+ AddService(new APT_S_Interface);
+ AddService(new APT_U_Interface);
+
+ HLE::Applets::Init();
+
+ using Kernel::MemoryPermission;
+ shared_font_mem =
+ Kernel::SharedMemory::Create(nullptr, 0x332000, // 3272 KB
+ MemoryPermission::ReadWrite, MemoryPermission::Read, 0,
+ Kernel::MemoryRegion::SYSTEM, "APT:SharedFont");
+
+ if (LoadSharedFont()) {
+ shared_font_loaded = true;
+ } else if (LoadLegacySharedFont()) {
+ LOG_WARNING(Service_APT, "Loaded shared font by legacy method");
+ shared_font_loaded = true;
} else {
- LOG_WARNING(Service_APT, "Unable to load shared font: %s", filepath.c_str());
- shared_font_mem = nullptr;
+ LOG_WARNING(Service_APT, "Unable to load shared font");
+ shared_font_loaded = false;
}
lock = Kernel::Mutex::Create(false, "APT_U:Lock");
@@ -693,6 +806,7 @@ void Init() {
void Shutdown() {
shared_font_mem = nullptr;
+ shared_font_loaded = false;
shared_font_relocated = false;
lock = nullptr;
notification_event = nullptr;
diff --git a/src/core/hle/service/apt/bcfnt/bcfnt.cpp b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
index 57eb39d75..6d2474702 100644
--- a/src/core/hle/service/apt/bcfnt/bcfnt.cpp
+++ b/src/core/hle/service/apt/bcfnt/bcfnt.cpp
@@ -78,7 +78,8 @@ void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAd
memcpy(&cmap, data, sizeof(cmap));
// Relocate the offsets in the CMAP section
- cmap.next_cmap_offset += offset;
+ if (cmap.next_cmap_offset != 0)
+ cmap.next_cmap_offset += offset;
memcpy(data, &cmap, sizeof(cmap));
} else if (memcmp(section_header.magic, "CWDH", 4) == 0) {
@@ -86,7 +87,8 @@ void RelocateSharedFont(Kernel::SharedPtr<Kernel::SharedMemory> shared_font, VAd
memcpy(&cwdh, data, sizeof(cwdh));
// Relocate the offsets in the CWDH section
- cwdh.next_cwdh_offset += offset;
+ if (cwdh.next_cwdh_offset != 0)
+ cwdh.next_cwdh_offset += offset;
memcpy(data, &cwdh, sizeof(cwdh));
} else if (memcmp(section_header.magic, "TGLP", 4) == 0) {
diff --git a/src/core/hle/service/boss/boss_p.cpp b/src/core/hle/service/boss/boss_p.cpp
index ee941e228..3990d0d6e 100644
--- a/src/core/hle/service/boss/boss_p.cpp
+++ b/src/core/hle/service/boss/boss_p.cpp
@@ -66,7 +66,10 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00360084, SetTaskQuery, "SetTaskQuery"},
{0x00370084, GetTaskQuery, "GetTaskQuery"},
// boss:p
+ {0x04010082, nullptr, "InitializeSessionPrivileged"},
{0x04040080, nullptr, "GetAppNewFlag"},
+ {0x040D0182, nullptr, "GetNsDataIdListPrivileged"},
+ {0x040E0182, nullptr, "GetNsDataIdListPrivileged1"},
{0x04130082, nullptr, "SendPropertyPrivileged"},
{0x041500C0, nullptr, "DeleteNsDataPrivileged"},
{0x04160142, nullptr, "GetNsDataHeaderInfoPrivileged"},
diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp
index 7394c844f..c9f9e9d95 100644
--- a/src/core/hle/service/cam/cam.cpp
+++ b/src/core/hle/service/cam/cam.cpp
@@ -347,7 +347,7 @@ void GetVsyncInterruptEvent(Service::Interface* self) {
int port = *port_select.begin();
rb.Push(RESULT_SUCCESS);
rb.PushCopyHandles(
- Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).MoveFrom());
+ Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).Unwrap());
} else {
LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
@@ -366,7 +366,7 @@ void GetBufferErrorInterruptEvent(Service::Interface* self) {
int port = *port_select.begin();
rb.Push(RESULT_SUCCESS);
rb.PushCopyHandles(
- Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).MoveFrom());
+ Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).Unwrap());
} else {
LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
@@ -400,7 +400,7 @@ void SetReceiving(Service::Interface* self) {
}
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(port.completion_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(port.completion_event).Unwrap());
} else {
LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
rb.Push(ERROR_INVALID_ENUM_VALUE);
diff --git a/src/core/hle/service/cecd/cecd.cpp b/src/core/hle/service/cecd/cecd.cpp
index bd9814244..421006a9e 100644
--- a/src/core/hle/service/cecd/cecd.cpp
+++ b/src/core/hle/service/cecd/cecd.cpp
@@ -31,8 +31,8 @@ void GetCecStateAbbreviated(Service::Interface* self) {
void GetCecInfoEventHandle(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[3] = Kernel::g_handle_table.Create(cecinfo_event).MoveFrom(); // Event handle
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[3] = Kernel::g_handle_table.Create(cecinfo_event).Unwrap(); // Event handle
LOG_WARNING(Service_CECD, "(STUBBED) called");
}
@@ -40,8 +40,8 @@ void GetCecInfoEventHandle(Service::Interface* self) {
void GetChangeStateEventHandle(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[3] = Kernel::g_handle_table.Create(change_state_event).MoveFrom(); // Event handle
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[3] = Kernel::g_handle_table.Create(change_state_event).Unwrap(); // Event handle
LOG_WARNING(Service_CECD, "(STUBBED) called");
}
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index 5a7878b31..6624f1711 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -406,7 +406,7 @@ ResultCode UpdateConfigNANDSavegame() {
auto config_result = Service::FS::OpenFileFromArchive(cfg_system_save_data_archive, path, mode);
ASSERT_MSG(config_result.Succeeded(), "could not open file");
- auto config = config_result.MoveFrom();
+ auto config = std::move(config_result).Unwrap();
config->backend->Write(0, CONFIG_SAVEFILE_SIZE, 1, cfg_config_file_buffer.data());
return RESULT_SUCCESS;
@@ -560,7 +560,7 @@ ResultCode LoadConfigNANDSaveFile() {
// Read the file if it already exists
if (config_result.Succeeded()) {
- auto config = config_result.MoveFrom();
+ auto config = std::move(config_result).Unwrap();
config->backend->Read(0, CONFIG_SAVEFILE_SIZE, cfg_config_file_buffer.data());
return RESULT_SUCCESS;
}
diff --git a/src/core/hle/service/csnd_snd.cpp b/src/core/hle/service/csnd_snd.cpp
index 1455f20ca..9471ec1ef 100644
--- a/src/core/hle/service/csnd_snd.cpp
+++ b/src/core/hle/service/csnd_snd.cpp
@@ -51,8 +51,8 @@ static void Initialize(Interface* self) {
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::CopyHandleDesc(2);
- cmd_buff[3] = Kernel::g_handle_table.Create(mutex).MoveFrom();
- cmd_buff[4] = Kernel::g_handle_table.Create(shared_memory).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(mutex).Unwrap();
+ cmd_buff[4] = Kernel::g_handle_table.Create(shared_memory).Unwrap();
LOG_WARNING(Service_CSND, "(STUBBED) called");
}
diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp
index 363066d14..7d746054f 100644
--- a/src/core/hle/service/dsp_dsp.cpp
+++ b/src/core/hle/service/dsp_dsp.cpp
@@ -168,7 +168,7 @@ static void GetSemaphoreEventHandle(Service::Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0x16, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
// cmd_buff[2] not set
- cmd_buff[3] = Kernel::g_handle_table.Create(semaphore_event).MoveFrom(); // Event handle
+ cmd_buff[3] = Kernel::g_handle_table.Create(semaphore_event).Unwrap(); // Event handle
LOG_WARNING(Service_DSP, "(STUBBED) called");
}
diff --git a/src/core/hle/service/frd/frd.cpp b/src/core/hle/service/frd/frd.cpp
index 76ecda8b7..7ad7798da 100644
--- a/src/core/hle/service/frd/frd.cpp
+++ b/src/core/hle/service/frd/frd.cpp
@@ -6,6 +6,7 @@
#include "common/logging/log.h"
#include "common/string_util.h"
#include "core/hle/ipc.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/result.h"
#include "core/hle/service/frd/frd.h"
#include "core/hle/service/frd/frd_a.h"
@@ -105,6 +106,48 @@ void GetMyScreenName(Service::Interface* self) {
LOG_WARNING(Service_FRD, "(STUBBED) called");
}
+void UnscrambleLocalFriendCode(Service::Interface* self) {
+ const size_t scrambled_friend_code_size = 12;
+ const size_t friend_code_size = 8;
+
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1C, 1, 2);
+ const u32 friend_code_count = rp.Pop<u32>();
+ size_t in_buffer_size;
+ const VAddr scrambled_friend_codes = rp.PopStaticBuffer(&in_buffer_size, false);
+ ASSERT_MSG(in_buffer_size == (friend_code_count * scrambled_friend_code_size),
+ "Wrong input buffer size");
+
+ size_t out_buffer_size;
+ VAddr unscrambled_friend_codes = rp.PeekStaticBuffer(0, &out_buffer_size);
+ ASSERT_MSG(out_buffer_size == (friend_code_count * friend_code_size),
+ "Wrong output buffer size");
+
+ for (u32 current = 0; current < friend_code_count; ++current) {
+ // TODO(B3N30): Unscramble the codes and compare them against the friend list
+ // Only write 0 if the code isn't in friend list, otherwise write the
+ // unscrambled one
+ //
+ // Code for unscrambling (should be compared to HW):
+ // std::array<u16, 6> scambled_friend_code;
+ // Memory::ReadBlock(scrambled_friend_codes+(current*scrambled_friend_code_size),
+ // scambled_friend_code.data(), scrambled_friend_code_size); std::array<u16, 4>
+ // unscrambled_friend_code; unscrambled_friend_code[0] = scambled_friend_code[0] ^
+ // scambled_friend_code[5]; unscrambled_friend_code[1] = scambled_friend_code[1] ^
+ // scambled_friend_code[5]; unscrambled_friend_code[2] = scambled_friend_code[2] ^
+ // scambled_friend_code[5]; unscrambled_friend_code[3] = scambled_friend_code[3] ^
+ // scambled_friend_code[5];
+
+ u64 result = 0ull;
+ Memory::WriteBlock(unscrambled_friend_codes + (current * sizeof(result)), &result,
+ sizeof(result));
+ }
+
+ LOG_WARNING(Service_FRD, "(STUBBED) called");
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(RESULT_SUCCESS);
+ rb.PushStaticBuffer(unscrambled_friend_codes, out_buffer_size, 0);
+}
+
void SetClientSdkVersion(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
diff --git a/src/core/hle/service/frd/frd.h b/src/core/hle/service/frd/frd.h
index e61940ea0..66a87c8cd 100644
--- a/src/core/hle/service/frd/frd.h
+++ b/src/core/hle/service/frd/frd.h
@@ -96,6 +96,19 @@ void GetMyFriendKey(Service::Interface* self);
void GetMyScreenName(Service::Interface* self);
/**
+ * FRD::UnscrambleLocalFriendCode service function
+ * Inputs:
+ * 1 : Friend code count
+ * 2 : ((count * 12) << 14) | 0x402
+ * 3 : Pointer to encoded friend codes. Each is 12 bytes large
+ * 64 : ((count * 8) << 14) | 2
+ * 65 : Pointer to write decoded local friend codes to. Each is 8 bytes large.
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void UnscrambleLocalFriendCode(Service::Interface* self);
+
+/**
* FRD::SetClientSdkVersion service function
* Inputs:
* 1 : Used SDK Version
diff --git a/src/core/hle/service/frd/frd_u.cpp b/src/core/hle/service/frd/frd_u.cpp
index 496f29ca9..6970ff768 100644
--- a/src/core/hle/service/frd/frd_u.cpp
+++ b/src/core/hle/service/frd/frd_u.cpp
@@ -36,7 +36,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00190042, nullptr, "GetFriendFavoriteGame"},
{0x001A00C4, nullptr, "GetFriendInfo"},
{0x001B0080, nullptr, "IsIncludedInFriendList"},
- {0x001C0042, nullptr, "UnscrambleLocalFriendCode"},
+ {0x001C0042, UnscrambleLocalFriendCode, "UnscrambleLocalFriendCode"},
{0x001D0002, nullptr, "UpdateGameModeDescription"},
{0x001E02C2, nullptr, "UpdateGameMode"},
{0x001F0042, nullptr, "SendInvitation"},
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index 3605ef175..033fbc9aa 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -311,7 +311,7 @@ ResultVal<std::shared_ptr<File>> OpenFileFromArchive(ArchiveHandle archive_handl
if (backend.Failed())
return backend.Code();
- auto file = std::shared_ptr<File>(new File(backend.MoveFrom(), path));
+ auto file = std::shared_ptr<File>(new File(std::move(backend).Unwrap(), path));
return MakeResult<std::shared_ptr<File>>(std::move(file));
}
@@ -401,7 +401,7 @@ ResultVal<std::shared_ptr<Directory>> OpenDirectoryFromArchive(ArchiveHandle arc
if (backend.Failed())
return backend.Code();
- auto directory = std::shared_ptr<Directory>(new Directory(backend.MoveFrom(), path));
+ auto directory = std::shared_ptr<Directory>(new Directory(std::move(backend).Unwrap(), path));
return MakeResult<std::shared_ptr<Directory>>(std::move(directory));
}
diff --git a/src/core/hle/service/fs/fs_user.cpp b/src/core/hle/service/fs/fs_user.cpp
index 34e1783ec..b9eab7838 100644
--- a/src/core/hle/service/fs/fs_user.cpp
+++ b/src/core/hle/service/fs/fs_user.cpp
@@ -87,7 +87,7 @@ static void OpenFile(Service::Interface* self) {
file->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
rb.PushMoveHandles(
- Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom());
+ Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap());
} else {
rb.PushMoveHandles(0);
LOG_ERROR(Service_FS, "failed to get a handle for file %s", file_path.DebugStr().c_str());
@@ -153,7 +153,7 @@ static void OpenFileDirectly(Service::Interface* self) {
file->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
cmd_buff[3] =
- Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom();
+ Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap();
} else {
cmd_buff[3] = 0;
LOG_ERROR(Service_FS, "failed to get a handle for file %s mode=%u attributes=%u",
@@ -420,7 +420,7 @@ static void OpenDirectory(Service::Interface* self) {
directory->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
cmd_buff[3] =
- Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).MoveFrom();
+ Kernel::g_handle_table.Create(std::get<SharedPtr<ClientSession>>(sessions)).Unwrap();
} else {
LOG_ERROR(Service_FS, "failed to get a handle for directory type=%d size=%d data=%s",
dirname_type, dirname_size, dir_path.DebugStr().c_str());
diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp
index 6ff0f4812..88684b82d 100644
--- a/src/core/hle/service/gsp_gpu.cpp
+++ b/src/core/hle/service/gsp_gpu.cpp
@@ -389,8 +389,8 @@ static void RegisterInterruptRelayQueue(Interface* self) {
} else {
cmd_buff[1] = RESULT_SUCCESS.raw;
}
- cmd_buff[2] = g_thread_id++; // Thread ID
- cmd_buff[4] = Kernel::g_handle_table.Create(g_shared_memory).MoveFrom(); // GSP shared memory
+ cmd_buff[2] = g_thread_id++; // Thread ID
+ cmd_buff[4] = Kernel::g_handle_table.Create(g_shared_memory).Unwrap(); // GSP shared memory
g_interrupt_event->Signal(); // TODO(bunnei): Is this correct?
@@ -475,12 +475,11 @@ static void ExecuteCommand(const Command& command, u32 thread_id) {
// TODO: Consider attempting rasterizer-accelerated surface blit if that usage is ever
// possible/likely
- Memory::RasterizerFlushRegion(
- Memory::VirtualToPhysicalAddress(command.dma_request.source_address),
- command.dma_request.size);
- Memory::RasterizerFlushAndInvalidateRegion(
- Memory::VirtualToPhysicalAddress(command.dma_request.dest_address),
- command.dma_request.size);
+ Memory::RasterizerFlushVirtualRegion(command.dma_request.source_address,
+ command.dma_request.size, Memory::FlushMode::Flush);
+ Memory::RasterizerFlushVirtualRegion(command.dma_request.dest_address,
+ command.dma_request.size,
+ Memory::FlushMode::FlushAndInvalidate);
// TODO(Subv): These memory accesses should not go through the application's memory mapping.
// They should go through the GSP module's memory mapping.
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 5255f6dc8..2014b8461 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -253,12 +253,12 @@ void GetIPCHandles(Service::Interface* self) {
cmd_buff[1] = 0; // No error
cmd_buff[2] = 0x14000000; // IPC Command Structure translate-header
// TODO(yuriks): Return error from SendSyncRequest is this fails (part of IPC marshalling)
- cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).MoveFrom();
- cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).MoveFrom();
- cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).MoveFrom();
- cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).MoveFrom();
- cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).MoveFrom();
- cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(Service::HID::shared_mem).Unwrap();
+ cmd_buff[4] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_1).Unwrap();
+ cmd_buff[5] = Kernel::g_handle_table.Create(Service::HID::event_pad_or_touch_2).Unwrap();
+ cmd_buff[6] = Kernel::g_handle_table.Create(Service::HID::event_accelerometer).Unwrap();
+ cmd_buff[7] = Kernel::g_handle_table.Create(Service::HID::event_gyroscope).Unwrap();
+ cmd_buff[8] = Kernel::g_handle_table.Create(Service::HID::event_debug_pad).Unwrap();
}
void EnableAccelerometer(Service::Interface* self) {
diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp
index 0de698003..837413f93 100644
--- a/src/core/hle/service/ir/ir_rst.cpp
+++ b/src/core/hle/service/ir/ir_rst.cpp
@@ -145,8 +145,8 @@ static void GetHandles(Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 3);
rb.Push(RESULT_SUCCESS);
- rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(),
- Kernel::g_handle_table.Create(Service::IR::update_event).MoveFrom());
+ rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).Unwrap(),
+ Kernel::g_handle_table.Create(Service::IR::update_event).Unwrap());
}
/**
diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp
index fdecdce64..fbdf7a465 100644
--- a/src/core/hle/service/ir/ir_user.cpp
+++ b/src/core/hle/service/ir/ir_user.cpp
@@ -337,7 +337,7 @@ void GetReceiveEvent(Interface* self) {
IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0A, 1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::receive_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::receive_event).Unwrap());
LOG_INFO(Service_IR, "called");
}
@@ -354,7 +354,7 @@ void GetSendEvent(Interface* self) {
IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0B, 1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::send_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::send_event).Unwrap());
LOG_INFO(Service_IR, "called");
}
@@ -394,7 +394,7 @@ static void GetConnectionStatusEvent(Interface* self) {
IPC::RequestBuilder rb(Kernel::GetCommandBuffer(), 0x0C, 1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::conn_status_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(Service::IR::conn_status_event).Unwrap());
LOG_INFO(Service_IR, "called");
}
diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp
index 35212b59b..23e1ff094 100644
--- a/src/core/hle/service/mic_u.cpp
+++ b/src/core/hle/service/mic_u.cpp
@@ -160,7 +160,7 @@ static void IsSampling(Interface* self) {
static void GetBufferFullEvent(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[3] = Kernel::g_handle_table.Create(buffer_full_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(buffer_full_event).Unwrap();
LOG_WARNING(Service_MIC, "(STUBBED) called");
}
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index b44a9f668..cb09ed0b7 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -95,7 +95,7 @@ void GetTagInRangeEvent(Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0xB, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).Unwrap();
LOG_WARNING(Service_NFC, "(STUBBED) called");
}
@@ -105,7 +105,7 @@ void GetTagOutOfRangeEvent(Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0xC, 1, 2);
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(tag_out_of_range_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(tag_out_of_range_event).Unwrap();
LOG_WARNING(Service_NFC, "(STUBBED) called");
}
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index 6c4600f25..6dbdff044 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -15,6 +15,7 @@
#include "core/hle/result.h"
#include "core/hle/service/nwm/nwm_uds.h"
#include "core/hle/service/nwm/uds_beacon.h"
+#include "core/hle/service/nwm/uds_data.h"
#include "core/memory.h"
namespace Service {
@@ -190,7 +191,7 @@ static void InitializeWithVersion(Interface* self) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap());
LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X",
sharedmem_size, version, sharedmem_handle);
@@ -215,6 +216,11 @@ static void GetConnectionStatus(Interface* self) {
rb.Push(RESULT_SUCCESS);
rb.PushRaw(connection_status);
+ // Reset the bitmask of changed nodes after each call to this
+ // function to prevent falsely informing games of outstanding
+ // changes in subsequent calls.
+ connection_status.changed_nodes = 0;
+
LOG_DEBUG(Service_NWM, "called");
}
@@ -260,7 +266,7 @@ static void Bind(Interface* self) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(event).MoveFrom());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(event).Unwrap());
}
/**
@@ -314,8 +320,11 @@ static void BeginHostingNetwork(Interface* self) {
// The host is always the first node
connection_status.network_node_id = 1;
node_info[0].network_node_id = 1;
+ connection_status.nodes[0] = connection_status.network_node_id;
// Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken.
connection_status.node_bitmask |= 1;
+ // Notify the application that the first node was set.
+ connection_status.changed_nodes |= 1;
// If the game has a preferred channel, use that instead.
if (network_info.channel != 0)
@@ -352,6 +361,8 @@ static void DestroyNetwork(Interface* self) {
// Unschedule the beacon broadcast event.
CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0);
+ // TODO(Subv): Check if connection_status is indeed reset after this call.
+ connection_status = {};
connection_status.status = static_cast<u8>(NetworkStatus::NotConnected);
connection_status_event->Signal();
@@ -363,6 +374,80 @@ static void DestroyNetwork(Interface* self) {
}
/**
+ * NWM_UDS::SendTo service function.
+ * Sends a data frame to the UDS network we're connected to.
+ * Inputs:
+ * 0 : Command header.
+ * 1 : Unknown.
+ * 2 : u16 Destination network node id.
+ * 3 : u8 Data channel.
+ * 4 : Buffer size >> 2
+ * 5 : Data size
+ * 6 : Flags
+ * 7 : Input buffer descriptor
+ * 8 : Input buffer address
+ * Outputs:
+ * 0 : Return header
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void SendTo(Interface* self) {
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x17, 6, 2);
+
+ rp.Skip(1, false);
+ u16 dest_node_id = rp.Pop<u16>();
+ u8 data_channel = rp.Pop<u8>();
+ rp.Skip(1, false);
+ u32 data_size = rp.Pop<u32>();
+ u32 flags = rp.Pop<u32>();
+
+ size_t desc_size;
+ const VAddr input_address = rp.PopStaticBuffer(&desc_size, false);
+ ASSERT(desc_size == data_size);
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsClient) &&
+ connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ rb.Push(ResultCode(ErrorDescription::NotAuthorized, ErrorModule::UDS,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+ return;
+ }
+
+ if (dest_node_id == connection_status.network_node_id) {
+ rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS,
+ ErrorSummary::WrongArgument, ErrorLevel::Status));
+ return;
+ }
+
+ // TODO(Subv): Do something with the flags.
+
+ constexpr size_t MaxSize = 0x5C6;
+ if (data_size > MaxSize) {
+ rb.Push(ResultCode(ErrorDescription::TooLarge, ErrorModule::UDS,
+ ErrorSummary::WrongArgument, ErrorLevel::Usage));
+ return;
+ }
+
+ std::vector<u8> data(data_size);
+ Memory::ReadBlock(input_address, data.data(), data.size());
+
+ // TODO(Subv): Increment the sequence number after each sent packet.
+ u16 sequence_number = 0;
+ std::vector<u8> data_payload = GenerateDataPayload(
+ data, data_channel, dest_node_id, connection_status.network_node_id, sequence_number);
+
+ // TODO(Subv): Retrieve the MAC address of the dest_node_id and our own to encrypt
+ // and encapsulate the payload.
+
+ // TODO(Subv): Send the frame.
+
+ rb.Push(RESULT_SUCCESS);
+
+ LOG_WARNING(Service_NWM, "(STUB) called dest_node_id=%u size=%u flags=%u channel=%u",
+ static_cast<u32>(dest_node_id), data_size, flags, static_cast<u32>(data_channel));
+}
+
+/**
* NWM_UDS::GetChannel service function.
* Returns the WiFi channel in which the network we're connected to is transmitting.
* Inputs:
@@ -533,6 +618,42 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {
beacon_broadcast_event, 0);
}
+/*
+ * Returns an available index in the nodes array for the
+ * currently-hosted UDS network.
+ */
+static u32 GetNextAvailableNodeId() {
+ ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
+ "Can not accept clients if we're not hosting a network");
+
+ for (unsigned index = 0; index < connection_status.max_nodes; ++index) {
+ if ((connection_status.node_bitmask & (1 << index)) == 0)
+ return index;
+ }
+
+ // Any connection attempts to an already full network should have been refused.
+ ASSERT_MSG(false, "No available connection slots in the network");
+}
+
+/*
+ * Called when a client connects to an UDS network we're hosting,
+ * updates the connection status and signals the update event.
+ * @param network_node_id Network Node Id of the connecting client.
+ */
+void OnClientConnected(u16 network_node_id) {
+ ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
+ "Can not accept clients if we're not hosting a network");
+ ASSERT_MSG(connection_status.total_nodes < connection_status.max_nodes,
+ "Can not accept connections on a full network");
+
+ u32 node_id = GetNextAvailableNodeId();
+ connection_status.node_bitmask |= 1 << node_id;
+ connection_status.changed_nodes |= 1 << node_id;
+ connection_status.nodes[node_id] = network_node_id;
+ connection_status.total_nodes++;
+ connection_status_event->Signal();
+}
+
const Interface::FunctionInfo FunctionTable[] = {
{0x00010442, nullptr, "Initialize (deprecated)"},
{0x00020000, nullptr, "Scrap"},
@@ -554,7 +675,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00130040, nullptr, "Unbind"},
{0x001400C0, nullptr, "PullPacket"},
{0x00150080, nullptr, "SetMaxSendDelay"},
- {0x00170182, nullptr, "SendTo"},
+ {0x00170182, SendTo, "SendTo"},
{0x001A0000, GetChannel, "GetChannel"},
{0x001B0302, InitializeWithVersion, "InitializeWithVersion"},
{0x001D0044, BeginHostingNetwork, "BeginHostingNetwork"},
diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h
index 29b146569..141f49f9c 100644
--- a/src/core/hle/service/nwm/nwm_uds.h
+++ b/src/core/hle/service/nwm/nwm_uds.h
@@ -24,6 +24,9 @@ const double MillisecondsPerTU = 1.024;
// Interval measured in TU, the default value is 100TU = 102.4ms
const u16 DefaultBeaconInterval = 100;
+/// The maximum number of nodes that can exist in an UDS session.
+constexpr u32 UDSMaxNodes = 16;
+
struct NodeInfo {
u64_le friend_code_seed;
std::array<u16_le, 10> username;
@@ -47,8 +50,8 @@ struct ConnectionStatus {
u32_le status;
INSERT_PADDING_WORDS(1);
u16_le network_node_id;
- INSERT_PADDING_BYTES(2);
- INSERT_PADDING_BYTES(32);
+ u16_le changed_nodes;
+ u16_le nodes[UDSMaxNodes];
u8 total_nodes;
u8 max_nodes;
u16_le node_bitmask;
diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h
index 6df4c4f47..caacf4c6f 100644
--- a/src/core/hle/service/nwm/uds_beacon.h
+++ b/src/core/hle/service/nwm/uds_beacon.h
@@ -15,9 +15,6 @@ namespace Service {
namespace NWM {
using MacAddress = std::array<u8, 6>;
-
-/// The maximum number of nodes that can exist in an UDS session.
-constexpr u32 UDSMaxNodes = 16;
constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32};
/// Additional block tag ids in the Beacon frames
diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp
new file mode 100644
index 000000000..8c6742dba
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_data.cpp
@@ -0,0 +1,278 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <cryptopp/aes.h>
+#include <cryptopp/ccm.h>
+#include <cryptopp/filters.h>
+#include <cryptopp/md5.h>
+#include <cryptopp/modes.h>
+#include "core/hle/service/nwm/nwm_uds.h"
+#include "core/hle/service/nwm/uds_data.h"
+#include "core/hw/aes/key.h"
+
+namespace Service {
+namespace NWM {
+
+using MacAddress = std::array<u8, 6>;
+
+/*
+ * Generates a SNAP-enabled 802.2 LLC header for the specified protocol.
+ * @returns a buffer with the bytes of the generated header.
+ */
+static std::vector<u8> GenerateLLCHeader(EtherType protocol) {
+ LLCHeader header{};
+ header.protocol = static_cast<u16>(protocol);
+
+ std::vector<u8> buffer(sizeof(header));
+ memcpy(buffer.data(), &header, sizeof(header));
+
+ return buffer;
+}
+
+/*
+ * Generates a Nintendo UDS SecureData header with the specified parameters.
+ * @returns a buffer with the bytes of the generated header.
+ */
+static std::vector<u8> GenerateSecureDataHeader(u16 data_size, u8 channel, u16 dest_node_id,
+ u16 src_node_id, u16 sequence_number) {
+ SecureDataHeader header{};
+ header.protocol_size = data_size + sizeof(SecureDataHeader);
+ // Note: This size includes everything except the first 4 bytes of the structure,
+ // reinforcing the hypotheses that the first 4 bytes are actually the header of
+ // another container protocol.
+ header.securedata_size = data_size + sizeof(SecureDataHeader) - 4;
+ // Frames sent by the emulated application are never UDS management frames
+ header.is_management = 0;
+ header.data_channel = channel;
+ header.sequence_number = sequence_number;
+ header.dest_node_id = dest_node_id;
+ header.src_node_id = src_node_id;
+
+ std::vector<u8> buffer(sizeof(header));
+ memcpy(buffer.data(), &header, sizeof(header));
+
+ return buffer;
+}
+
+/*
+ * Calculates the CTR used for the AES-CTR process that calculates
+ * the CCMP crypto key for data frames.
+ * @returns The CTR used for data frames crypto key generation.
+ */
+static std::array<u8, CryptoPP::MD5::DIGESTSIZE> GetDataCryptoCTR(const NetworkInfo& network_info) {
+ DataFrameCryptoCTR data{};
+
+ data.host_mac = network_info.host_mac_address;
+ data.wlan_comm_id = network_info.wlan_comm_id;
+ data.id = network_info.id;
+ data.network_id = network_info.network_id;
+
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> hash;
+ CryptoPP::MD5().CalculateDigest(hash.data(), reinterpret_cast<u8*>(&data), sizeof(data));
+
+ return hash;
+}
+
+/*
+ * Generates the key used for encrypting the 802.11 data frames generated by UDS.
+ * @returns The key used for data frames crypto.
+ */
+static std::array<u8, CryptoPP::AES::BLOCKSIZE> GenerateDataCCMPKey(
+ const std::vector<u8>& passphrase, const NetworkInfo& network_info) {
+ // Calculate the MD5 hash of the input passphrase.
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> passphrase_hash;
+ CryptoPP::MD5().CalculateDigest(passphrase_hash.data(), passphrase.data(), passphrase.size());
+
+ std::array<u8, CryptoPP::AES::BLOCKSIZE> ccmp_key;
+
+ // The CCMP key is the result of encrypting the MD5 hash of the passphrase with AES-CTR using
+ // keyslot 0x2D.
+ using CryptoPP::AES;
+ std::array<u8, CryptoPP::MD5::DIGESTSIZE> counter = GetDataCryptoCTR(network_info);
+ std::array<u8, AES::BLOCKSIZE> key = HW::AES::GetNormalKey(HW::AES::KeySlotID::UDSDataKey);
+ CryptoPP::CTR_Mode<AES>::Encryption aes;
+ aes.SetKeyWithIV(key.data(), AES::BLOCKSIZE, counter.data());
+ aes.ProcessData(ccmp_key.data(), passphrase_hash.data(), passphrase_hash.size());
+
+ return ccmp_key;
+}
+
+/*
+ * Generates the Additional Authenticated Data (AAD) for an UDS 802.11 encrypted data frame.
+ * @returns a buffer with the bytes of the AAD.
+ */
+static std::vector<u8> GenerateCCMPAAD(const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 frame_control) {
+ // Reference: IEEE 802.11-2007
+
+ // 8.3.3.3.2 Construct AAD (22-30 bytes)
+ // The AAD is constructed from the MPDU header. The AAD does not include the header Duration
+ // field, because the Duration field value can change due to normal IEEE 802.11 operation (e.g.,
+ // a rate change during retransmission). For similar reasons, several subfields in the Frame
+ // Control field are masked to 0.
+ struct {
+ u16_be FC; // MPDU Frame Control field
+ MacAddress A1;
+ MacAddress A2;
+ MacAddress A3;
+ u16_be SC; // MPDU Sequence Control field
+ } aad_struct{};
+
+ constexpr u16 AADFrameControlMask = 0x8FC7;
+ aad_struct.FC = frame_control & AADFrameControlMask;
+ aad_struct.SC = 0;
+
+ bool to_ds = (frame_control & (1 << 0)) != 0;
+ bool from_ds = (frame_control & (1 << 1)) != 0;
+ // In the 802.11 standard, ToDS = 1 and FromDS = 1 is a valid configuration,
+ // however, the 3DS doesn't seem to transmit frames with such combination.
+ ASSERT_MSG(to_ds != from_ds, "Invalid combination");
+
+ // The meaning of the address fields depends on the ToDS and FromDS fields.
+ if (from_ds) {
+ aad_struct.A1 = receiver;
+ aad_struct.A2 = bssid;
+ aad_struct.A3 = sender;
+ }
+
+ if (to_ds) {
+ aad_struct.A1 = bssid;
+ aad_struct.A2 = sender;
+ aad_struct.A3 = receiver;
+ }
+
+ std::vector<u8> aad(sizeof(aad_struct));
+ std::memcpy(aad.data(), &aad_struct, sizeof(aad_struct));
+
+ return aad;
+}
+
+/*
+ * Decrypts the payload of an encrypted 802.11 data frame using the specified key.
+ * @returns The decrypted payload.
+ */
+static std::vector<u8> DecryptDataFrame(const std::vector<u8>& encrypted_payload,
+ const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key,
+ const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 sequence_number,
+ u16 frame_control) {
+
+ // Reference: IEEE 802.11-2007
+
+ std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control);
+
+ std::vector<u8> packet_number{0,
+ 0,
+ 0,
+ 0,
+ static_cast<u8>((sequence_number >> 8) & 0xFF),
+ static_cast<u8>(sequence_number & 0xFF)};
+
+ // 8.3.3.3.3 Construct CCM nonce (13 bytes)
+ std::vector<u8> nonce;
+ nonce.push_back(0); // priority
+ nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2
+ nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN
+
+ try {
+ CryptoPP::CCM<CryptoPP::AES, 8>::Decryption d;
+ d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size());
+ d.SpecifyDataLengths(aad.size(), encrypted_payload.size() - 8, 0);
+
+ CryptoPP::AuthenticatedDecryptionFilter df(
+ d, nullptr, CryptoPP::AuthenticatedDecryptionFilter::MAC_AT_END |
+ CryptoPP::AuthenticatedDecryptionFilter::THROW_EXCEPTION);
+ // put aad
+ df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size());
+
+ // put cipher with mac
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, encrypted_payload.data(),
+ encrypted_payload.size() - 8);
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL,
+ encrypted_payload.data() + encrypted_payload.size() - 8, 8);
+
+ df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL);
+ df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
+ df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
+
+ int size = df.MaxRetrievable();
+
+ std::vector<u8> pdata(size);
+ df.Get(pdata.data(), size);
+ return pdata;
+ } catch (CryptoPP::Exception&) {
+ LOG_ERROR(Service_NWM, "failed to decrypt");
+ }
+
+ return {};
+}
+
+/*
+ * Encrypts the payload of an 802.11 data frame using the specified key.
+ * @returns The encrypted payload.
+ */
+static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload,
+ const std::array<u8, CryptoPP::AES::BLOCKSIZE>& ccmp_key,
+ const MacAddress& sender, const MacAddress& receiver,
+ const MacAddress& bssid, u16 sequence_number,
+ u16 frame_control) {
+ // Reference: IEEE 802.11-2007
+
+ std::vector<u8> aad = GenerateCCMPAAD(sender, receiver, bssid, frame_control);
+
+ std::vector<u8> packet_number{0,
+ 0,
+ 0,
+ 0,
+ static_cast<u8>((sequence_number >> 8) & 0xFF),
+ static_cast<u8>(sequence_number & 0xFF)};
+
+ // 8.3.3.3.3 Construct CCM nonce (13 bytes)
+ std::vector<u8> nonce;
+ nonce.push_back(0); // priority
+ nonce.insert(nonce.end(), sender.begin(), sender.end()); // Address 2
+ nonce.insert(nonce.end(), packet_number.begin(), packet_number.end()); // PN
+
+ try {
+ CryptoPP::CCM<CryptoPP::AES, 8>::Encryption d;
+ d.SetKeyWithIV(ccmp_key.data(), ccmp_key.size(), nonce.data(), nonce.size());
+ d.SpecifyDataLengths(aad.size(), payload.size(), 0);
+
+ CryptoPP::AuthenticatedEncryptionFilter df(d);
+ // put aad
+ df.ChannelPut(CryptoPP::AAD_CHANNEL, aad.data(), aad.size());
+ df.ChannelMessageEnd(CryptoPP::AAD_CHANNEL);
+
+ // put plaintext
+ df.ChannelPut(CryptoPP::DEFAULT_CHANNEL, payload.data(), payload.size());
+ df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
+
+ df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
+
+ int size = df.MaxRetrievable();
+
+ std::vector<u8> cipher(size);
+ df.Get(cipher.data(), size);
+ return cipher;
+ } catch (CryptoPP::Exception&) {
+ LOG_ERROR(Service_NWM, "failed to encrypt");
+ }
+
+ return {};
+}
+
+std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
+ u16 src_node, u16 sequence_number) {
+ std::vector<u8> buffer = GenerateLLCHeader(EtherType::SecureData);
+ std::vector<u8> securedata_header =
+ GenerateSecureDataHeader(data.size(), channel, dest_node, src_node, sequence_number);
+
+ buffer.insert(buffer.end(), securedata_header.begin(), securedata_header.end());
+ buffer.insert(buffer.end(), data.begin(), data.end());
+ return buffer;
+}
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h
new file mode 100644
index 000000000..a23520a41
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_data.h
@@ -0,0 +1,78 @@
+// Copyright 2017 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_types.h"
+#include "common/swap.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NWM {
+
+enum class SAP : u8 { SNAPExtensionUsed = 0xAA };
+
+enum class PDUControl : u8 { UnnumberedInformation = 3 };
+
+enum class EtherType : u16 { SecureData = 0x876D, EAPoL = 0x888E };
+
+/*
+ * 802.2 header, UDS packets always use SNAP for these headers,
+ * which means the dsap and ssap are always SNAPExtensionUsed (0xAA)
+ * and the OUI is always 0.
+ */
+struct LLCHeader {
+ u8 dsap = static_cast<u8>(SAP::SNAPExtensionUsed);
+ u8 ssap = static_cast<u8>(SAP::SNAPExtensionUsed);
+ u8 control = static_cast<u8>(PDUControl::UnnumberedInformation);
+ std::array<u8, 3> OUI = {};
+ u16_be protocol;
+};
+
+static_assert(sizeof(LLCHeader) == 8, "LLCHeader has the wrong size");
+
+/*
+ * Nintendo SecureData header, every UDS packet contains one,
+ * it is used to store metadata about the transmission such as
+ * the source and destination network node ids.
+ */
+struct SecureDataHeader {
+ // TODO(Subv): It is likely that the first 4 bytes of this header are
+ // actually part of another container protocol.
+ u16_be protocol_size;
+ INSERT_PADDING_BYTES(2);
+ u16_be securedata_size;
+ u8 is_management;
+ u8 data_channel;
+ u16_be sequence_number;
+ u16_be dest_node_id;
+ u16_be src_node_id;
+};
+
+static_assert(sizeof(SecureDataHeader) == 14, "SecureDataHeader has the wrong size");
+
+/*
+ * The raw bytes of this structure are the CTR used in the encryption (AES-CTR)
+ * process used to generate the CCMP key for data frame encryption.
+ */
+struct DataFrameCryptoCTR {
+ u32_le wlan_comm_id;
+ u32_le network_id;
+ std::array<u8, 6> host_mac;
+ u16_le id;
+};
+
+static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size");
+
+/**
+ * Generates an unencrypted 802.11 data payload.
+ * @returns The generated frame payload.
+ */
+std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
+ u16 src_node, u16 sequence_number);
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp
index 39382ef09..a0b959797 100644
--- a/src/core/hle/service/ptm/ptm.cpp
+++ b/src/core/hle/service/ptm/ptm.cpp
@@ -152,7 +152,7 @@ void Init() {
auto gamecoin_result =
Service::FS::OpenFileFromArchive(*archive_result, gamecoin_path, open_mode);
if (gamecoin_result.Succeeded()) {
- auto gamecoin = gamecoin_result.MoveFrom();
+ auto gamecoin = std::move(gamecoin_result).Unwrap();
gamecoin->backend->Write(0, sizeof(GameCoin), true,
reinterpret_cast<const u8*>(&default_game_coin));
gamecoin->backend->Close();
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 0d443aa44..aad950e50 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -2,9 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
+#include <fmt/format.h>
+#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_util.h"
+#include "core/hle/ipc.h"
#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/process.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/service/ac/ac.h"
@@ -45,9 +50,14 @@
#include "core/hle/service/ssl_c.h"
#include "core/hle/service/y2r_u.h"
+using Kernel::ClientPort;
+using Kernel::ServerPort;
+using Kernel::ServerSession;
+using Kernel::SharedPtr;
+
namespace Service {
-std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports;
+std::unordered_map<std::string, SharedPtr<ClientPort>> g_kernel_named_ports;
/**
* Creates a function string for logging, complete with the name (or header code, depending
@@ -69,7 +79,7 @@ static std::string MakeFunctionString(const char* name, const char* port_name,
Interface::Interface(u32 max_sessions) : max_sessions(max_sessions) {}
Interface::~Interface() = default;
-void Interface::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) {
+void Interface::HandleSyncRequest(SharedPtr<ServerSession> server_session) {
// TODO(Subv): Make use of the server_session in the HLE service handlers to distinguish which
// session triggered each command.
@@ -103,30 +113,108 @@ void Interface::Register(const FunctionInfo* functions, size_t n) {
}
////////////////////////////////////////////////////////////////////////////////////////////////////
+
+ServiceFrameworkBase::ServiceFrameworkBase(const char* service_name, u32 max_sessions,
+ InvokerFn* handler_invoker)
+ : service_name(service_name), max_sessions(max_sessions), handler_invoker(handler_invoker) {}
+
+ServiceFrameworkBase::~ServiceFrameworkBase() = default;
+
+void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) {
+ ASSERT(port == nullptr);
+ port = service_manager.RegisterService(service_name, max_sessions).Unwrap();
+ port->SetHleHandler(shared_from_this());
+}
+
+void ServiceFrameworkBase::InstallAsNamedPort() {
+ ASSERT(port == nullptr);
+ SharedPtr<ServerPort> server_port;
+ SharedPtr<ClientPort> client_port;
+ std::tie(server_port, client_port) = ServerPort::CreatePortPair(max_sessions, service_name);
+ server_port->SetHleHandler(shared_from_this());
+ AddNamedPort(service_name, std::move(client_port));
+}
+
+void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, size_t n) {
+ handlers.reserve(handlers.size() + n);
+ for (size_t i = 0; i < n; ++i) {
+ // Usually this array is sorted by id already, so hint to insert at the end
+ handlers.emplace_hint(handlers.cend(), functions[i].expected_header, functions[i]);
+ }
+}
+
+void ServiceFrameworkBase::ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info) {
+ IPC::Header header{cmd_buf[0]};
+ int num_params = header.normal_params_size + header.translate_params_size;
+ std::string function_name = info == nullptr ? fmt::format("{:#08x}", cmd_buf[0]) : info->name;
+
+ fmt::MemoryWriter w;
+ w.write("function '{}': port='{}' cmd_buf={{[0]={:#x}", function_name, service_name,
+ cmd_buf[0]);
+ for (int i = 1; i <= num_params; ++i) {
+ w.write(", [{}]={:#x}", i, cmd_buf[i]);
+ }
+ w << '}';
+
+ LOG_ERROR(Service, "unknown / unimplemented %s", w.c_str());
+ // TODO(bunnei): Hack - ignore error
+ cmd_buf[1] = 0;
+}
+
+void ServiceFrameworkBase::HandleSyncRequest(SharedPtr<ServerSession> server_session) {
+ u32* cmd_buf = Kernel::GetCommandBuffer();
+
+ u32 header_code = cmd_buf[0];
+ auto itr = handlers.find(header_code);
+ const FunctionInfoBase* info = itr == handlers.end() ? nullptr : &itr->second;
+ if (info == nullptr || info->handler_callback == nullptr) {
+ return ReportUnimplementedFunction(cmd_buf, info);
+ }
+
+ // TODO(yuriks): The kernel should be the one handling this as part of translation after
+ // everything else is migrated
+ Kernel::HLERequestContext context(std::move(server_session));
+ context.PopulateFromIncomingCommandBuffer(cmd_buf, *Kernel::g_current_process,
+ Kernel::g_handle_table);
+
+ LOG_TRACE(Service, "%s",
+ MakeFunctionString(info->name, GetServiceName().c_str(), cmd_buf).c_str());
+ handler_invoker(this, info->handler_callback, context);
+ context.WriteToOutgoingCommandBuffer(cmd_buf, *Kernel::g_current_process,
+ Kernel::g_handle_table);
+}
+
+////////////////////////////////////////////////////////////////////////////////////////////////////
// Module interface
+// TODO(yuriks): Move to kernel
+void AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
+ g_kernel_named_ports.emplace(std::move(name), std::move(port));
+}
+
static void AddNamedPort(Interface* interface_) {
- Kernel::SharedPtr<Kernel::ServerPort> server_port;
- Kernel::SharedPtr<Kernel::ClientPort> client_port;
+ SharedPtr<ServerPort> server_port;
+ SharedPtr<ClientPort> client_port;
std::tie(server_port, client_port) =
- Kernel::ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName());
+ ServerPort::CreatePortPair(interface_->GetMaxSessions(), interface_->GetPortName());
server_port->SetHleHandler(std::shared_ptr<Interface>(interface_));
- g_kernel_named_ports.emplace(interface_->GetPortName(), std::move(client_port));
+ AddNamedPort(interface_->GetPortName(), std::move(client_port));
}
void AddService(Interface* interface_) {
auto server_port =
SM::g_service_manager
->RegisterService(interface_->GetPortName(), interface_->GetMaxSessions())
- .MoveFrom();
+ .Unwrap();
server_port->SetHleHandler(std::shared_ptr<Interface>(interface_));
}
/// Initialize ServiceManager
void Init() {
- SM::g_service_manager = std::make_unique<SM::ServiceManager>();
- AddNamedPort(new SM::SRV);
+ SM::g_service_manager = std::make_shared<SM::ServiceManager>();
+ SM::ServiceManager::InstallInterfaces(SM::g_service_manager);
+
AddNamedPort(new ERR::ERR_F);
FS::ArchiveInit();
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 8933d57cc..281ff99bb 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -18,11 +18,16 @@
namespace Kernel {
class ClientPort;
+class ServerPort;
class ServerSession;
}
namespace Service {
+namespace SM {
+class ServiceManager;
+}
+
static const int kMaxPortSize = 8; ///< Maximum size of a port name (8 characters)
/// Arbitrary default number of maximum connections to an HLE service.
static const u32 DefaultMaxSessions = 10;
@@ -30,6 +35,9 @@ static const u32 DefaultMaxSessions = 10;
/**
* Framework for implementing HLE service handlers which dispatch incoming SyncRequests based on a
* table mapping header ids to handler functions.
+ *
+ * @deprecated Use ServiceFramework for new services instead. It allows services to be stateful and
+ * is more extensible going forward.
*/
class Interface : public Kernel::SessionRequestHandler {
public:
@@ -101,6 +109,146 @@ private:
boost::container::flat_map<u32, FunctionInfo> m_functions;
};
+/**
+ * This is an non-templated base of ServiceFramework to reduce code bloat and compilation times, it
+ * is not meant to be used directly.
+ *
+ * @see ServiceFramework
+ */
+class ServiceFrameworkBase : public Kernel::SessionRequestHandler {
+public:
+ /// Returns the string identifier used to connect to the service.
+ std::string GetServiceName() const {
+ return service_name;
+ }
+
+ /**
+ * Returns the maximum number of sessions that can be connected to this service at the same
+ * time.
+ */
+ u32 GetMaxSessions() const {
+ return max_sessions;
+ }
+
+ /// Creates a port pair and registers this service with the given ServiceManager.
+ void InstallAsService(SM::ServiceManager& service_manager);
+ /// Creates a port pair and registers it on the kernel's global port registry.
+ void InstallAsNamedPort();
+
+ void HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> server_session) override;
+
+protected:
+ /// Member-function pointer type of SyncRequest handlers.
+ template <typename Self>
+ using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&);
+
+private:
+ template <typename T>
+ friend class ServiceFramework;
+
+ struct FunctionInfoBase {
+ u32 expected_header;
+ HandlerFnP<ServiceFrameworkBase> handler_callback;
+ const char* name;
+ };
+
+ using InvokerFn = void(ServiceFrameworkBase* object, HandlerFnP<ServiceFrameworkBase> member,
+ Kernel::HLERequestContext& ctx);
+
+ ServiceFrameworkBase(const char* service_name, u32 max_sessions, InvokerFn* handler_invoker);
+ ~ServiceFrameworkBase();
+
+ void RegisterHandlersBase(const FunctionInfoBase* functions, size_t n);
+ void ReportUnimplementedFunction(u32* cmd_buf, const FunctionInfoBase* info);
+
+ /// Identifier string used to connect to the service.
+ std::string service_name;
+ /// Maximum number of concurrent sessions that this service can handle.
+ u32 max_sessions;
+
+ /**
+ * Port where incoming connections will be received. Only created when InstallAsService() or
+ * InstallAsNamedPort() are called.
+ */
+ Kernel::SharedPtr<Kernel::ServerPort> port;
+
+ /// Function used to safely up-cast pointers to the derived class before invoking a handler.
+ InvokerFn* handler_invoker;
+ boost::container::flat_map<u32, FunctionInfoBase> handlers;
+};
+
+/**
+ * Framework for implementing HLE services. Dispatches on the header id of incoming SyncRequests
+ * based on a table mapping header ids to handler functions. Service implementations should inherit
+ * from ServiceFramework using the CRTP (`class Foo : public ServiceFramework<Foo> { ... };`) and
+ * populate it with handlers by calling #RegisterHandlers.
+ *
+ * In order to avoid duplicating code in the binary and exposing too many implementation details in
+ * the header, this class is split into a non-templated base (ServiceFrameworkBase) and a template
+ * deriving from it (ServiceFramework). The functions in this class will mostly only erase the type
+ * of the passed in function pointers and then delegate the actual work to the implementation in the
+ * base class.
+ */
+template <typename Self>
+class ServiceFramework : public ServiceFrameworkBase {
+protected:
+ /// Contains information about a request type which is handled by the service.
+ struct FunctionInfo : FunctionInfoBase {
+ // TODO(yuriks): This function could be constexpr, but clang is the only compiler that
+ // doesn't emit an ICE or a wrong diagnostic because of the static_cast.
+
+ /**
+ * Constructs a FunctionInfo for a function.
+ *
+ * @param expected_header request header in the command buffer which will trigger dispatch
+ * to this handler
+ * @param handler_callback member function in this service which will be called to handle
+ * the request
+ * @param name human-friendly name for the request. Used mostly for logging purposes.
+ */
+ FunctionInfo(u32 expected_header, HandlerFnP<Self> handler_callback, const char* name)
+ : FunctionInfoBase{
+ expected_header,
+ // Type-erase member function pointer by casting it down to the base class.
+ static_cast<HandlerFnP<ServiceFrameworkBase>>(handler_callback), name} {}
+ };
+
+ /**
+ * Initializes the handler with no functions installed.
+ * @param max_sessions Maximum number of sessions that can be
+ * connected to this service at the same time.
+ */
+ ServiceFramework(const char* service_name, u32 max_sessions = DefaultMaxSessions)
+ : ServiceFrameworkBase(service_name, max_sessions, Invoker) {}
+
+ /// Registers handlers in the service.
+ template <size_t N>
+ void RegisterHandlers(const FunctionInfo (&functions)[N]) {
+ RegisterHandlers(functions, N);
+ }
+
+ /**
+ * Registers handlers in the service. Usually prefer using the other RegisterHandlers
+ * overload in order to avoid needing to specify the array size.
+ */
+ void RegisterHandlers(const FunctionInfo* functions, size_t n) {
+ RegisterHandlersBase(functions, n);
+ }
+
+private:
+ /**
+ * This function is used to allow invocation of pointers to handlers stored in the base class
+ * without needing to expose the type of this derived class. Pointers-to-member may require a
+ * fixup when being up or downcast, and thus code that does that needs to know the concrete type
+ * of the derived class in order to invoke one of it's functions through a pointer.
+ */
+ static void Invoker(ServiceFrameworkBase* object, HandlerFnP<ServiceFrameworkBase> member,
+ Kernel::HLERequestContext& ctx) {
+ // Cast back up to our original types and call the member function
+ (static_cast<Self*>(object)->*static_cast<HandlerFnP<Self>>(member))(ctx);
+ }
+};
+
/// Initialize ServiceManager
void Init();
@@ -110,6 +258,8 @@ void Shutdown();
/// Map of named ports managed by the kernel, which can be retrieved using the ConnectToPort SVC.
extern std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> g_kernel_named_ports;
+/// Adds a port to the named port table
+void AddNamedPort(std::string name, Kernel::SharedPtr<Kernel::ClientPort> port);
/// Adds a service to the services table
void AddService(Interface* interface_);
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 361f7a0a9..5e7fc68f9 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -3,11 +3,13 @@
// Refer to the license.txt file included.
#include <tuple>
+#include "common/assert.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/result.h"
#include "core/hle/service/sm/sm.h"
+#include "core/hle/service/sm/srv.h"
namespace Service {
namespace SM {
@@ -22,6 +24,14 @@ static ResultCode ValidateServiceName(const std::string& name) {
return RESULT_SUCCESS;
}
+void ServiceManager::InstallInterfaces(std::shared_ptr<ServiceManager> self) {
+ ASSERT(self->srv_interface.expired());
+
+ auto srv = std::make_shared<SRV>(self);
+ srv->InstallAsNamedPort();
+ self->srv_interface = srv;
+}
+
ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService(
std::string name, unsigned int max_sessions) {
@@ -30,7 +40,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService
Kernel::SharedPtr<Kernel::ClientPort> client_port;
std::tie(server_port, client_port) = Kernel::ServerPort::CreatePortPair(max_sessions, name);
- registered_services.emplace(name, std::move(client_port));
+ registered_services.emplace(std::move(name), std::move(client_port));
return MakeResult<Kernel::SharedPtr<Kernel::ServerPort>>(std::move(server_port));
}
@@ -53,7 +63,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToSer
return client_port->Connect();
}
-std::unique_ptr<ServiceManager> g_service_manager;
+std::shared_ptr<ServiceManager> g_service_manager;
} // namespace SM
} // namespace Service
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 5fac5455c..8f0dbf2db 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -20,6 +20,8 @@ class SessionRequestHandler;
namespace Service {
namespace SM {
+class SRV;
+
constexpr ResultCode ERR_SERVICE_NOT_REGISTERED(1, ErrorModule::SRV, ErrorSummary::WouldBlock,
ErrorLevel::Temporary); // 0xD0406401
constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED(2, ErrorModule::SRV, ErrorSummary::WouldBlock,
@@ -33,17 +35,21 @@ constexpr ResultCode ERR_NAME_CONTAINS_NUL(7, ErrorModule::SRV, ErrorSummary::Wr
class ServiceManager {
public:
+ static void InstallInterfaces(std::shared_ptr<ServiceManager> self);
+
ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name,
unsigned int max_sessions);
ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name);
ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ConnectToService(const std::string& name);
private:
- /// Map of services registered with the "srv:" service, retrieved using GetServiceHandle.
+ std::weak_ptr<SRV> srv_interface;
+
+ /// Map of registered services, retrieved using GetServicePort or ConnectToService.
std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> registered_services;
};
-extern std::unique_ptr<ServiceManager> g_service_manager;
+extern std::shared_ptr<ServiceManager> g_service_manager;
} // namespace SM
} // namespace Service
diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp
index 063b1b0fc..352941e69 100644
--- a/src/core/hle/service/sm/srv.cpp
+++ b/src/core/hle/service/sm/srv.cpp
@@ -7,9 +7,11 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/hle/ipc.h"
+#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/client_session.h"
-#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/errors.h"
+#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/semaphore.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/service/sm/sm.h"
@@ -20,8 +22,6 @@ namespace SM {
constexpr int MAX_PENDING_NOTIFICATIONS = 16;
-static Kernel::SharedPtr<Kernel::Semaphore> notification_semaphore;
-
/**
* SRV::RegisterClient service function
* Inputs:
@@ -31,16 +31,19 @@ static Kernel::SharedPtr<Kernel::Semaphore> notification_semaphore;
* 0: 0x00010040
* 1: ResultCode
*/
-static void RegisterClient(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
+void SRV::RegisterClient(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0x1, 0, 2);
- if (cmd_buff[1] != IPC::CallingPidDesc()) {
- cmd_buff[0] = IPC::MakeHeader(0x0, 0x1, 0); // 0x40
- cmd_buff[1] = IPC::ERR_INVALID_BUFFER_DESCRIPTOR.raw;
+ u32 pid_descriptor = rp.Pop<u32>();
+ if (pid_descriptor != IPC::CallingPidDesc()) {
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(IPC::ERR_INVALID_BUFFER_DESCRIPTOR);
return;
}
- cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0); // 0x10040
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ u32 caller_pid = rp.Pop<u32>();
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_SRV, "(STUBBED) called");
}
@@ -54,16 +57,15 @@ static void RegisterClient(Interface* self) {
* 2: Translation descriptor: 0x20
* 3: Handle to semaphore signaled on process notification
*/
-static void EnableNotification(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
+void SRV::EnableNotification(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0x2, 0, 0);
notification_semaphore =
Kernel::Semaphore::Create(0, MAX_PENDING_NOTIFICATIONS, "SRV:Notification").Unwrap();
- cmd_buff[0] = IPC::MakeHeader(0x2, 0x1, 0x2); // 0x20042
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[2] = IPC::CopyHandleDesc(1);
- cmd_buff[3] = Kernel::g_handle_table.Create(notification_semaphore).MoveFrom();
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(RESULT_SUCCESS);
+ rb.PushObjects(notification_semaphore);
LOG_WARNING(Service_SRV, "(STUBBED) called");
}
@@ -78,44 +80,50 @@ static void EnableNotification(Interface* self) {
* 1: ResultCode
* 3: Service handle
*/
-static void GetServiceHandle(Interface* self) {
- ResultCode res = RESULT_SUCCESS;
- u32* cmd_buff = Kernel::GetCommandBuffer();
+void SRV::GetServiceHandle(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0x5, 4, 0);
+ auto name_buf = rp.PopRaw<std::array<char, 8>>();
+ size_t name_len = rp.Pop<u32>();
+ u32 flags = rp.Pop<u32>();
+
+ bool return_port_on_failure = (flags & 1) == 0;
- size_t name_len = cmd_buff[3];
if (name_len > Service::kMaxPortSize) {
- cmd_buff[1] = ERR_INVALID_NAME_SIZE.raw;
- LOG_ERROR(Service_SRV, "called name_len=0x%X, failed with code=0x%08X", name_len,
- cmd_buff[1]);
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(ERR_INVALID_NAME_SIZE);
+ LOG_ERROR(Service_SRV, "called name_len=0x%X -> ERR_INVALID_NAME_SIZE", name_len);
return;
}
- std::string name(reinterpret_cast<const char*>(&cmd_buff[1]), name_len);
- bool return_port_on_failure = (cmd_buff[4] & 1) == 0;
+ std::string name(name_buf.data(), name_len);
// TODO(yuriks): Permission checks go here
- auto client_port = g_service_manager->GetServicePort(name);
+ auto client_port = service_manager->GetServicePort(name);
if (client_port.Failed()) {
- cmd_buff[1] = client_port.Code().raw;
- LOG_ERROR(Service_SRV, "called service=%s, failed with code=0x%08X", name.c_str(),
- cmd_buff[1]);
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(client_port.Code());
+ LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(),
+ client_port.Code().raw);
return;
}
auto session = client_port.Unwrap()->Connect();
- cmd_buff[1] = session.Code().raw;
if (session.Succeeded()) {
- cmd_buff[3] = Kernel::g_handle_table.Create(session.MoveFrom()).MoveFrom();
- LOG_DEBUG(Service_SRV, "called service=%s, session handle=0x%08X", name.c_str(),
- cmd_buff[3]);
+ LOG_DEBUG(Service_SRV, "called service=%s -> session=%u", name.c_str(),
+ (*session)->GetObjectId());
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(session.Code());
+ rb.PushObjects(std::move(session).Unwrap());
} else if (session.Code() == Kernel::ERR_MAX_CONNECTIONS_REACHED && return_port_on_failure) {
- cmd_buff[1] = ERR_MAX_CONNECTIONS_REACHED.raw;
- cmd_buff[3] = Kernel::g_handle_table.Create(client_port.MoveFrom()).MoveFrom();
- LOG_WARNING(Service_SRV, "called service=%s, *port* handle=0x%08X", name.c_str(),
- cmd_buff[3]);
+ LOG_WARNING(Service_SRV, "called service=%s -> ERR_MAX_CONNECTIONS_REACHED, *port*=%u",
+ name.c_str(), (*client_port)->GetObjectId());
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(ERR_MAX_CONNECTIONS_REACHED);
+ rb.PushObjects(std::move(client_port).Unwrap());
} else {
- LOG_ERROR(Service_SRV, "called service=%s, failed with code=0x%08X", name.c_str(),
- cmd_buff[1]);
+ LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(), session.Code());
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(session.Code());
}
}
@@ -128,13 +136,12 @@ static void GetServiceHandle(Interface* self) {
* 0: 0x00090040
* 1: ResultCode
*/
-static void Subscribe(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- u32 notification_id = cmd_buff[1];
+void SRV::Subscribe(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0x9, 1, 0);
+ u32 notification_id = rp.Pop<u32>();
- cmd_buff[0] = IPC::MakeHeader(0x9, 0x1, 0); // 0x90040
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id);
}
@@ -147,13 +154,12 @@ static void Subscribe(Interface* self) {
* 0: 0x000A0040
* 1: ResultCode
*/
-static void Unsubscribe(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
+void SRV::Unsubscribe(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0xA, 1, 0);
+ u32 notification_id = rp.Pop<u32>();
- u32 notification_id = cmd_buff[1];
-
- cmd_buff[0] = IPC::MakeHeader(0xA, 0x1, 0); // 0xA0040
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X", notification_id);
}
@@ -167,43 +173,39 @@ static void Unsubscribe(Interface* self) {
* 0: 0x000C0040
* 1: ResultCode
*/
-static void PublishToSubscriber(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- u32 notification_id = cmd_buff[1];
- u8 flags = cmd_buff[2] & 0xFF;
+void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0xC, 2, 0);
+ u32 notification_id = rp.Pop<u32>();
+ u8 flags = rp.Pop<u8>();
- cmd_buff[0] = IPC::MakeHeader(0xC, 0x1, 0); // 0xC0040
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(RESULT_SUCCESS);
LOG_WARNING(Service_SRV, "(STUBBED) called, notification_id=0x%X, flags=%u", notification_id,
flags);
}
-const Interface::FunctionInfo FunctionTable[] = {
- {0x00010002, RegisterClient, "RegisterClient"},
- {0x00020000, EnableNotification, "EnableNotification"},
- {0x00030100, nullptr, "RegisterService"},
- {0x000400C0, nullptr, "UnregisterService"},
- {0x00050100, GetServiceHandle, "GetServiceHandle"},
- {0x000600C2, nullptr, "RegisterPort"},
- {0x000700C0, nullptr, "UnregisterPort"},
- {0x00080100, nullptr, "GetPort"},
- {0x00090040, Subscribe, "Subscribe"},
- {0x000A0040, Unsubscribe, "Unsubscribe"},
- {0x000B0000, nullptr, "ReceiveNotification"},
- {0x000C0080, PublishToSubscriber, "PublishToSubscriber"},
- {0x000D0040, nullptr, "PublishAndGetSubscriber"},
- {0x000E00C0, nullptr, "IsServiceRegistered"},
-};
-
-SRV::SRV() {
- Register(FunctionTable);
- notification_semaphore = nullptr;
+SRV::SRV(std::shared_ptr<ServiceManager> service_manager)
+ : ServiceFramework("srv:", 4), service_manager(std::move(service_manager)) {
+ static const FunctionInfo functions[] = {
+ {0x00010002, &SRV::RegisterClient, "RegisterClient"},
+ {0x00020000, &SRV::EnableNotification, "EnableNotification"},
+ {0x00030100, nullptr, "RegisterService"},
+ {0x000400C0, nullptr, "UnregisterService"},
+ {0x00050100, &SRV::GetServiceHandle, "GetServiceHandle"},
+ {0x000600C2, nullptr, "RegisterPort"},
+ {0x000700C0, nullptr, "UnregisterPort"},
+ {0x00080100, nullptr, "GetPort"},
+ {0x00090040, &SRV::Subscribe, "Subscribe"},
+ {0x000A0040, &SRV::Unsubscribe, "Unsubscribe"},
+ {0x000B0000, nullptr, "ReceiveNotification"},
+ {0x000C0080, &SRV::PublishToSubscriber, "PublishToSubscriber"},
+ {0x000D0040, nullptr, "PublishAndGetSubscriber"},
+ {0x000E00C0, nullptr, "IsServiceRegistered"},
+ };
+ RegisterHandlers(functions);
}
-SRV::~SRV() {
- notification_semaphore = nullptr;
-}
+SRV::~SRV() = default;
} // namespace SM
} // namespace Service
diff --git a/src/core/hle/service/sm/srv.h b/src/core/hle/service/sm/srv.h
index 4196ca1e2..75cca5184 100644
--- a/src/core/hle/service/sm/srv.h
+++ b/src/core/hle/service/sm/srv.h
@@ -4,21 +4,33 @@
#pragma once
-#include <string>
+#include "core/hle/kernel/kernel.h"
#include "core/hle/service/service.h"
+namespace Kernel {
+class HLERequestContext;
+class Semaphore;
+}
+
namespace Service {
namespace SM {
/// Interface to "srv:" service
-class SRV final : public Interface {
+class SRV final : public ServiceFramework<SRV> {
public:
- SRV();
- ~SRV() override;
+ explicit SRV(std::shared_ptr<ServiceManager> service_manager);
+ ~SRV();
+
+private:
+ void RegisterClient(Kernel::HLERequestContext& ctx);
+ void EnableNotification(Kernel::HLERequestContext& ctx);
+ void GetServiceHandle(Kernel::HLERequestContext& ctx);
+ void Subscribe(Kernel::HLERequestContext& ctx);
+ void Unsubscribe(Kernel::HLERequestContext& ctx);
+ void PublishToSubscriber(Kernel::HLERequestContext& ctx);
- std::string GetPortName() const override {
- return "srv:";
- }
+ std::shared_ptr<ServiceManager> service_manager;
+ Kernel::SharedPtr<Kernel::Semaphore> notification_semaphore;
};
} // namespace SM
diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp
index bb7bf2d67..57172ddd6 100644
--- a/src/core/hle/service/y2r_u.cpp
+++ b/src/core/hle/service/y2r_u.cpp
@@ -275,7 +275,7 @@ static void GetTransferEndEvent(Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom();
+ cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).Unwrap();
LOG_DEBUG(Service_Y2R, "called");
}
@@ -587,8 +587,8 @@ static void StartConversion(Interface* self) {
// dst_image_size would seem to be perfect for this, but it doesn't include the gap :(
u32 total_output_size =
conversion.input_lines * (conversion.dst.transfer_unit + conversion.dst.gap);
- Memory::RasterizerFlushAndInvalidateRegion(
- Memory::VirtualToPhysicalAddress(conversion.dst.address), total_output_size);
+ Memory::RasterizerFlushVirtualRegion(conversion.dst.address, total_output_size,
+ Memory::FlushMode::FlushAndInvalidate);
HW::Y2R::PerformConversion(conversion);
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index e68b9f16a..e4b803046 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -25,6 +25,7 @@
#include "core/hle/kernel/semaphore.h"
#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
+#include "core/hle/kernel/session.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/timer.h"
@@ -36,8 +37,9 @@
////////////////////////////////////////////////////////////////////////////////////////////////////
// Namespace SVC
-using Kernel::SharedPtr;
using Kernel::ERR_INVALID_HANDLE;
+using Kernel::Handle;
+using Kernel::SharedPtr;
namespace SVC {
@@ -236,7 +238,7 @@ static ResultCode SendSyncRequest(Kernel::Handle handle) {
// TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server
// responds and cause a reschedule.
- return session->SendSyncRequest();
+ return session->SendSyncRequest(Kernel::GetCurrentThread());
}
/// Close a handle
@@ -397,6 +399,112 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha
}
}
+/// In a single operation, sends a IPC reply and waits for a new request.
+static ResultCode ReplyAndReceive(s32* index, Kernel::Handle* handles, s32 handle_count,
+ Kernel::Handle reply_target) {
+ // 'handles' has to be a valid pointer even if 'handle_count' is 0.
+ if (handles == nullptr)
+ return Kernel::ERR_INVALID_POINTER;
+
+ // Check if 'handle_count' is invalid
+ if (handle_count < 0)
+ return Kernel::ERR_OUT_OF_RANGE;
+
+ using ObjectPtr = SharedPtr<Kernel::WaitObject>;
+ std::vector<ObjectPtr> objects(handle_count);
+
+ for (int i = 0; i < handle_count; ++i) {
+ auto object = Kernel::g_handle_table.Get<Kernel::WaitObject>(handles[i]);
+ if (object == nullptr)
+ return ERR_INVALID_HANDLE;
+ objects[i] = object;
+ }
+
+ // We are also sending a command reply.
+ // Do not send a reply if the command id in the command buffer is 0xFFFF.
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ IPC::Header header{cmd_buff[0]};
+ if (reply_target != 0 && header.command_id != 0xFFFF) {
+ auto session = Kernel::g_handle_table.Get<Kernel::ServerSession>(reply_target);
+ if (session == nullptr)
+ return ERR_INVALID_HANDLE;
+
+ auto request_thread = std::move(session->currently_handling);
+
+ // Mark the request as "handled".
+ session->currently_handling = nullptr;
+
+ // Error out if there's no request thread or the session was closed.
+ // TODO(Subv): Is the same error code (ClosedByRemote) returned for both of these cases?
+ if (request_thread == nullptr || session->parent->client == nullptr) {
+ *index = -1;
+ return Kernel::ERR_SESSION_CLOSED_BY_REMOTE;
+ }
+
+ // TODO(Subv): Perform IPC translation from the current thread to request_thread.
+
+ // Note: The scheduler is not invoked here.
+ request_thread->ResumeFromWait();
+ }
+
+ if (handle_count == 0) {
+ *index = 0;
+ // The kernel uses this value as a placeholder for the real error, and returns it when we
+ // pass no handles and do not perform any reply.
+ if (reply_target == 0 || header.command_id == 0xFFFF)
+ return ResultCode(0xE7E3FFFF);
+
+ return RESULT_SUCCESS;
+ }
+
+ auto thread = Kernel::GetCurrentThread();
+
+ // Find the first object that is acquirable in the provided list of objects
+ auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) {
+ return !object->ShouldWait(thread);
+ });
+
+ if (itr != objects.end()) {
+ // We found a ready object, acquire it and set the result value
+ Kernel::WaitObject* object = itr->get();
+ object->Acquire(thread);
+ *index = std::distance(objects.begin(), itr);
+
+ if (object->GetHandleType() == Kernel::HandleType::ServerSession) {
+ auto server_session = static_cast<Kernel::ServerSession*>(object);
+ if (server_session->parent->client == nullptr)
+ return Kernel::ERR_SESSION_CLOSED_BY_REMOTE;
+
+ // TODO(Subv): Perform IPC translation from the ServerSession to the current thread.
+ }
+ return RESULT_SUCCESS;
+ }
+
+ // No objects were ready to be acquired, prepare to suspend the thread.
+
+ // TODO(Subv): Perform IPC translation upon wakeup.
+
+ // Put the thread to sleep
+ thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
+
+ // Add the thread to each of the objects' waiting threads.
+ for (size_t i = 0; i < objects.size(); ++i) {
+ Kernel::WaitObject* object = objects[i].get();
+ object->AddWaitingThread(thread);
+ }
+
+ thread->wait_objects = std::move(objects);
+
+ Core::System::GetInstance().PrepareReschedule();
+
+ // Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a
+ // signal in one of its wait objects, or to 0xC8A01836 if there was a translation error.
+ // By default the index is set to -1.
+ thread->wait_set_output = true;
+ *index = -1;
+ return RESULT_SUCCESS;
+}
+
/// Create an address arbiter (to allocate access to shared resources)
static ResultCode CreateAddressArbiter(Kernel::Handle* out_handle) {
using Kernel::AddressArbiter;
@@ -933,7 +1041,6 @@ static ResultCode CreatePort(Kernel::Handle* server_port, Kernel::Handle* client
using Kernel::ServerPort;
using Kernel::ClientPort;
- using Kernel::SharedPtr;
auto ports = ServerPort::CreatePortPair(max_sessions);
CASCADE_RESULT(*client_port, Kernel::g_handle_table.Create(
@@ -947,6 +1054,41 @@ static ResultCode CreatePort(Kernel::Handle* server_port, Kernel::Handle* client
return RESULT_SUCCESS;
}
+static ResultCode CreateSessionToPort(Handle* out_client_session, Handle client_port_handle) {
+ using Kernel::ClientPort;
+ SharedPtr<ClientPort> client_port = Kernel::g_handle_table.Get<ClientPort>(client_port_handle);
+ if (client_port == nullptr)
+ return ERR_INVALID_HANDLE;
+
+ CASCADE_RESULT(auto session, client_port->Connect());
+ CASCADE_RESULT(*out_client_session, Kernel::g_handle_table.Create(std::move(session)));
+ return RESULT_SUCCESS;
+}
+
+static ResultCode CreateSession(Handle* server_session, Handle* client_session) {
+ auto sessions = Kernel::ServerSession::CreateSessionPair();
+
+ auto& server = std::get<SharedPtr<Kernel::ServerSession>>(sessions);
+ CASCADE_RESULT(*server_session, Kernel::g_handle_table.Create(std::move(server)));
+
+ auto& client = std::get<SharedPtr<Kernel::ClientSession>>(sessions);
+ CASCADE_RESULT(*client_session, Kernel::g_handle_table.Create(std::move(client)));
+
+ LOG_TRACE(Kernel_SVC, "called");
+ return RESULT_SUCCESS;
+}
+
+static ResultCode AcceptSession(Handle* out_server_session, Handle server_port_handle) {
+ using Kernel::ServerPort;
+ SharedPtr<ServerPort> server_port = Kernel::g_handle_table.Get<ServerPort>(server_port_handle);
+ if (server_port == nullptr)
+ return ERR_INVALID_HANDLE;
+
+ CASCADE_RESULT(auto session, server_port->Accept());
+ CASCADE_RESULT(*out_server_session, Kernel::g_handle_table.Create(std::move(session)));
+ return RESULT_SUCCESS;
+}
+
static ResultCode GetSystemInfo(s64* out, u32 type, s32 param) {
using Kernel::MemoryRegion;
@@ -1121,14 +1263,14 @@ static const FunctionDef SVC_Table[] = {
{0x45, nullptr, "Unknown"},
{0x46, nullptr, "Unknown"},
{0x47, HLE::Wrap<CreatePort>, "CreatePort"},
- {0x48, nullptr, "CreateSessionToPort"},
- {0x49, nullptr, "CreateSession"},
- {0x4A, nullptr, "AcceptSession"},
+ {0x48, HLE::Wrap<CreateSessionToPort>, "CreateSessionToPort"},
+ {0x49, HLE::Wrap<CreateSession>, "CreateSession"},
+ {0x4A, HLE::Wrap<AcceptSession>, "AcceptSession"},
{0x4B, nullptr, "ReplyAndReceive1"},
{0x4C, nullptr, "ReplyAndReceive2"},
{0x4D, nullptr, "ReplyAndReceive3"},
{0x4E, nullptr, "ReplyAndReceive4"},
- {0x4F, nullptr, "ReplyAndReceive"},
+ {0x4F, HLE::Wrap<ReplyAndReceive>, "ReplyAndReceive"},
{0x50, nullptr, "BindInterrupt"},
{0x51, nullptr, "UnbindInterrupt"},
{0x52, nullptr, "InvalidateProcessDataCache"},
diff --git a/src/core/hw/aes/key.h b/src/core/hw/aes/key.h
index b01d04f13..c9f1342f4 100644
--- a/src/core/hw/aes/key.h
+++ b/src/core/hw/aes/key.h
@@ -12,6 +12,8 @@ namespace HW {
namespace AES {
enum KeySlotID : size_t {
+ // AES Keyslot used to generate the UDS data frame CCMP key.
+ UDSDataKey = 0x2D,
APTWrap = 0x31,
MaxKeySlotID = 0x40,
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index 42809c731..6838e449c 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -5,6 +5,7 @@
#include <cstring>
#include <numeric>
#include <type_traits>
+#include "common/alignment.h"
#include "common/color.h"
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -313,7 +314,7 @@ static void TextureCopy(const Regs::DisplayTransferConfig& config) {
const PAddr src_addr = config.GetPhysicalInputAddress();
const PAddr dst_addr = config.GetPhysicalOutputAddress();
- // TODO: do hwtest with these cases
+ // TODO: do hwtest with invalid addresses
if (!Memory::IsValidPhysicalAddress(src_addr)) {
LOG_CRITICAL(HW_GPU, "invalid input address 0x%08X", src_addr);
return;
@@ -324,31 +325,36 @@ static void TextureCopy(const Regs::DisplayTransferConfig& config) {
return;
}
- if (config.texture_copy.input_width == 0) {
- LOG_CRITICAL(HW_GPU, "zero input width");
+ if (VideoCore::g_renderer->Rasterizer()->AccelerateTextureCopy(config))
return;
- }
- if (config.texture_copy.output_width == 0) {
- LOG_CRITICAL(HW_GPU, "zero output width");
+ u8* src_pointer = Memory::GetPhysicalPointer(src_addr);
+ u8* dst_pointer = Memory::GetPhysicalPointer(dst_addr);
+
+ u32 remaining_size = Common::AlignDown(config.texture_copy.size, 16);
+
+ if (remaining_size == 0) {
+ LOG_CRITICAL(HW_GPU, "zero size. Real hardware freezes on this.");
return;
}
- if (config.texture_copy.size == 0) {
- LOG_CRITICAL(HW_GPU, "zero size");
+ u32 input_gap = config.texture_copy.input_gap * 16;
+ u32 output_gap = config.texture_copy.output_gap * 16;
+
+ // Zero gap means contiguous input/output even if width = 0. To avoid infinite loop below, width
+ // is assigned with the total size if gap = 0.
+ u32 input_width = input_gap == 0 ? remaining_size : config.texture_copy.input_width * 16;
+ u32 output_width = output_gap == 0 ? remaining_size : config.texture_copy.output_width * 16;
+
+ if (input_width == 0) {
+ LOG_CRITICAL(HW_GPU, "zero input width. Real hardware freezes on this.");
return;
}
- if (VideoCore::g_renderer->Rasterizer()->AccelerateTextureCopy(config))
+ if (output_width == 0) {
+ LOG_CRITICAL(HW_GPU, "zero output width. Real hardware freezes on this.");
return;
-
- u8* src_pointer = Memory::GetPhysicalPointer(src_addr);
- u8* dst_pointer = Memory::GetPhysicalPointer(dst_addr);
-
- u32 input_width = config.texture_copy.input_width * 16;
- u32 input_gap = config.texture_copy.input_gap * 16;
- u32 output_width = config.texture_copy.output_width * 16;
- u32 output_gap = config.texture_copy.output_gap * 16;
+ }
size_t contiguous_input_size =
config.texture_copy.size / input_width * (input_width + input_gap);
@@ -360,7 +366,6 @@ static void TextureCopy(const Regs::DisplayTransferConfig& config) {
Memory::RasterizerFlushAndInvalidateRegion(config.GetPhysicalOutputAddress(),
static_cast<u32>(contiguous_output_size));
- u32 remaining_size = config.texture_copy.size;
u32 remaining_input = input_width;
u32 remaining_output = output_width;
while (remaining_size > 0) {
diff --git a/src/core/hw/gpu.h b/src/core/hw/gpu.h
index bdd997b2a..21b127fee 100644
--- a/src/core/hw/gpu.h
+++ b/src/core/hw/gpu.h
@@ -225,7 +225,7 @@ struct Regs {
INSERT_PADDING_WORDS(0x1);
struct {
- u32 size;
+ u32 size; // The lower 4 bits are ignored
union {
u32 input_size;
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index ffc019560..fc4d14a59 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -342,9 +342,11 @@ ResultStatus AppLoader_NCCH::Load() {
if (result != ResultStatus::Success)
return result;
- LOG_INFO(Loader, "Program ID: %016" PRIX64, ncch_header.program_id);
+ std::string program_id{Common::StringFromFormat("%016" PRIX64, ncch_header.program_id)};
- Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", ncch_header.program_id);
+ LOG_INFO(Loader, "Program ID: %s", program_id.c_str());
+
+ Core::Telemetry().AddField(Telemetry::FieldType::Session, "ProgramId", program_id);
is_loaded = true; // Set state to loaded
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index b8438e490..65649d9d7 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -83,19 +83,13 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) {
LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE,
(base + size) * PAGE_SIZE);
- u32 end = base + size;
+ RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE,
+ FlushMode::FlushAndInvalidate);
+ u32 end = base + size;
while (base != end) {
ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base);
- // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
- // null here
- if (current_page_table->attributes[base] == PageType::RasterizerCachedMemory ||
- current_page_table->attributes[base] == PageType::RasterizerCachedSpecial) {
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(base << PAGE_BITS),
- PAGE_SIZE);
- }
-
current_page_table->attributes[base] = type;
current_page_table->pointers[base] = memory;
current_page_table->cached_res_count[base] = 0;
@@ -139,7 +133,12 @@ void UnmapRegion(VAddr base, u32 size) {
static u8* GetPointerFromVMA(VAddr vaddr) {
u8* direct_pointer = nullptr;
- auto& vma = Kernel::g_current_process->vm_manager.FindVMA(vaddr)->second;
+ auto& vm_manager = Kernel::g_current_process->vm_manager;
+
+ auto it = vm_manager.FindVMA(vaddr);
+ ASSERT(it != vm_manager.vma_map.end());
+
+ auto& vma = it->second;
switch (vma.type) {
case Kernel::VMAType::AllocatedMemoryBlock:
direct_pointer = vma.backing_block->data() + vma.offset;
@@ -147,6 +146,8 @@ static u8* GetPointerFromVMA(VAddr vaddr) {
case Kernel::VMAType::BackingMemory:
direct_pointer = vma.backing_memory;
break;
+ case Kernel::VMAType::Free:
+ return nullptr;
default:
UNREACHABLE();
}
@@ -189,7 +190,7 @@ T Read(const VAddr vaddr) {
ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
break;
case PageType::RasterizerCachedMemory: {
- RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
+ RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
T value;
std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
@@ -198,8 +199,7 @@ T Read(const VAddr vaddr) {
case PageType::Special:
return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr);
case PageType::RasterizerCachedSpecial: {
- RasterizerFlushRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
-
+ RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
return ReadMMIO<T>(GetMMIOHandler(vaddr), vaddr);
}
default:
@@ -229,8 +229,7 @@ void Write(const VAddr vaddr, const T data) {
ASSERT_MSG(false, "Mapped memory page without a pointer @ %08X", vaddr);
break;
case PageType::RasterizerCachedMemory: {
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
-
+ RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate);
std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T));
break;
}
@@ -238,8 +237,7 @@ void Write(const VAddr vaddr, const T data) {
WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data);
break;
case PageType::RasterizerCachedSpecial: {
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(vaddr), sizeof(T));
-
+ RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::FlushAndInvalidate);
WriteMMIO<T>(GetMMIOHandler(vaddr), vaddr, data);
break;
}
@@ -268,7 +266,8 @@ bool IsValidVirtualAddress(const VAddr vaddr) {
}
bool IsValidPhysicalAddress(const PAddr paddr) {
- return IsValidVirtualAddress(PhysicalToVirtualAddress(paddr));
+ boost::optional<VAddr> vaddr = PhysicalToVirtualAddress(paddr);
+ return vaddr && IsValidVirtualAddress(*vaddr);
}
u8* GetPointer(const VAddr vaddr) {
@@ -301,7 +300,8 @@ std::string ReadCString(VAddr vaddr, std::size_t max_length) {
u8* GetPhysicalPointer(PAddr address) {
// TODO(Subv): This call should not go through the application's memory mapping.
- return GetPointer(PhysicalToVirtualAddress(address));
+ boost::optional<VAddr> vaddr = PhysicalToVirtualAddress(address);
+ return vaddr ? GetPointer(*vaddr) : nullptr;
}
void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) {
@@ -312,8 +312,12 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) {
u32 num_pages = ((start + size - 1) >> PAGE_BITS) - (start >> PAGE_BITS) + 1;
PAddr paddr = start;
- for (unsigned i = 0; i < num_pages; ++i) {
- VAddr vaddr = PhysicalToVirtualAddress(paddr);
+ for (unsigned i = 0; i < num_pages; ++i, paddr += PAGE_SIZE) {
+ boost::optional<VAddr> maybe_vaddr = PhysicalToVirtualAddress(paddr);
+ if (!maybe_vaddr)
+ continue;
+ VAddr vaddr = *maybe_vaddr;
+
u8& res_count = current_page_table->cached_res_count[vaddr >> PAGE_BITS];
ASSERT_MSG(count_delta <= UINT8_MAX - res_count,
"Rasterizer resource cache counter overflow!");
@@ -341,11 +345,19 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) {
if (res_count == 0) {
PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
switch (page_type) {
- case PageType::RasterizerCachedMemory:
- page_type = PageType::Memory;
- current_page_table->pointers[vaddr >> PAGE_BITS] =
- GetPointerFromVMA(vaddr & ~PAGE_MASK);
+ case PageType::RasterizerCachedMemory: {
+ u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK);
+ if (pointer == nullptr) {
+ // It's possible that this function has called been while updating the pagetable
+ // after unmapping a VMA. In that case the underlying VMA will no longer exist,
+ // and we should just leave the pagetable entry blank.
+ page_type = PageType::Unmapped;
+ } else {
+ page_type = PageType::Memory;
+ current_page_table->pointers[vaddr >> PAGE_BITS] = pointer;
+ }
break;
+ }
case PageType::RasterizerCachedSpecial:
page_type = PageType::Special;
break;
@@ -353,7 +365,6 @@ void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) {
UNREACHABLE();
}
}
- paddr += PAGE_SIZE;
}
}
@@ -364,11 +375,48 @@ void RasterizerFlushRegion(PAddr start, u32 size) {
}
void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size) {
+ // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
+ // null here
if (VideoCore::g_renderer != nullptr) {
VideoCore::g_renderer->Rasterizer()->FlushAndInvalidateRegion(start, size);
}
}
+void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode) {
+ // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
+ // null here
+ if (VideoCore::g_renderer != nullptr) {
+ VAddr end = start + size;
+
+ auto CheckRegion = [&](VAddr region_start, VAddr region_end) {
+ if (start >= region_end || end <= region_start) {
+ // No overlap with region
+ return;
+ }
+
+ VAddr overlap_start = std::max(start, region_start);
+ VAddr overlap_end = std::min(end, region_end);
+
+ PAddr physical_start = TryVirtualToPhysicalAddress(overlap_start).value();
+ u32 overlap_size = overlap_end - overlap_start;
+
+ auto* rasterizer = VideoCore::g_renderer->Rasterizer();
+ switch (mode) {
+ case FlushMode::Flush:
+ rasterizer->FlushRegion(physical_start, overlap_size);
+ break;
+ case FlushMode::FlushAndInvalidate:
+ rasterizer->FlushAndInvalidateRegion(physical_start, overlap_size);
+ break;
+ }
+ };
+
+ CheckRegion(LINEAR_HEAP_VADDR, LINEAR_HEAP_VADDR_END);
+ CheckRegion(NEW_LINEAR_HEAP_VADDR, NEW_LINEAR_HEAP_VADDR_END);
+ CheckRegion(VRAM_VADDR, VRAM_VADDR_END);
+ }
+}
+
u8 Read8(const VAddr addr) {
return Read<u8>(addr);
}
@@ -415,16 +463,13 @@ void ReadBlock(const VAddr src_addr, void* dest_buffer, const size_t size) {
break;
}
case PageType::RasterizerCachedMemory: {
- RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
std::memcpy(dest_buffer, GetPointerFromVMA(current_vaddr), copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
- RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, dest_buffer, copy_amount);
break;
}
@@ -485,18 +530,13 @@ void WriteBlock(const VAddr dest_addr, const void* src_buffer, const size_t size
break;
}
case PageType::RasterizerCachedMemory: {
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
- copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
std::memcpy(GetPointerFromVMA(current_vaddr), src_buffer, copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
- copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, src_buffer, copy_amount);
break;
}
@@ -542,18 +582,13 @@ void ZeroBlock(const VAddr dest_addr, const size_t size) {
break;
}
case PageType::RasterizerCachedMemory: {
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
- copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
std::memset(GetPointerFromVMA(current_vaddr), 0, copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
- RasterizerFlushAndInvalidateRegion(VirtualToPhysicalAddress(current_vaddr),
- copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::FlushAndInvalidate);
GetMMIOHandler(current_vaddr)->WriteBlock(current_vaddr, zeros.data(), copy_amount);
break;
}
@@ -598,15 +633,13 @@ void CopyBlock(VAddr dest_addr, VAddr src_addr, const size_t size) {
break;
}
case PageType::RasterizerCachedMemory: {
- RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
-
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
WriteBlock(dest_addr, GetPointerFromVMA(current_vaddr), copy_amount);
break;
}
case PageType::RasterizerCachedSpecial: {
DEBUG_ASSERT(GetMMIOHandler(current_vaddr));
-
- RasterizerFlushRegion(VirtualToPhysicalAddress(current_vaddr), copy_amount);
+ RasterizerFlushVirtualRegion(current_vaddr, copy_amount, FlushMode::Flush);
std::vector<u8> buffer(copy_amount);
GetMMIOHandler(current_vaddr)->ReadBlock(current_vaddr, buffer.data(), buffer.size());
@@ -665,7 +698,7 @@ void WriteMMIO<u64>(MMIORegionPointer mmio_handler, VAddr addr, const u64 data)
mmio_handler->Write64(addr, data);
}
-PAddr VirtualToPhysicalAddress(const VAddr addr) {
+boost::optional<PAddr> TryVirtualToPhysicalAddress(const VAddr addr) {
if (addr == 0) {
return 0;
} else if (addr >= VRAM_VADDR && addr < VRAM_VADDR_END) {
@@ -682,12 +715,20 @@ PAddr VirtualToPhysicalAddress(const VAddr addr) {
return addr - N3DS_EXTRA_RAM_VADDR + N3DS_EXTRA_RAM_PADDR;
}
- LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr);
- // To help with debugging, set bit on address so that it's obviously invalid.
- return addr | 0x80000000;
+ return boost::none;
+}
+
+PAddr VirtualToPhysicalAddress(const VAddr addr) {
+ auto paddr = TryVirtualToPhysicalAddress(addr);
+ if (!paddr) {
+ LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x%08X", addr);
+ // To help with debugging, set bit on address so that it's obviously invalid.
+ return addr | 0x80000000;
+ }
+ return *paddr;
}
-VAddr PhysicalToVirtualAddress(const PAddr addr) {
+boost::optional<VAddr> PhysicalToVirtualAddress(const PAddr addr) {
if (addr == 0) {
return 0;
} else if (addr >= VRAM_PADDR && addr < VRAM_PADDR_END) {
@@ -702,9 +743,7 @@ VAddr PhysicalToVirtualAddress(const PAddr addr) {
return addr - N3DS_EXTRA_RAM_PADDR + N3DS_EXTRA_RAM_VADDR;
}
- LOG_ERROR(HW_Memory, "Unknown physical address @ 0x%08X", addr);
- // To help with debugging, set bit on address so that it's obviously invalid.
- return addr | 0x80000000;
+ return boost::none;
}
} // namespace
diff --git a/src/core/memory.h b/src/core/memory.h
index 802aa465e..c8c56babd 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -7,6 +7,7 @@
#include <array>
#include <cstddef>
#include <string>
+#include <boost/optional.hpp>
#include "common/common_types.h"
namespace Memory {
@@ -55,8 +56,10 @@ enum : PAddr {
/// Main FCRAM
FCRAM_PADDR = 0x20000000,
- FCRAM_SIZE = 0x08000000, ///< FCRAM size (128MB)
+ FCRAM_SIZE = 0x08000000, ///< FCRAM size on the Old 3DS (128MB)
+ FCRAM_N3DS_SIZE = 0x10000000, ///< FCRAM size on the New 3DS (256MB)
FCRAM_PADDR_END = FCRAM_PADDR + FCRAM_SIZE,
+ FCRAM_N3DS_PADDR_END = FCRAM_PADDR + FCRAM_N3DS_SIZE,
};
/// Virtual user-space memory regions
@@ -146,15 +149,23 @@ u8* GetPointer(VAddr virtual_address);
std::string ReadCString(VAddr virtual_address, std::size_t max_length);
/**
-* Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical
-* address. This should be used by services to translate addresses for use by the hardware.
-*/
+ * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical
+ * address. This should be used by services to translate addresses for use by the hardware.
+ */
+boost::optional<PAddr> TryVirtualToPhysicalAddress(VAddr addr);
+
+/**
+ * Converts a virtual address inside a region with 1:1 mapping to physical memory to a physical
+ * address. This should be used by services to translate addresses for use by the hardware.
+ *
+ * @deprecated Use TryVirtualToPhysicalAddress(), which reports failure.
+ */
PAddr VirtualToPhysicalAddress(VAddr addr);
/**
-* Undoes a mapping performed by VirtualToPhysicalAddress().
-*/
-VAddr PhysicalToVirtualAddress(PAddr addr);
+ * Undoes a mapping performed by VirtualToPhysicalAddress().
+ */
+boost::optional<VAddr> PhysicalToVirtualAddress(PAddr addr);
/**
* Gets a pointer to the memory region beginning at the specified physical address.
@@ -179,6 +190,19 @@ void RasterizerFlushRegion(PAddr start, u32 size);
*/
void RasterizerFlushAndInvalidateRegion(PAddr start, u32 size);
+enum class FlushMode {
+ /// Write back modified surfaces to RAM
+ Flush,
+ /// Write back modified surfaces to RAM, and also remove them from the cache
+ FlushAndInvalidate,
+};
+
+/**
+ * Flushes and invalidates any externally cached rasterizer resources touching the given virtual
+ * address region.
+ */
+void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode);
+
/**
* Dynarmic has an optimization to memory accesses when the pointer to the page exists that
* can be used by setting up the current page table as a callback. This function is used to
diff --git a/src/core/settings.h b/src/core/settings.h
index 03c64c94c..ee16bb90a 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -126,6 +126,9 @@ struct Values {
// Debugging
bool use_gdbstub;
u16 gdbstub_port;
+
+ // WebService
+ std::string telemetry_endpoint_url;
} extern values;
// a special value for Values::region_value indicating that citra will automatically select a region
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index ddc8b262e..841d6cfa1 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -4,32 +4,94 @@
#include <cstring>
+#include "common/assert.h"
#include "common/scm_rev.h"
+#include "common/x64/cpu_detect.h"
+#include "core/settings.h"
#include "core/telemetry_session.h"
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/telemetry_json.h"
+#endif
+
namespace Core {
+static const char* CpuVendorToStr(Common::CPUVendor vendor) {
+ switch (vendor) {
+ case Common::CPUVendor::INTEL:
+ return "Intel";
+ case Common::CPUVendor::AMD:
+ return "Amd";
+ case Common::CPUVendor::OTHER:
+ return "Other";
+ }
+ UNREACHABLE();
+}
+
TelemetrySession::TelemetrySession() {
- // TODO(bunnei): Replace with a backend that logs to our web service
+#ifdef ENABLE_WEB_SERVICE
+ backend = std::make_unique<WebService::TelemetryJson>();
+#else
backend = std::make_unique<Telemetry::NullVisitor>();
-
+#endif
// Log one-time session start information
- const auto duration{std::chrono::steady_clock::now().time_since_epoch()};
- const auto start_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()};
- AddField(Telemetry::FieldType::Session, "StartTime", start_time);
+ const s64 init_time{std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count()};
+ AddField(Telemetry::FieldType::Session, "Init_Time", init_time);
- // Log one-time application information
+ // Log application information
const bool is_git_dirty{std::strstr(Common::g_scm_desc, "dirty") != nullptr};
- AddField(Telemetry::FieldType::App, "GitIsDirty", is_git_dirty);
- AddField(Telemetry::FieldType::App, "GitBranch", Common::g_scm_branch);
- AddField(Telemetry::FieldType::App, "GitRevision", Common::g_scm_rev);
+ AddField(Telemetry::FieldType::App, "Git_IsDirty", is_git_dirty);
+ AddField(Telemetry::FieldType::App, "Git_Branch", Common::g_scm_branch);
+ AddField(Telemetry::FieldType::App, "Git_Revision", Common::g_scm_rev);
+
+ // Log user system information
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Model", Common::GetCPUCaps().cpu_string);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_BrandString",
+ Common::GetCPUCaps().brand_string);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Vendor",
+ CpuVendorToStr(Common::GetCPUCaps().vendor));
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_FMA4", Common::GetCPUCaps().fma4);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE", Common::GetCPUCaps().sse);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE2", Common::GetCPUCaps().sse2);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE3", Common::GetCPUCaps().sse3);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSSE3",
+ Common::GetCPUCaps().ssse3);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE41",
+ Common::GetCPUCaps().sse4_1);
+ AddField(Telemetry::FieldType::UserSystem, "CPU_Extension_x64_SSE42",
+ Common::GetCPUCaps().sse4_2);
+
+ // Log user configuration information
+ AddField(Telemetry::FieldType::UserConfig, "Audio_EnableAudioStretching",
+ Settings::values.enable_audio_stretching);
+ AddField(Telemetry::FieldType::UserConfig, "Core_UseCpuJit", Settings::values.use_cpu_jit);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_ResolutionFactor",
+ Settings::values.resolution_factor);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_ToggleFramelimit",
+ Settings::values.toggle_framelimit);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_UseHwRenderer",
+ Settings::values.use_hw_renderer);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_UseShaderJit",
+ Settings::values.use_shader_jit);
+ AddField(Telemetry::FieldType::UserConfig, "Renderer_UseVsync", Settings::values.use_vsync);
+ AddField(Telemetry::FieldType::UserConfig, "System_IsNew3ds", Settings::values.is_new_3ds);
+ AddField(Telemetry::FieldType::UserConfig, "System_RegionValue", Settings::values.region_value);
}
TelemetrySession::~TelemetrySession() {
// Log one-time session end information
- const auto duration{std::chrono::steady_clock::now().time_since_epoch()};
- const auto end_time{std::chrono::duration_cast<std::chrono::microseconds>(duration).count()};
- AddField(Telemetry::FieldType::Session, "EndTime", end_time);
+ const s64 shutdown_time{std::chrono::duration_cast<std::chrono::milliseconds>(
+ std::chrono::system_clock::now().time_since_epoch())
+ .count()};
+ AddField(Telemetry::FieldType::Session, "Shutdown_Time", shutdown_time);
// Complete the session, submitting to web service if necessary
// This is just a placeholder to wrap up the session once the core completes and this is
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
new file mode 100644
index 000000000..ac9d028da
--- /dev/null
+++ b/src/network/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(SRCS
+ network.cpp
+ packet.cpp
+ room.cpp
+ room_member.cpp
+ )
+
+set(HEADERS
+ network.h
+ packet.h
+ room.h
+ room_member.h
+ )
+
+create_directory_groups(${SRCS} ${HEADERS})
+
+add_library(network STATIC ${SRCS} ${HEADERS})
+target_link_libraries(network PRIVATE common enet)
diff --git a/src/network/network.cpp b/src/network/network.cpp
new file mode 100644
index 000000000..51b5d6a9f
--- /dev/null
+++ b/src/network/network.cpp
@@ -0,0 +1,50 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "enet/enet.h"
+#include "network/network.h"
+
+namespace Network {
+
+static std::shared_ptr<RoomMember> g_room_member; ///< RoomMember (Client) for network games
+static std::shared_ptr<Room> g_room; ///< Room (Server) for network games
+// TODO(B3N30): Put these globals into a networking class
+
+bool Init() {
+ if (enet_initialize() != 0) {
+ LOG_ERROR(Network, "Error initalizing ENet");
+ return false;
+ }
+ g_room = std::make_shared<Room>();
+ g_room_member = std::make_shared<RoomMember>();
+ LOG_DEBUG(Network, "initialized OK");
+ return true;
+}
+
+std::weak_ptr<Room> GetRoom() {
+ return g_room;
+}
+
+std::weak_ptr<RoomMember> GetRoomMember() {
+ return g_room_member;
+}
+
+void Shutdown() {
+ if (g_room_member) {
+ if (g_room_member->IsConnected())
+ g_room_member->Leave();
+ g_room_member.reset();
+ }
+ if (g_room) {
+ if (g_room->GetState() == Room::State::Open)
+ g_room->Destroy();
+ g_room.reset();
+ }
+ enet_deinitialize();
+ LOG_DEBUG(Network, "shutdown OK");
+}
+
+} // namespace Network
diff --git a/src/network/network.h b/src/network/network.h
new file mode 100644
index 000000000..6d002d693
--- /dev/null
+++ b/src/network/network.h
@@ -0,0 +1,25 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "network/room.h"
+#include "network/room_member.h"
+
+namespace Network {
+
+/// Initializes and registers the network device, the room, and the room member.
+bool Init();
+
+/// Returns a pointer to the room handle
+std::weak_ptr<Room> GetRoom();
+
+/// Returns a pointer to the room member handle
+std::weak_ptr<RoomMember> GetRoomMember();
+
+/// Unregisters the network device, the room, and the room member and shut them down.
+void Shutdown();
+
+} // namespace Network
diff --git a/src/network/packet.cpp b/src/network/packet.cpp
new file mode 100644
index 000000000..660e92c0d
--- /dev/null
+++ b/src/network/packet.cpp
@@ -0,0 +1,225 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#ifdef _WIN32
+#include <winsock2.h>
+#else
+#include <arpa/inet.h>
+#endif
+#include <cstring>
+#include <string>
+#include "network/packet.h"
+
+namespace Network {
+
+void Packet::Append(const void* in_data, std::size_t size_in_bytes) {
+ if (in_data && (size_in_bytes > 0)) {
+ std::size_t start = data.size();
+ data.resize(start + size_in_bytes);
+ std::memcpy(&data[start], in_data, size_in_bytes);
+ }
+}
+
+void Packet::Read(void* out_data, std::size_t size_in_bytes) {
+ if (out_data && CheckSize(size_in_bytes)) {
+ std::memcpy(out_data, &data[read_pos], size_in_bytes);
+ read_pos += size_in_bytes;
+ }
+}
+
+void Packet::Clear() {
+ data.clear();
+ read_pos = 0;
+ is_valid = true;
+}
+
+const void* Packet::GetData() const {
+ return !data.empty() ? &data[0] : nullptr;
+}
+
+void Packet::IgnoreBytes(u32 length) {
+ read_pos += length;
+}
+
+std::size_t Packet::GetDataSize() const {
+ return data.size();
+}
+
+bool Packet::EndOfPacket() const {
+ return read_pos >= data.size();
+}
+
+Packet::operator bool() const {
+ return is_valid ? &Packet::CheckSize : nullptr;
+}
+
+Packet& Packet::operator>>(bool& out_data) {
+ u8 value;
+ if (*this >> value) {
+ out_data = (value != 0);
+ }
+ return *this;
+}
+
+Packet& Packet::operator>>(s8& out_data) {
+ Read(&out_data, sizeof(out_data));
+ return *this;
+}
+
+Packet& Packet::operator>>(u8& out_data) {
+ Read(&out_data, sizeof(out_data));
+ return *this;
+}
+
+Packet& Packet::operator>>(s16& out_data) {
+ s16 value;
+ Read(&value, sizeof(value));
+ out_data = ntohs(value);
+ return *this;
+}
+
+Packet& Packet::operator>>(u16& out_data) {
+ u16 value;
+ Read(&value, sizeof(value));
+ out_data = ntohs(value);
+ return *this;
+}
+
+Packet& Packet::operator>>(s32& out_data) {
+ s32 value;
+ Read(&value, sizeof(value));
+ out_data = ntohl(value);
+ return *this;
+}
+
+Packet& Packet::operator>>(u32& out_data) {
+ u32 value;
+ Read(&value, sizeof(value));
+ out_data = ntohl(value);
+ return *this;
+}
+
+Packet& Packet::operator>>(float& out_data) {
+ Read(&out_data, sizeof(out_data));
+ return *this;
+}
+
+Packet& Packet::operator>>(double& out_data) {
+ Read(&out_data, sizeof(out_data));
+ return *this;
+}
+
+Packet& Packet::operator>>(char* out_data) {
+ // First extract string length
+ u32 length = 0;
+ *this >> length;
+
+ if ((length > 0) && CheckSize(length)) {
+ // Then extract characters
+ std::memcpy(out_data, &data[read_pos], length);
+ out_data[length] = '\0';
+
+ // Update reading position
+ read_pos += length;
+ }
+
+ return *this;
+}
+
+Packet& Packet::operator>>(std::string& out_data) {
+ // First extract string length
+ u32 length = 0;
+ *this >> length;
+
+ out_data.clear();
+ if ((length > 0) && CheckSize(length)) {
+ // Then extract characters
+ out_data.assign(&data[read_pos], length);
+
+ // Update reading position
+ read_pos += length;
+ }
+
+ return *this;
+}
+
+Packet& Packet::operator<<(bool in_data) {
+ *this << static_cast<u8>(in_data);
+ return *this;
+}
+
+Packet& Packet::operator<<(s8 in_data) {
+ Append(&in_data, sizeof(in_data));
+ return *this;
+}
+
+Packet& Packet::operator<<(u8 in_data) {
+ Append(&in_data, sizeof(in_data));
+ return *this;
+}
+
+Packet& Packet::operator<<(s16 in_data) {
+ s16 toWrite = htons(in_data);
+ Append(&toWrite, sizeof(toWrite));
+ return *this;
+}
+
+Packet& Packet::operator<<(u16 in_data) {
+ u16 toWrite = htons(in_data);
+ Append(&toWrite, sizeof(toWrite));
+ return *this;
+}
+
+Packet& Packet::operator<<(s32 in_data) {
+ s32 toWrite = htonl(in_data);
+ Append(&toWrite, sizeof(toWrite));
+ return *this;
+}
+
+Packet& Packet::operator<<(u32 in_data) {
+ u32 toWrite = htonl(in_data);
+ Append(&toWrite, sizeof(toWrite));
+ return *this;
+}
+
+Packet& Packet::operator<<(float in_data) {
+ Append(&in_data, sizeof(in_data));
+ return *this;
+}
+
+Packet& Packet::operator<<(double in_data) {
+ Append(&in_data, sizeof(in_data));
+ return *this;
+}
+
+Packet& Packet::operator<<(const char* in_data) {
+ // First insert string length
+ u32 length = std::strlen(in_data);
+ *this << length;
+
+ // Then insert characters
+ Append(in_data, length * sizeof(char));
+
+ return *this;
+}
+
+Packet& Packet::operator<<(const std::string& in_data) {
+ // First insert string length
+ u32 length = static_cast<u32>(in_data.size());
+ *this << length;
+
+ // Then insert characters
+ if (length > 0)
+ Append(in_data.c_str(), length * sizeof(std::string::value_type));
+
+ return *this;
+}
+
+bool Packet::CheckSize(std::size_t size) {
+ is_valid = is_valid && (read_pos + size <= data.size());
+
+ return is_valid;
+}
+
+} // namespace Network
diff --git a/src/network/packet.h b/src/network/packet.h
new file mode 100644
index 000000000..94b351ab1
--- /dev/null
+++ b/src/network/packet.h
@@ -0,0 +1,162 @@
+// Copyright 2017 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_types.h"
+
+namespace Network {
+
+/// A class that serializes data for network transfer. It also handles endianess
+class Packet {
+public:
+ Packet() = default;
+ ~Packet() = default;
+
+ /**
+ * Append data to the end of the packet
+ * @param data Pointer to the sequence of bytes to append
+ * @param size_in_bytes Number of bytes to append
+ */
+ void Append(const void* data, std::size_t size_in_bytes);
+
+ /**
+ * Reads data from the current read position of the packet
+ * @param out_data Pointer where the data should get written to
+ * @param size_in_bytes Number of bytes to read
+ */
+ void Read(void* out_data, std::size_t size_in_bytes);
+
+ /**
+ * Clear the packet
+ * After calling Clear, the packet is empty.
+ */
+ void Clear();
+
+ /**
+ * Ignores bytes while reading
+ * @param length THe number of bytes to ignore
+ */
+ void IgnoreBytes(u32 length);
+
+ /**
+ * Get a pointer to the data contained in the packet
+ * @return Pointer to the data
+ */
+ const void* GetData() const;
+
+ /**
+ * This function returns the number of bytes pointed to by
+ * what getData returns.
+ * @return Data size, in bytes
+ */
+ std::size_t GetDataSize() const;
+
+ /**
+ * This function is useful to know if there is some data
+ * left to be read, without actually reading it.
+ * @return True if all data was read, false otherwise
+ */
+ bool EndOfPacket() const;
+
+ explicit operator bool() const;
+
+ /// Overloads of operator >> to read data from the packet
+ Packet& operator>>(bool& out_data);
+ Packet& operator>>(s8& out_data);
+ Packet& operator>>(u8& out_data);
+ Packet& operator>>(s16& out_data);
+ Packet& operator>>(u16& out_data);
+ Packet& operator>>(s32& out_data);
+ Packet& operator>>(u32& out_data);
+ Packet& operator>>(float& out_data);
+ Packet& operator>>(double& out_data);
+ Packet& operator>>(char* out_data);
+ Packet& operator>>(std::string& out_data);
+ template <typename T>
+ Packet& operator>>(std::vector<T>& out_data);
+ template <typename T, std::size_t S>
+ Packet& operator>>(std::array<T, S>& out_data);
+
+ /// Overloads of operator << to write data into the packet
+ Packet& operator<<(bool in_data);
+ Packet& operator<<(s8 in_data);
+ Packet& operator<<(u8 in_data);
+ Packet& operator<<(s16 in_data);
+ Packet& operator<<(u16 in_data);
+ Packet& operator<<(s32 in_data);
+ Packet& operator<<(u32 in_data);
+ Packet& operator<<(float in_data);
+ Packet& operator<<(double in_data);
+ Packet& operator<<(const char* in_data);
+ Packet& operator<<(const std::string& in_data);
+ template <typename T>
+ Packet& operator<<(const std::vector<T>& in_data);
+ template <typename T, std::size_t S>
+ Packet& operator<<(const std::array<T, S>& data);
+
+private:
+ /**
+ * Check if the packet can extract a given number of bytes
+ * This function updates accordingly the state of the packet.
+ * @param size Size to check
+ * @return True if size bytes can be read from the packet
+ */
+ bool CheckSize(std::size_t size);
+
+ // Member data
+ std::vector<char> data; ///< Data stored in the packet
+ std::size_t read_pos = 0; ///< Current reading position in the packet
+ bool is_valid = true; ///< Reading state of the packet
+};
+
+template <typename T>
+Packet& Packet::operator>>(std::vector<T>& out_data) {
+ // First extract the size
+ u32 size = 0;
+ *this >> size;
+ out_data.resize(size);
+
+ // Then extract the data
+ for (std::size_t i = 0; i < out_data.size(); ++i) {
+ T character = 0;
+ *this >> character;
+ out_data[i] = character;
+ }
+ return *this;
+}
+
+template <typename T, std::size_t S>
+Packet& Packet::operator>>(std::array<T, S>& out_data) {
+ for (std::size_t i = 0; i < out_data.size(); ++i) {
+ T character = 0;
+ *this >> character;
+ out_data[i] = character;
+ }
+ return *this;
+}
+
+template <typename T>
+Packet& Packet::operator<<(const std::vector<T>& in_data) {
+ // First insert the size
+ *this << static_cast<u32>(in_data.size());
+
+ // Then insert the data
+ for (std::size_t i = 0; i < in_data.size(); ++i) {
+ *this << in_data[i];
+ }
+ return *this;
+}
+
+template <typename T, std::size_t S>
+Packet& Packet::operator<<(const std::array<T, S>& in_data) {
+ for (std::size_t i = 0; i < in_data.size(); ++i) {
+ *this << in_data[i];
+ }
+ return *this;
+}
+
+} // namespace Network
diff --git a/src/network/room.cpp b/src/network/room.cpp
new file mode 100644
index 000000000..8b7915bb7
--- /dev/null
+++ b/src/network/room.cpp
@@ -0,0 +1,454 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <atomic>
+#include <random>
+#include <thread>
+#include <vector>
+#include "enet/enet.h"
+#include "network/packet.h"
+#include "network/room.h"
+
+namespace Network {
+
+/// Maximum number of concurrent connections allowed to this room.
+static constexpr u32 MaxConcurrentConnections = 10;
+
+class Room::RoomImpl {
+public:
+ // This MAC address is used to generate a 'Nintendo' like Mac address.
+ const MacAddress NintendoOUI = {0x00, 0x1F, 0x32, 0x00, 0x00, 0x00};
+ std::mt19937 random_gen; ///< Random number generator. Used for GenerateMacAddress
+
+ ENetHost* server = nullptr; ///< Network interface.
+
+ std::atomic<State> state{State::Closed}; ///< Current state of the room.
+ RoomInformation room_information; ///< Information about this room.
+
+ struct Member {
+ std::string nickname; ///< The nickname of the member.
+ std::string game_name; ///< The current game of the member
+ MacAddress mac_address; ///< The assigned mac address of the member.
+ ENetPeer* peer; ///< The remote peer.
+ };
+ using MemberList = std::vector<Member>;
+ MemberList members; ///< Information about the members of this room.
+
+ RoomImpl() : random_gen(std::random_device()()) {}
+
+ /// Thread that receives and dispatches network packets
+ std::unique_ptr<std::thread> room_thread;
+
+ /// Thread function that will receive and dispatch messages until the room is destroyed.
+ void ServerLoop();
+ void StartLoop();
+
+ /**
+ * Parses and answers a room join request from a client.
+ * Validates the uniqueness of the username and assigns the MAC address
+ * that the client will use for the remainder of the connection.
+ */
+ void HandleJoinRequest(const ENetEvent* event);
+
+ /**
+ * Returns whether the nickname is valid, ie. isn't already taken by someone else in the room.
+ */
+ bool IsValidNickname(const std::string& nickname) const;
+
+ /**
+ * Returns whether the MAC address is valid, ie. isn't already taken by someone else in the
+ * room.
+ */
+ bool IsValidMacAddress(const MacAddress& address) const;
+
+ /**
+ * Sends a ID_ROOM_NAME_COLLISION message telling the client that the name is invalid.
+ */
+ void SendNameCollision(ENetPeer* client);
+
+ /**
+ * Sends a ID_ROOM_MAC_COLLISION message telling the client that the MAC is invalid.
+ */
+ void SendMacCollision(ENetPeer* client);
+
+ /**
+ * Sends a ID_ROOM_VERSION_MISMATCH message telling the client that the version is invalid.
+ */
+ void SendVersionMismatch(ENetPeer* client);
+
+ /**
+ * Notifies the member that its connection attempt was successful,
+ * and it is now part of the room.
+ */
+ void SendJoinSuccess(ENetPeer* client, MacAddress mac_address);
+
+ /**
+ * Notifies the members that the room is closed,
+ */
+ void SendCloseMessage();
+
+ /**
+ * Sends the information about the room, along with the list of members
+ * to every connected client in the room.
+ * The packet has the structure:
+ * <MessageID>ID_ROOM_INFORMATION
+ * <String> room_name
+ * <u32> member_slots: The max number of clients allowed in this room
+ * <u32> num_members: the number of currently joined clients
+ * This is followed by the following three values for each member:
+ * <String> nickname of that member
+ * <MacAddress> mac_address of that member
+ * <String> game_name of that member
+ */
+ void BroadcastRoomInformation();
+
+ /**
+ * Generates a free MAC address to assign to a new client.
+ * The first 3 bytes are the NintendoOUI 0x00, 0x1F, 0x32
+ */
+ MacAddress GenerateMacAddress();
+
+ /**
+ * Broadcasts this packet to all members except the sender.
+ * @param event The ENet event containing the data
+ */
+ void HandleWifiPacket(const ENetEvent* event);
+
+ /**
+ * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
+ * @param event The ENet event that was received.
+ */
+ void HandleChatPacket(const ENetEvent* event);
+
+ /**
+ * Extracts the game name from a received ENet packet and broadcasts it.
+ * @param event The ENet event that was received.
+ */
+ void HandleGameNamePacket(const ENetEvent* event);
+
+ /**
+ * Removes the client from the members list if it was in it and announces the change
+ * to all other clients.
+ */
+ void HandleClientDisconnection(ENetPeer* client);
+};
+
+// RoomImpl
+void Room::RoomImpl::ServerLoop() {
+ while (state != State::Closed) {
+ ENetEvent event;
+ if (enet_host_service(server, &event, 100) > 0) {
+ switch (event.type) {
+ case ENET_EVENT_TYPE_RECEIVE:
+ switch (event.packet->data[0]) {
+ case IdJoinRequest:
+ HandleJoinRequest(&event);
+ break;
+ case IdSetGameName:
+ HandleGameNamePacket(&event);
+ break;
+ case IdWifiPacket:
+ HandleWifiPacket(&event);
+ break;
+ case IdChatMessage:
+ HandleChatPacket(&event);
+ break;
+ }
+ enet_packet_destroy(event.packet);
+ break;
+ case ENET_EVENT_TYPE_DISCONNECT:
+ HandleClientDisconnection(event.peer);
+ break;
+ }
+ }
+ }
+ // Close the connection to all members:
+ SendCloseMessage();
+}
+
+void Room::RoomImpl::StartLoop() {
+ room_thread = std::make_unique<std::thread>(&Room::RoomImpl::ServerLoop, this);
+}
+
+void Room::RoomImpl::HandleJoinRequest(const ENetEvent* event) {
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+ packet.IgnoreBytes(sizeof(u8)); // Igonore the message type
+ std::string nickname;
+ packet >> nickname;
+
+ MacAddress preferred_mac;
+ packet >> preferred_mac;
+
+ u32 client_version;
+ packet >> client_version;
+
+ if (!IsValidNickname(nickname)) {
+ SendNameCollision(event->peer);
+ return;
+ }
+
+ if (preferred_mac != NoPreferredMac) {
+ // Verify if the preferred mac is available
+ if (!IsValidMacAddress(preferred_mac)) {
+ SendMacCollision(event->peer);
+ return;
+ }
+ } else {
+ // Assign a MAC address of this client automatically
+ preferred_mac = GenerateMacAddress();
+ }
+
+ if (client_version != network_version) {
+ SendVersionMismatch(event->peer);
+ return;
+ }
+
+ // At this point the client is ready to be added to the room.
+ Member member{};
+ member.mac_address = preferred_mac;
+ member.nickname = nickname;
+ member.peer = event->peer;
+
+ members.push_back(std::move(member));
+
+ // Notify everyone that the room information has changed.
+ BroadcastRoomInformation();
+ SendJoinSuccess(event->peer, preferred_mac);
+}
+
+bool Room::RoomImpl::IsValidNickname(const std::string& nickname) const {
+ // A nickname is valid if it is not already taken by anybody else in the room.
+ // TODO(B3N30): Check for empty names, spaces, etc.
+ return std::all_of(members.begin(), members.end(),
+ [&nickname](const auto& member) { return member.nickname != nickname; });
+}
+
+bool Room::RoomImpl::IsValidMacAddress(const MacAddress& address) const {
+ // A MAC address is valid if it is not already taken by anybody else in the room.
+ return std::all_of(members.begin(), members.end(),
+ [&address](const auto& member) { return member.mac_address != address; });
+}
+
+void Room::RoomImpl::SendNameCollision(ENetPeer* client) {
+ Packet packet;
+ packet << static_cast<u8>(IdNameCollision);
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendMacCollision(ENetPeer* client) {
+ Packet packet;
+ packet << static_cast<u8>(IdMacCollision);
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendVersionMismatch(ENetPeer* client) {
+ Packet packet;
+ packet << static_cast<u8>(IdVersionMismatch);
+ packet << network_version;
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendJoinSuccess(ENetPeer* client, MacAddress mac_address) {
+ Packet packet;
+ packet << static_cast<u8>(IdJoinSuccess);
+ packet << mac_address;
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(client, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::SendCloseMessage() {
+ Packet packet;
+ packet << static_cast<u8>(IdCloseRoom);
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ for (auto& member : members) {
+ enet_peer_send(member.peer, 0, enet_packet);
+ }
+ enet_host_flush(server);
+ for (auto& member : members) {
+ enet_peer_disconnect(member.peer, 0);
+ }
+}
+
+void Room::RoomImpl::BroadcastRoomInformation() {
+ Packet packet;
+ packet << static_cast<u8>(IdRoomInformation);
+ packet << room_information.name;
+ packet << room_information.member_slots;
+
+ packet << static_cast<u32>(members.size());
+ for (const auto& member : members) {
+ packet << member.nickname;
+ packet << member.mac_address;
+ packet << member.game_name;
+ }
+
+ ENetPacket* enet_packet =
+ enet_packet_create(packet.GetData(), packet.GetDataSize(), ENET_PACKET_FLAG_RELIABLE);
+ enet_host_broadcast(server, 0, enet_packet);
+ enet_host_flush(server);
+}
+
+MacAddress Room::RoomImpl::GenerateMacAddress() {
+ MacAddress result_mac =
+ NintendoOUI; // The first three bytes of each MAC address will be the NintendoOUI
+ std::uniform_int_distribution<> dis(0x00, 0xFF); // Random byte between 0 and 0xFF
+ do {
+ for (size_t i = 3; i < result_mac.size(); ++i) {
+ result_mac[i] = dis(random_gen);
+ }
+ } while (!IsValidMacAddress(result_mac));
+ return result_mac;
+}
+
+void Room::RoomImpl::HandleWifiPacket(const ENetEvent* event) {
+ Packet in_packet;
+ in_packet.Append(event->packet->data, event->packet->dataLength);
+ in_packet.IgnoreBytes(sizeof(u8)); // Message type
+ in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Type
+ in_packet.IgnoreBytes(sizeof(u8)); // WifiPacket Channel
+ in_packet.IgnoreBytes(sizeof(MacAddress)); // WifiPacket Transmitter Address
+ MacAddress destination_address;
+ in_packet >> destination_address;
+
+ Packet out_packet;
+ out_packet.Append(event->packet->data, event->packet->dataLength);
+ ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
+ ENET_PACKET_FLAG_RELIABLE);
+
+ if (destination_address == BroadcastMac) { // Send the data to everyone except the sender
+ for (const auto& member : members) {
+ if (member.peer != event->peer)
+ enet_peer_send(member.peer, 0, enet_packet);
+ }
+ } else { // Send the data only to the destination client
+ auto member = std::find_if(members.begin(), members.end(),
+ [destination_address](const Member& member) -> bool {
+ return member.mac_address == destination_address;
+ });
+ if (member != members.end()) {
+ enet_peer_send(member->peer, 0, enet_packet);
+ }
+ }
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
+ Packet in_packet;
+ in_packet.Append(event->packet->data, event->packet->dataLength);
+
+ in_packet.IgnoreBytes(sizeof(u8)); // Igonore the message type
+ std::string message;
+ in_packet >> message;
+ auto CompareNetworkAddress = [event](const Member member) -> bool {
+ return member.peer == event->peer;
+ };
+ const auto sending_member = std::find_if(members.begin(), members.end(), CompareNetworkAddress);
+ if (sending_member == members.end()) {
+ return; // Received a chat message from a unknown sender
+ }
+
+ Packet out_packet;
+ out_packet << static_cast<u8>(IdChatMessage);
+ out_packet << sending_member->nickname;
+ out_packet << message;
+
+ ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
+ ENET_PACKET_FLAG_RELIABLE);
+ for (const auto& member : members) {
+ if (member.peer != event->peer)
+ enet_peer_send(member.peer, 0, enet_packet);
+ }
+ enet_host_flush(server);
+}
+
+void Room::RoomImpl::HandleGameNamePacket(const ENetEvent* event) {
+ Packet in_packet;
+ in_packet.Append(event->packet->data, event->packet->dataLength);
+
+ in_packet.IgnoreBytes(sizeof(u8)); // Igonore the message type
+ std::string game_name;
+ in_packet >> game_name;
+ auto member =
+ std::find_if(members.begin(), members.end(),
+ [event](const Member& member) -> bool { return member.peer == event->peer; });
+ if (member != members.end()) {
+ member->game_name = game_name;
+ BroadcastRoomInformation();
+ }
+}
+
+void Room::RoomImpl::HandleClientDisconnection(ENetPeer* client) {
+ // Remove the client from the members list.
+ members.erase(std::remove_if(members.begin(), members.end(),
+ [client](const Member& member) { return member.peer == client; }),
+ members.end());
+
+ // Announce the change to all clients.
+ enet_peer_disconnect(client, 0);
+ BroadcastRoomInformation();
+}
+
+// Room
+Room::Room() : room_impl{std::make_unique<RoomImpl>()} {}
+
+Room::~Room() = default;
+
+void Room::Create(const std::string& name, const std::string& server_address, u16 server_port) {
+ ENetAddress address;
+ address.host = ENET_HOST_ANY;
+ if (!server_address.empty()) {
+ enet_address_set_host(&address, server_address.c_str());
+ }
+ address.port = server_port;
+
+ room_impl->server = enet_host_create(&address, MaxConcurrentConnections, NumChannels, 0, 0);
+ // TODO(B3N30): Allow specifying the maximum number of concurrent connections.
+ room_impl->state = State::Open;
+
+ room_impl->room_information.name = name;
+ room_impl->room_information.member_slots = MaxConcurrentConnections;
+ room_impl->StartLoop();
+}
+
+Room::State Room::GetState() const {
+ return room_impl->state;
+}
+
+const RoomInformation& Room::GetRoomInformation() const {
+ return room_impl->room_information;
+}
+
+void Room::Destroy() {
+ room_impl->state = State::Closed;
+ room_impl->room_thread->join();
+ room_impl->room_thread.reset();
+
+ if (room_impl->server) {
+ enet_host_destroy(room_impl->server);
+ }
+ room_impl->room_information = {};
+ room_impl->server = nullptr;
+ room_impl->members.clear();
+ room_impl->room_information.member_slots = 0;
+ room_impl->room_information.name.clear();
+}
+
+} // namespace Network
diff --git a/src/network/room.h b/src/network/room.h
new file mode 100644
index 000000000..54cccf0ae
--- /dev/null
+++ b/src/network/room.h
@@ -0,0 +1,84 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+
+namespace Network {
+
+constexpr u32 network_version = 1; ///< The version of this Room and RoomMember
+
+constexpr u16 DefaultRoomPort = 1234;
+constexpr size_t NumChannels = 1; // Number of channels used for the connection
+
+struct RoomInformation {
+ std::string name; ///< Name of the server
+ u32 member_slots; ///< Maximum number of members in this room
+};
+
+using MacAddress = std::array<u8, 6>;
+/// A special MAC address that tells the room we're joining to assign us a MAC address
+/// automatically.
+const MacAddress NoPreferredMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+// 802.11 broadcast MAC address
+constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+// The different types of messages that can be sent. The first byte of each packet defines the type
+enum RoomMessageTypes : u8 {
+ IdJoinRequest = 1,
+ IdJoinSuccess,
+ IdRoomInformation,
+ IdSetGameName,
+ IdWifiPacket,
+ IdChatMessage,
+ IdNameCollision,
+ IdMacCollision,
+ IdVersionMismatch,
+ IdCloseRoom
+};
+
+/// This is what a server [person creating a server] would use.
+class Room final {
+public:
+ enum class State : u8 {
+ Open, ///< The room is open and ready to accept connections.
+ Closed, ///< The room is not opened and can not accept connections.
+ };
+
+ Room();
+ ~Room();
+
+ /**
+ * Gets the current state of the room.
+ */
+ State GetState() const;
+
+ /**
+ * Gets the room information of the room.
+ */
+ const RoomInformation& GetRoomInformation() const;
+
+ /**
+ * Creates the socket for this room. Will bind to default address if
+ * server is empty string.
+ */
+ void Create(const std::string& name, const std::string& server = "",
+ u16 server_port = DefaultRoomPort);
+
+ /**
+ * Destroys the socket
+ */
+ void Destroy();
+
+private:
+ class RoomImpl;
+ std::unique_ptr<RoomImpl> room_impl;
+};
+
+} // namespace Network
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
new file mode 100644
index 000000000..dac9bacae
--- /dev/null
+++ b/src/network/room_member.cpp
@@ -0,0 +1,382 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <atomic>
+#include <list>
+#include <mutex>
+#include <thread>
+#include "common/assert.h"
+#include "enet/enet.h"
+#include "network/packet.h"
+#include "network/room_member.h"
+
+namespace Network {
+
+constexpr u32 ConnectionTimeoutMs = 5000;
+
+class RoomMember::RoomMemberImpl {
+public:
+ ENetHost* client = nullptr; ///< ENet network interface.
+ ENetPeer* server = nullptr; ///< The server peer the client is connected to
+
+ /// Information about the clients connected to the same room as us.
+ MemberList member_information;
+ /// Information about the room we're connected to.
+ RoomInformation room_information;
+
+ std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
+ void SetState(const State new_state);
+ bool IsConnected() const;
+
+ std::string nickname; ///< The nickname of this member.
+ MacAddress mac_address; ///< The mac_address of this member.
+
+ std::mutex network_mutex; ///< Mutex that controls access to the `client` variable.
+ /// Thread that receives and dispatches network packets
+ std::unique_ptr<std::thread> loop_thread;
+ std::mutex send_list_mutex; ///< Mutex that controls access to the `send_list` variable.
+ std::list<Packet> send_list; ///< A list that stores all packets to send the async
+ void MemberLoop();
+
+ void StartLoop();
+
+ /**
+ * Sends data to the room. It will be send on channel 0 with flag RELIABLE
+ * @param packet The data to send
+ */
+ void Send(Packet&& packet);
+
+ /**
+ * Sends a request to the server, asking for permission to join a room with the specified
+ * nickname and preferred mac.
+ * @params nickname The desired nickname.
+ * @params preferred_mac The preferred MAC address to use in the room, the NoPreferredMac tells
+ * the server to assign one for us.
+ */
+ void SendJoinRequest(const std::string& nickname,
+ const MacAddress& preferred_mac = NoPreferredMac);
+
+ /**
+ * Extracts a MAC Address from a received ENet packet.
+ * @param event The ENet event that was received.
+ */
+ void HandleJoinPacket(const ENetEvent* event);
+ /**
+ * Extracts RoomInformation and MemberInformation from a received RakNet packet.
+ * @param event The ENet event that was received.
+ */
+ void HandleRoomInformationPacket(const ENetEvent* event);
+
+ /**
+ * Extracts a WifiPacket from a received ENet packet.
+ * @param event The ENet event that was received.
+ */
+ void HandleWifiPackets(const ENetEvent* event);
+
+ /**
+ * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
+ * @param event The ENet event that was received.
+ */
+ void HandleChatPacket(const ENetEvent* event);
+
+ /**
+ * Disconnects the RoomMember from the Room
+ */
+ void Disconnect();
+};
+
+// RoomMemberImpl
+void RoomMember::RoomMemberImpl::SetState(const State new_state) {
+ state = new_state;
+ // TODO(B3N30): Invoke the callback functions
+}
+
+bool RoomMember::RoomMemberImpl::IsConnected() const {
+ return state == State::Joining || state == State::Joined;
+}
+
+void RoomMember::RoomMemberImpl::MemberLoop() {
+ // Receive packets while the connection is open
+ while (IsConnected()) {
+ std::lock_guard<std::mutex> lock(network_mutex);
+ ENetEvent event;
+ if (enet_host_service(client, &event, 100) > 0) {
+ switch (event.type) {
+ case ENET_EVENT_TYPE_RECEIVE:
+ switch (event.packet->data[0]) {
+ case IdWifiPacket:
+ HandleWifiPackets(&event);
+ break;
+ case IdChatMessage:
+ HandleChatPacket(&event);
+ break;
+ case IdRoomInformation:
+ HandleRoomInformationPacket(&event);
+ break;
+ case IdJoinSuccess:
+ // The join request was successful, we are now in the room.
+ // If we joined successfully, there must be at least one client in the room: us.
+ ASSERT_MSG(member_information.size() > 0,
+ "We have not yet received member information.");
+ HandleJoinPacket(&event); // Get the MAC Address for the client
+ SetState(State::Joined);
+ break;
+ case IdNameCollision:
+ SetState(State::NameCollision);
+ break;
+ case IdMacCollision:
+ SetState(State::MacCollision);
+ break;
+ case IdVersionMismatch:
+ SetState(State::WrongVersion);
+ break;
+ case IdCloseRoom:
+ SetState(State::LostConnection);
+ break;
+ }
+ enet_packet_destroy(event.packet);
+ break;
+ case ENET_EVENT_TYPE_DISCONNECT:
+ SetState(State::LostConnection);
+ break;
+ }
+ }
+ {
+ std::lock_guard<std::mutex> lock(send_list_mutex);
+ for (const auto& packet : send_list) {
+ ENetPacket* enetPacket = enet_packet_create(packet.GetData(), packet.GetDataSize(),
+ ENET_PACKET_FLAG_RELIABLE);
+ enet_peer_send(server, 0, enetPacket);
+ }
+ enet_host_flush(client);
+ send_list.clear();
+ }
+ }
+ Disconnect();
+};
+
+void RoomMember::RoomMemberImpl::StartLoop() {
+ loop_thread = std::make_unique<std::thread>(&RoomMember::RoomMemberImpl::MemberLoop, this);
+}
+
+void RoomMember::RoomMemberImpl::Send(Packet&& packet) {
+ std::lock_guard<std::mutex> lock(send_list_mutex);
+ send_list.push_back(std::move(packet));
+}
+
+void RoomMember::RoomMemberImpl::SendJoinRequest(const std::string& nickname,
+ const MacAddress& preferred_mac) {
+ Packet packet;
+ packet << static_cast<u8>(IdJoinRequest);
+ packet << nickname;
+ packet << preferred_mac;
+ packet << network_version;
+ Send(std::move(packet));
+}
+
+void RoomMember::RoomMemberImpl::HandleRoomInformationPacket(const ENetEvent* event) {
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8)); // Igonore the message type
+
+ RoomInformation info{};
+ packet >> info.name;
+ packet >> info.member_slots;
+ room_information.name = info.name;
+ room_information.member_slots = info.member_slots;
+
+ u32 num_members;
+ packet >> num_members;
+ member_information.resize(num_members);
+
+ for (auto& member : member_information) {
+ packet >> member.nickname;
+ packet >> member.mac_address;
+ packet >> member.game_name;
+ }
+ // TODO(B3N30): Invoke callbacks
+}
+
+void RoomMember::RoomMemberImpl::HandleJoinPacket(const ENetEvent* event) {
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8)); // Igonore the message type
+
+ // Parse the MAC Address from the packet
+ packet >> mac_address;
+ // TODO(B3N30): Invoke callbacks
+}
+
+void RoomMember::RoomMemberImpl::HandleWifiPackets(const ENetEvent* event) {
+ WifiPacket wifi_packet{};
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8)); // Igonore the message type
+
+ // Parse the WifiPacket from the packet
+ u8 frame_type;
+ packet >> frame_type;
+ WifiPacket::PacketType type = static_cast<WifiPacket::PacketType>(frame_type);
+
+ wifi_packet.type = type;
+ packet >> wifi_packet.channel;
+ packet >> wifi_packet.transmitter_address;
+ packet >> wifi_packet.destination_address;
+
+ u32 data_length;
+ packet >> data_length;
+
+ packet >> wifi_packet.data;
+
+ // TODO(B3N30): Invoke callbacks
+}
+
+void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
+ Packet packet;
+ packet.Append(event->packet->data, event->packet->dataLength);
+
+ // Ignore the first byte, which is the message id.
+ packet.IgnoreBytes(sizeof(u8));
+
+ ChatEntry chat_entry{};
+ packet >> chat_entry.nickname;
+ packet >> chat_entry.message;
+ // TODO(B3N30): Invoke callbacks
+}
+
+void RoomMember::RoomMemberImpl::Disconnect() {
+ member_information.clear();
+ room_information.member_slots = 0;
+ room_information.name.clear();
+
+ if (!server)
+ return;
+ enet_peer_disconnect(server, 0);
+
+ ENetEvent event;
+ while (enet_host_service(client, &event, ConnectionTimeoutMs) > 0) {
+ switch (event.type) {
+ case ENET_EVENT_TYPE_RECEIVE:
+ enet_packet_destroy(event.packet); // Ignore all incoming data
+ break;
+ case ENET_EVENT_TYPE_DISCONNECT:
+ server = nullptr;
+ return;
+ }
+ }
+ // didn't disconnect gracefully force disconnect
+ enet_peer_reset(server);
+ server = nullptr;
+}
+
+// RoomMember
+RoomMember::RoomMember() : room_member_impl{std::make_unique<RoomMemberImpl>()} {
+ room_member_impl->client = enet_host_create(nullptr, 1, NumChannels, 0, 0);
+ ASSERT_MSG(room_member_impl->client != nullptr, "Could not create client");
+}
+
+RoomMember::~RoomMember() {
+ ASSERT_MSG(!IsConnected(), "RoomMember is being destroyed while connected");
+ enet_host_destroy(room_member_impl->client);
+}
+
+RoomMember::State RoomMember::GetState() const {
+ return room_member_impl->state;
+}
+
+const RoomMember::MemberList& RoomMember::GetMemberInformation() const {
+ return room_member_impl->member_information;
+}
+
+const std::string& RoomMember::GetNickname() const {
+ return room_member_impl->nickname;
+}
+
+const MacAddress& RoomMember::GetMacAddress() const {
+ ASSERT_MSG(IsConnected(), "Tried to get MAC address while not connected");
+ return room_member_impl->mac_address;
+}
+
+RoomInformation RoomMember::GetRoomInformation() const {
+ return room_member_impl->room_information;
+}
+
+void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port,
+ u16 client_port, const MacAddress& preferred_mac) {
+ // If the member is connected, kill the connection first
+ if (room_member_impl->loop_thread && room_member_impl->loop_thread->joinable()) {
+ room_member_impl->SetState(State::Error);
+ room_member_impl->loop_thread->join();
+ room_member_impl->loop_thread.reset();
+ }
+ // If the thread isn't running but the ptr still exists, reset it
+ else if (room_member_impl->loop_thread) {
+ room_member_impl->loop_thread.reset();
+ }
+
+ ENetAddress address{};
+ enet_address_set_host(&address, server_addr);
+ address.port = server_port;
+ room_member_impl->server =
+ enet_host_connect(room_member_impl->client, &address, NumChannels, 0);
+
+ if (!room_member_impl->server) {
+ room_member_impl->SetState(State::Error);
+ return;
+ }
+
+ ENetEvent event{};
+ int net = enet_host_service(room_member_impl->client, &event, ConnectionTimeoutMs);
+ if (net > 0 && event.type == ENET_EVENT_TYPE_CONNECT) {
+ room_member_impl->nickname = nick;
+ room_member_impl->SetState(State::Joining);
+ room_member_impl->StartLoop();
+ room_member_impl->SendJoinRequest(nick, preferred_mac);
+ } else {
+ room_member_impl->SetState(State::CouldNotConnect);
+ }
+}
+
+bool RoomMember::IsConnected() const {
+ return room_member_impl->IsConnected();
+}
+
+void RoomMember::SendWifiPacket(const WifiPacket& wifi_packet) {
+ Packet packet;
+ packet << static_cast<u8>(IdWifiPacket);
+ packet << static_cast<u8>(wifi_packet.type);
+ packet << wifi_packet.channel;
+ packet << wifi_packet.transmitter_address;
+ packet << wifi_packet.destination_address;
+ packet << wifi_packet.data;
+ room_member_impl->Send(std::move(packet));
+}
+
+void RoomMember::SendChatMessage(const std::string& message) {
+ Packet packet;
+ packet << static_cast<u8>(IdChatMessage);
+ packet << message;
+ room_member_impl->Send(std::move(packet));
+}
+
+void RoomMember::SendGameName(const std::string& game_name) {
+ Packet packet;
+ packet << static_cast<u8>(IdSetGameName);
+ packet << game_name;
+ room_member_impl->Send(std::move(packet));
+}
+
+void RoomMember::Leave() {
+ room_member_impl->SetState(State::Idle);
+ room_member_impl->loop_thread->join();
+ room_member_impl->loop_thread.reset();
+}
+
+} // namespace Network
diff --git a/src/network/room_member.h b/src/network/room_member.h
new file mode 100644
index 000000000..bc1af3a7e
--- /dev/null
+++ b/src/network/room_member.h
@@ -0,0 +1,131 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include <vector>
+#include "common/common_types.h"
+#include "network/room.h"
+
+namespace Network {
+
+/// Information about the received WiFi packets.
+/// Acts as our own 802.11 header.
+struct WifiPacket {
+ enum class PacketType : u8 { Beacon, Data, Authentication, AssociationResponse };
+ PacketType type; ///< The type of 802.11 frame.
+ std::vector<u8> data; ///< Raw 802.11 frame data, starting at the management frame header
+ /// for management frames.
+ MacAddress transmitter_address; ///< Mac address of the transmitter.
+ MacAddress destination_address; ///< Mac address of the receiver.
+ u8 channel; ///< WiFi channel where this frame was transmitted.
+};
+
+/// Represents a chat message.
+struct ChatEntry {
+ std::string nickname; ///< Nickname of the client who sent this message.
+ std::string message; ///< Body of the message.
+};
+
+/**
+ * This is what a client [person joining a server] would use.
+ * It also has to be used if you host a game yourself (You'd create both, a Room and a
+ * RoomMembership for yourself)
+ */
+class RoomMember final {
+public:
+ enum class State : u8 {
+ Idle, ///< Default state
+ Error, ///< Some error [permissions to network device missing or something]
+ Joining, ///< The client is attempting to join a room.
+ Joined, ///< The client is connected to the room and is ready to send/receive packets.
+ LostConnection, ///< Connection closed
+
+ // Reasons why connection was rejected
+ NameCollision, ///< Somebody is already using this name
+ MacCollision, ///< Somebody is already using that mac-address
+ WrongVersion, ///< The room version is not the same as for this RoomMember
+ CouldNotConnect ///< The room is not responding to a connection attempt
+ };
+
+ struct MemberInformation {
+ std::string nickname; ///< Nickname of the member.
+ std::string game_name; ///< Name of the game they're currently playing, or empty if they're
+ /// not playing anything.
+ MacAddress mac_address; ///< MAC address associated with this member.
+ };
+ using MemberList = std::vector<MemberInformation>;
+
+ RoomMember();
+ ~RoomMember();
+
+ /**
+ * Returns the status of our connection to the room.
+ */
+ State GetState() const;
+
+ /**
+ * Returns information about the members in the room we're currently connected to.
+ */
+ const MemberList& GetMemberInformation() const;
+
+ /**
+ * Returns the nickname of the RoomMember.
+ */
+ const std::string& GetNickname() const;
+
+ /**
+ * Returns the MAC address of the RoomMember.
+ */
+ const MacAddress& GetMacAddress() const;
+
+ /**
+ * Returns information about the room we're currently connected to.
+ */
+ RoomInformation GetRoomInformation() const;
+
+ /**
+ * Returns whether we're connected to a server or not.
+ */
+ bool IsConnected() const;
+
+ /**
+ * Attempts to join a room at the specified address and port, using the specified nickname.
+ * This may fail if the username is already taken.
+ */
+ void Join(const std::string& nickname, const char* server_addr = "127.0.0.1",
+ const u16 serverPort = DefaultRoomPort, const u16 clientPort = 0,
+ const MacAddress& preferred_mac = NoPreferredMac);
+
+ /**
+ * Sends a WiFi packet to the room.
+ * @param packet The WiFi packet to send.
+ */
+ void SendWifiPacket(const WifiPacket& packet);
+
+ /**
+ * Sends a chat message to the room.
+ * @param message The contents of the message.
+ */
+ void SendChatMessage(const std::string& message);
+
+ /**
+ * Sends the current game name to the room.
+ * @param game_name The game name.
+ */
+ void SendGameName(const std::string& game_name);
+
+ /**
+ * Leaves the current room.
+ */
+ void Leave();
+
+private:
+ class RoomMemberImpl;
+ std::unique_ptr<RoomMemberImpl> room_member_impl;
+};
+
+} // namespace Network
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 00d7c636a..a14df325a 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,8 +1,9 @@
set(SRCS
- glad.cpp
- tests.cpp
common/param_package.cpp
core/file_sys/path_parser.cpp
+ core/hle/kernel/hle_ipc.cpp
+ glad.cpp
+ tests.cpp
)
set(HEADERS
diff --git a/src/tests/core/hle/kernel/hle_ipc.cpp b/src/tests/core/hle/kernel/hle_ipc.cpp
new file mode 100644
index 000000000..52336d027
--- /dev/null
+++ b/src/tests/core/hle/kernel/hle_ipc.cpp
@@ -0,0 +1,216 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <catch.hpp>
+#include "core/hle/ipc.h"
+#include "core/hle/kernel/client_port.h"
+#include "core/hle/kernel/client_session.h"
+#include "core/hle/kernel/event.h"
+#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/hle_ipc.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/server_session.h"
+
+namespace Kernel {
+
+static SharedPtr<Object> MakeObject() {
+ return Event::Create(ResetType::OneShot);
+}
+
+TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") {
+ auto session = std::get<SharedPtr<ServerSession>>(ServerSession::CreateSessionPair());
+ HLERequestContext context(std::move(session));
+
+ auto process = Process::Create(CodeSet::Create("", 0));
+ HandleTable handle_table;
+
+ SECTION("works with empty cmdbuf") {
+ const u32_le input[]{
+ IPC::MakeHeader(0x1234, 0, 0),
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ REQUIRE(context.CommandBuffer()[0] == 0x12340000);
+ }
+
+ SECTION("translates regular params") {
+ const u32_le input[]{
+ IPC::MakeHeader(0, 3, 0), 0x12345678, 0x21122112, 0xAABBCCDD,
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ auto* output = context.CommandBuffer();
+ REQUIRE(output[1] == 0x12345678);
+ REQUIRE(output[2] == 0x21122112);
+ REQUIRE(output[3] == 0xAABBCCDD);
+ }
+
+ SECTION("translates move handles") {
+ auto a = MakeObject();
+ Handle a_handle = handle_table.Create(a).Unwrap();
+ const u32_le input[]{
+ IPC::MakeHeader(0, 0, 2), IPC::MoveHandleDesc(1), a_handle,
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ auto* output = context.CommandBuffer();
+ REQUIRE(context.GetIncomingHandle(output[2]) == a);
+ REQUIRE(handle_table.GetGeneric(a_handle) == nullptr);
+ }
+
+ SECTION("translates copy handles") {
+ auto a = MakeObject();
+ Handle a_handle = handle_table.Create(a).Unwrap();
+ const u32_le input[]{
+ IPC::MakeHeader(0, 0, 2), IPC::CopyHandleDesc(1), a_handle,
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ auto* output = context.CommandBuffer();
+ REQUIRE(context.GetIncomingHandle(output[2]) == a);
+ REQUIRE(handle_table.GetGeneric(a_handle) == a);
+ }
+
+ SECTION("translates multi-handle descriptors") {
+ auto a = MakeObject();
+ auto b = MakeObject();
+ auto c = MakeObject();
+ const u32_le input[]{
+ IPC::MakeHeader(0, 0, 5), IPC::MoveHandleDesc(2),
+ handle_table.Create(a).Unwrap(), handle_table.Create(b).Unwrap(),
+ IPC::MoveHandleDesc(1), handle_table.Create(c).Unwrap(),
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ auto* output = context.CommandBuffer();
+ REQUIRE(context.GetIncomingHandle(output[2]) == a);
+ REQUIRE(context.GetIncomingHandle(output[3]) == b);
+ REQUIRE(context.GetIncomingHandle(output[5]) == c);
+ }
+
+ SECTION("translates null handles") {
+ const u32_le input[]{
+ IPC::MakeHeader(0, 0, 2), IPC::MoveHandleDesc(1), 0,
+ };
+
+ auto result = context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ REQUIRE(result == RESULT_SUCCESS);
+ auto* output = context.CommandBuffer();
+ REQUIRE(context.GetIncomingHandle(output[2]) == nullptr);
+ }
+
+ SECTION("translates CallingPid descriptors") {
+ const u32_le input[]{
+ IPC::MakeHeader(0, 0, 2), IPC::CallingPidDesc(), 0x98989898,
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ REQUIRE(context.CommandBuffer()[2] == process->process_id);
+ }
+
+ SECTION("translates mixed params") {
+ auto a = MakeObject();
+ const u32_le input[]{
+ IPC::MakeHeader(0, 2, 4),
+ 0x12345678,
+ 0xABCDEF00,
+ IPC::MoveHandleDesc(1),
+ handle_table.Create(a).Unwrap(),
+ IPC::CallingPidDesc(),
+ 0,
+ };
+
+ context.PopulateFromIncomingCommandBuffer(input, *process, handle_table);
+
+ auto* output = context.CommandBuffer();
+ REQUIRE(output[1] == 0x12345678);
+ REQUIRE(output[2] == 0xABCDEF00);
+ REQUIRE(context.GetIncomingHandle(output[4]) == a);
+ REQUIRE(output[6] == process->process_id);
+ }
+}
+
+TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") {
+ auto session = std::get<SharedPtr<ServerSession>>(ServerSession::CreateSessionPair());
+ HLERequestContext context(std::move(session));
+
+ auto process = Process::Create(CodeSet::Create("", 0));
+ HandleTable handle_table;
+ auto* input = context.CommandBuffer();
+ u32_le output[IPC::COMMAND_BUFFER_LENGTH];
+
+ SECTION("works with empty cmdbuf") {
+ input[0] = IPC::MakeHeader(0x1234, 0, 0);
+
+ context.WriteToOutgoingCommandBuffer(output, *process, handle_table);
+
+ REQUIRE(output[0] == 0x12340000);
+ }
+
+ SECTION("translates regular params") {
+ input[0] = IPC::MakeHeader(0, 3, 0);
+ input[1] = 0x12345678;
+ input[2] = 0x21122112;
+ input[3] = 0xAABBCCDD;
+
+ context.WriteToOutgoingCommandBuffer(output, *process, handle_table);
+
+ REQUIRE(output[1] == 0x12345678);
+ REQUIRE(output[2] == 0x21122112);
+ REQUIRE(output[3] == 0xAABBCCDD);
+ }
+
+ SECTION("translates move/copy handles") {
+ auto a = MakeObject();
+ auto b = MakeObject();
+ input[0] = IPC::MakeHeader(0, 0, 4);
+ input[1] = IPC::MoveHandleDesc(1);
+ input[2] = context.AddOutgoingHandle(a);
+ input[3] = IPC::CopyHandleDesc(1);
+ input[4] = context.AddOutgoingHandle(b);
+
+ context.WriteToOutgoingCommandBuffer(output, *process, handle_table);
+
+ REQUIRE(handle_table.GetGeneric(output[2]) == a);
+ REQUIRE(handle_table.GetGeneric(output[4]) == b);
+ }
+
+ SECTION("translates null handles") {
+ input[0] = IPC::MakeHeader(0, 0, 2);
+ input[1] = IPC::MoveHandleDesc(1);
+ input[2] = context.AddOutgoingHandle(nullptr);
+
+ auto result = context.WriteToOutgoingCommandBuffer(output, *process, handle_table);
+
+ REQUIRE(result == RESULT_SUCCESS);
+ REQUIRE(output[2] == 0);
+ }
+
+ SECTION("translates multi-handle descriptors") {
+ auto a = MakeObject();
+ auto b = MakeObject();
+ auto c = MakeObject();
+ input[0] = IPC::MakeHeader(0, 0, 5);
+ input[1] = IPC::MoveHandleDesc(2);
+ input[2] = context.AddOutgoingHandle(a);
+ input[3] = context.AddOutgoingHandle(b);
+ input[4] = IPC::CopyHandleDesc(1);
+ input[5] = context.AddOutgoingHandle(c);
+
+ context.WriteToOutgoingCommandBuffer(output, *process, handle_table);
+
+ REQUIRE(handle_table.GetGeneric(output[2]) == a);
+ REQUIRE(handle_table.GetGeneric(output[3]) == b);
+ REQUIRE(handle_table.GetGeneric(output[5]) == c);
+ }
+}
+
+} // namespace Kernel
diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h
index f46db09fb..2d23d34e6 100644
--- a/src/video_core/pica_state.h
+++ b/src/video_core/pica_state.h
@@ -87,12 +87,18 @@ struct State {
// LUT value, encoded as 12-bit fixed point, with 12 fraction bits
BitField<0, 12, u32> value; // 0.0.12 fixed point
- // Used by HW for efficient interpolation, Citra does not use these
- BitField<12, 12, s32> difference; // 1.0.11 fixed point
+ // Used for efficient interpolation.
+ BitField<12, 11, u32> difference; // 0.0.11 fixed point
+ BitField<23, 1, u32> neg_difference;
- float ToFloat() {
+ float ToFloat() const {
return static_cast<float>(value) / 4095.f;
}
+
+ float DiffToFloat() const {
+ float diff = static_cast<float>(difference) / 2047.f;
+ return neg_difference ? -diff : diff;
+ }
};
std::array<std::array<LutEntry, 256>, 24> luts;
@@ -105,6 +111,14 @@ struct State {
BitField<0, 13, s32> difference; // 1.1.11 fixed point
BitField<13, 11, u32> value; // 0.0.11 fixed point
+
+ float ToFloat() const {
+ return static_cast<float>(value) / 2047.0f;
+ }
+
+ float DiffToFloat() const {
+ return static_cast<float>(difference) / 2047.0f;
+ }
};
std::array<LutEntry, 128> lut;
diff --git a/src/video_core/regs_lighting.h b/src/video_core/regs_lighting.h
index 6793405d9..b89709cfe 100644
--- a/src/video_core/regs_lighting.h
+++ b/src/video_core/regs_lighting.h
@@ -26,6 +26,18 @@ struct LightingRegs {
DistanceAttenuation = 16,
};
+ static constexpr unsigned NumLightingSampler = 24;
+
+ static LightingSampler SpotlightAttenuationSampler(unsigned index) {
+ return static_cast<LightingSampler>(
+ static_cast<unsigned>(LightingSampler::SpotlightAttenuation) + index);
+ }
+
+ static LightingSampler DistanceAttenuationSampler(unsigned index) {
+ return static_cast<LightingSampler>(
+ static_cast<unsigned>(LightingSampler::DistanceAttenuation) + index);
+ }
+
/**
* Pica fragment lighting supports using different LUTs for each lighting component: Reflectance
* R, G, and B channels, distribution function for specular components 0 and 1, fresnel factor,
@@ -73,6 +85,8 @@ struct LightingRegs {
VH = 1, // Cosine of the angle between the view and half-angle vectors
NV = 2, // Cosine of the angle between the normal and the view vector
LN = 3, // Cosine of the angle between the light and the normal vectors
+ SP = 4, // Cosine of the angle between the light and the inverse spotlight vectors
+ CP = 5, // Cosine of the angle between the tangent and projection of half-angle vectors
};
enum class LightingBumpMode : u32 {
@@ -104,6 +118,9 @@ struct LightingRegs {
return (config != LightingConfig::Config0) && (config != LightingConfig::Config1) &&
(config != LightingConfig::Config5);
+ case LightingSampler::SpotlightAttenuation:
+ return (config != LightingConfig::Config2) && (config != LightingConfig::Config3);
+
case LightingSampler::Fresnel:
return (config != LightingConfig::Config0) && (config != LightingConfig::Config2) &&
(config != LightingConfig::Config4);
@@ -116,11 +133,10 @@ struct LightingRegs {
return (config == LightingConfig::Config4) || (config == LightingConfig::Config5) ||
(config == LightingConfig::Config7);
default:
- UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached "
- "unreachable section, sampler should be one "
- "of Distribution0, Distribution1, Fresnel, "
- "ReflectRed, ReflectGreen or ReflectBlue, instead "
- "got %i",
+ UNREACHABLE_MSG("Regs::IsLightingSamplerSupported: Reached unreachable section, "
+ "sampler should be one of Distribution0, Distribution1, "
+ "SpotlightAttenuation, Fresnel, ReflectRed, ReflectGreen or "
+ "ReflectBlue, instead got %i",
static_cast<int>(config));
}
}
@@ -140,11 +156,22 @@ struct LightingRegs {
BitField<0, 16, u32> z;
};
- INSERT_PADDING_WORDS(0x3);
+ // inverse spotlight direction vector, encoded as fixed1.1.11
+ union {
+ BitField<0, 13, s32> spot_x;
+ BitField<16, 13, s32> spot_y;
+ };
+ union {
+ BitField<0, 13, s32> spot_z;
+ };
+
+ INSERT_PADDING_WORDS(0x1);
union {
BitField<0, 1, u32> directional;
BitField<1, 1, u32> two_sided_diffuse; // When disabled, clamp dot-product to 0
+ BitField<2, 1, u32> geometric_factor_0;
+ BitField<3, 1, u32> geometric_factor_1;
} config;
BitField<0, 20, u32> dist_atten_bias;
@@ -169,8 +196,16 @@ struct LightingRegs {
} config0;
union {
+ u32 raw;
+
+ // Each bit specifies whether spot light attenuation should be applied for the corresponding
+ // light.
+ BitField<8, 8, u32> disable_spot_atten;
+
BitField<16, 1, u32> disable_lut_d0;
BitField<17, 1, u32> disable_lut_d1;
+ // Note: by intuition, BitField<18, 1, u32> should be disable_lut_sp, but it is actually a
+ // dummy bit which is always set as 1.
BitField<19, 1, u32> disable_lut_fr;
BitField<20, 1, u32> disable_lut_rr;
BitField<21, 1, u32> disable_lut_rg;
@@ -178,23 +213,15 @@ struct LightingRegs {
// Each bit specifies whether distance attenuation should be applied for the corresponding
// light.
- BitField<24, 1, u32> disable_dist_atten_light_0;
- BitField<25, 1, u32> disable_dist_atten_light_1;
- BitField<26, 1, u32> disable_dist_atten_light_2;
- BitField<27, 1, u32> disable_dist_atten_light_3;
- BitField<28, 1, u32> disable_dist_atten_light_4;
- BitField<29, 1, u32> disable_dist_atten_light_5;
- BitField<30, 1, u32> disable_dist_atten_light_6;
- BitField<31, 1, u32> disable_dist_atten_light_7;
+ BitField<24, 8, u32> disable_dist_atten;
} config1;
bool IsDistAttenDisabled(unsigned index) const {
- const unsigned disable[] = {
- config1.disable_dist_atten_light_0, config1.disable_dist_atten_light_1,
- config1.disable_dist_atten_light_2, config1.disable_dist_atten_light_3,
- config1.disable_dist_atten_light_4, config1.disable_dist_atten_light_5,
- config1.disable_dist_atten_light_6, config1.disable_dist_atten_light_7};
- return disable[index] != 0;
+ return (config1.disable_dist_atten & (1 << index)) != 0;
+ }
+
+ bool IsSpotAttenDisabled(unsigned index) const {
+ return (config1.disable_spot_atten & (1 << index)) != 0;
}
union {
diff --git a/src/video_core/regs_texturing.h b/src/video_core/regs_texturing.h
index 3f5355fa9..0b09f2299 100644
--- a/src/video_core/regs_texturing.h
+++ b/src/video_core/regs_texturing.h
@@ -30,10 +30,10 @@ struct TexturingRegs {
Repeat = 2,
MirroredRepeat = 3,
// Mode 4-7 produces some weird result and may be just invalid:
- // 4: Positive coord: clamp to edge; negative coord: repeat
- // 5: Positive coord: clamp to border; negative coord: repeat
- // 6: Repeat
- // 7: Repeat
+ ClampToEdge2 = 4, // Positive coord: clamp to edge; negative coord: repeat
+ ClampToBorder2 = 5, // Positive coord: clamp to border; negative coord: repeat
+ Repeat2 = 6, // Same as Repeat
+ Repeat3 = 7, // Same as Repeat
};
enum TextureFilter : u32 {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index aa9b831dd..ff3f69ba3 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -49,9 +49,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
uniform_block_data.dirty = true;
- for (unsigned index = 0; index < lighting_luts.size(); index++) {
- uniform_block_data.lut_dirty[index] = true;
- }
+ uniform_block_data.lut_dirty.fill(true);
uniform_block_data.fog_lut_dirty = true;
@@ -96,36 +94,32 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
framebuffer.Create();
// Allocate and bind lighting lut textures
- for (size_t i = 0; i < lighting_luts.size(); ++i) {
- lighting_luts[i].Create();
- state.lighting_luts[i].texture_1d = lighting_luts[i].handle;
- }
+ lighting_lut.Create();
+ state.lighting_lut.texture_buffer = lighting_lut.handle;
state.Apply();
-
- for (size_t i = 0; i < lighting_luts.size(); ++i) {
- glActiveTexture(static_cast<GLenum>(GL_TEXTURE3 + i));
- glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr);
- glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- }
+ lighting_lut_buffer.Create();
+ glBindBuffer(GL_TEXTURE_BUFFER, lighting_lut_buffer.handle);
+ glBufferData(GL_TEXTURE_BUFFER,
+ sizeof(GLfloat) * 2 * 256 * Pica::LightingRegs::NumLightingSampler, nullptr,
+ GL_DYNAMIC_DRAW);
+ glActiveTexture(TextureUnits::LightingLUT.Enum());
+ glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, lighting_lut_buffer.handle);
// Setup the LUT for the fog
- {
- fog_lut.Create();
- state.fog_lut.texture_1d = fog_lut.handle;
- }
+ fog_lut.Create();
+ state.fog_lut.texture_buffer = fog_lut.handle;
state.Apply();
-
- glActiveTexture(GL_TEXTURE9);
- glTexImage1D(GL_TEXTURE_1D, 0, GL_R32UI, 128, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
- glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ fog_lut_buffer.Create();
+ glBindBuffer(GL_TEXTURE_BUFFER, fog_lut_buffer.handle);
+ glBufferData(GL_TEXTURE_BUFFER, sizeof(GLfloat) * 2 * 128, nullptr, GL_DYNAMIC_DRAW);
+ glActiveTexture(TextureUnits::FogLUT.Enum());
+ glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, fog_lut_buffer.handle);
// Setup the noise LUT for proctex
proctex_noise_lut.Create();
state.proctex_noise_lut.texture_1d = proctex_noise_lut.handle;
state.Apply();
- glActiveTexture(GL_TEXTURE10);
+ glActiveTexture(TextureUnits::ProcTexNoiseLUT.Enum());
glTexImage1D(GL_TEXTURE_1D, 0, GL_RG32F, 128, 0, GL_RG, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -134,7 +128,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
proctex_color_map.Create();
state.proctex_color_map.texture_1d = proctex_color_map.handle;
state.Apply();
- glActiveTexture(GL_TEXTURE11);
+ glActiveTexture(TextureUnits::ProcTexColorMap.Enum());
glTexImage1D(GL_TEXTURE_1D, 0, GL_RG32F, 128, 0, GL_RG, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -143,7 +137,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
proctex_alpha_map.Create();
state.proctex_alpha_map.texture_1d = proctex_alpha_map.handle;
state.Apply();
- glActiveTexture(GL_TEXTURE12);
+ glActiveTexture(TextureUnits::ProcTexAlphaMap.Enum());
glTexImage1D(GL_TEXTURE_1D, 0, GL_RG32F, 128, 0, GL_RG, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -152,7 +146,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
proctex_lut.Create();
state.proctex_lut.texture_1d = proctex_lut.handle;
state.Apply();
- glActiveTexture(GL_TEXTURE13);
+ glActiveTexture(TextureUnits::ProcTexLUT.Enum());
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -161,7 +155,7 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
proctex_diff_lut.Create();
state.proctex_diff_lut.texture_1d = proctex_diff_lut.handle;
state.Apply();
- glActiveTexture(GL_TEXTURE14);
+ glActiveTexture(TextureUnits::ProcTexDiffLUT.Enum());
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F, 256, 0, GL_RGBA, GL_FLOAT, nullptr);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
@@ -182,19 +176,22 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
RasterizerOpenGL::~RasterizerOpenGL() {}
/**
- * This is a helper function to resolve an issue with opposite quaternions being interpolated by
- * OpenGL. See below for a detailed description of this issue (yuriks):
+ * This is a helper function to resolve an issue when interpolating opposite quaternions. See below
+ * for a detailed description of this issue (yuriks):
*
* For any rotation, there are two quaternions Q, and -Q, that represent the same rotation. If you
* interpolate two quaternions that are opposite, instead of going from one rotation to another
* using the shortest path, you'll go around the longest path. You can test if two quaternions are
- * opposite by checking if Dot(Q1, W2) < 0. In that case, you can flip either of them, therefore
- * making Dot(-Q1, W2) positive.
+ * opposite by checking if Dot(Q1, Q2) < 0. In that case, you can flip either of them, therefore
+ * making Dot(Q1, -Q2) positive.
+ *
+ * This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This is
+ * correct for most cases but can still rotate around the long way sometimes. An implementation
+ * which did `lerp(lerp(Q1, Q2), Q3)` (with proper weighting), applying the dot product check
+ * between each step would work for those cases at the cost of being more complex to implement.
*
- * NOTE: This solution corrects this issue per-vertex before passing the quaternions to OpenGL. This
- * should be correct for nearly all cases, however a more correct implementation (but less trivial
- * and perhaps unnecessary) would be to handle this per-fragment, by interpolating the quaternions
- * manually using two Lerps, and doing this correction before each Lerp.
+ * Fortunately however, the 3DS hardware happens to also use this exact same logic to work around
+ * these issues, making this basic implementation actually more accurate to the hardware.
*/
static bool AreQuaternionsOpposite(Math::Vec4<Pica::float24> qa, Math::Vec4<Pica::float24> qb) {
Math::Vec4f a{qa.x.ToFloat32(), qa.y.ToFloat32(), qa.z.ToFloat32(), qa.w.ToFloat32()};
@@ -310,7 +307,7 @@ void RasterizerOpenGL::DrawTriangles() {
}
// Sync the lighting luts
- for (unsigned index = 0; index < lighting_luts.size(); index++) {
+ for (unsigned index = 0; index < uniform_block_data.lut_dirty.size(); index++) {
if (uniform_block_data.lut_dirty[index]) {
SyncLightingLUT(index);
uniform_block_data.lut_dirty[index] = false;
@@ -735,6 +732,40 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
SyncLightPosition(7);
break;
+ // Fragment spot lighting direction
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[0].spot_x, 0x146 + 0 * 0x10):
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[0].spot_z, 0x147 + 0 * 0x10):
+ SyncLightSpotDirection(0);
+ break;
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[1].spot_x, 0x146 + 1 * 0x10):
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[1].spot_z, 0x147 + 1 * 0x10):
+ SyncLightSpotDirection(1);
+ break;
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[2].spot_x, 0x146 + 2 * 0x10):
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[2].spot_z, 0x147 + 2 * 0x10):
+ SyncLightSpotDirection(2);
+ break;
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[3].spot_x, 0x146 + 3 * 0x10):
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[3].spot_z, 0x147 + 3 * 0x10):
+ SyncLightSpotDirection(3);
+ break;
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[4].spot_x, 0x146 + 4 * 0x10):
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[4].spot_z, 0x147 + 4 * 0x10):
+ SyncLightSpotDirection(4);
+ break;
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[5].spot_x, 0x146 + 5 * 0x10):
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[5].spot_z, 0x147 + 5 * 0x10):
+ SyncLightSpotDirection(5);
+ break;
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[6].spot_x, 0x146 + 6 * 0x10):
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[6].spot_z, 0x147 + 6 * 0x10):
+ SyncLightSpotDirection(6);
+ break;
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[7].spot_x, 0x146 + 7 * 0x10):
+ case PICA_REG_INDEX_WORKAROUND(lighting.light[7].spot_z, 0x147 + 7 * 0x10):
+ SyncLightSpotDirection(7);
+ break;
+
// Fragment lighting light source config
case PICA_REG_INDEX_WORKAROUND(lighting.light[0].config, 0x149 + 0 * 0x10):
case PICA_REG_INDEX_WORKAROUND(lighting.light[1].config, 0x149 + 1 * 0x10):
@@ -814,7 +845,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
case PICA_REG_INDEX_WORKAROUND(lighting.lut_data[6], 0x1ce):
case PICA_REG_INDEX_WORKAROUND(lighting.lut_data[7], 0x1cf): {
auto& lut_config = regs.lighting.lut_config;
- uniform_block_data.lut_dirty[lut_config.type / 4] = true;
+ uniform_block_data.lut_dirty[lut_config.type] = true;
break;
}
}
@@ -1150,77 +1181,57 @@ void RasterizerOpenGL::SetShader() {
state.Apply();
// Set the texture samplers to correspond to different texture units
- GLuint uniform_tex = glGetUniformLocation(shader->shader.handle, "tex[0]");
+ GLint uniform_tex = glGetUniformLocation(shader->shader.handle, "tex[0]");
if (uniform_tex != -1) {
- glUniform1i(uniform_tex, 0);
+ glUniform1i(uniform_tex, TextureUnits::PicaTexture(0).id);
}
uniform_tex = glGetUniformLocation(shader->shader.handle, "tex[1]");
if (uniform_tex != -1) {
- glUniform1i(uniform_tex, 1);
+ glUniform1i(uniform_tex, TextureUnits::PicaTexture(1).id);
}
uniform_tex = glGetUniformLocation(shader->shader.handle, "tex[2]");
if (uniform_tex != -1) {
- glUniform1i(uniform_tex, 2);
+ glUniform1i(uniform_tex, TextureUnits::PicaTexture(2).id);
}
// Set the texture samplers to correspond to different lookup table texture units
- GLuint uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[0]");
+ GLint uniform_lut = glGetUniformLocation(shader->shader.handle, "lighting_lut");
if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 3);
- }
- uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[1]");
- if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 4);
- }
- uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[2]");
- if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 5);
- }
- uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[3]");
- if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 6);
- }
- uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[4]");
- if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 7);
- }
- uniform_lut = glGetUniformLocation(shader->shader.handle, "lut[5]");
- if (uniform_lut != -1) {
- glUniform1i(uniform_lut, 8);
+ glUniform1i(uniform_lut, TextureUnits::LightingLUT.id);
}
- GLuint uniform_fog_lut = glGetUniformLocation(shader->shader.handle, "fog_lut");
+ GLint uniform_fog_lut = glGetUniformLocation(shader->shader.handle, "fog_lut");
if (uniform_fog_lut != -1) {
- glUniform1i(uniform_fog_lut, 9);
+ glUniform1i(uniform_fog_lut, TextureUnits::FogLUT.id);
}
- GLuint uniform_proctex_noise_lut =
+ GLint uniform_proctex_noise_lut =
glGetUniformLocation(shader->shader.handle, "proctex_noise_lut");
if (uniform_proctex_noise_lut != -1) {
- glUniform1i(uniform_proctex_noise_lut, 10);
+ glUniform1i(uniform_proctex_noise_lut, TextureUnits::ProcTexNoiseLUT.id);
}
- GLuint uniform_proctex_color_map =
+ GLint uniform_proctex_color_map =
glGetUniformLocation(shader->shader.handle, "proctex_color_map");
if (uniform_proctex_color_map != -1) {
- glUniform1i(uniform_proctex_color_map, 11);
+ glUniform1i(uniform_proctex_color_map, TextureUnits::ProcTexColorMap.id);
}
- GLuint uniform_proctex_alpha_map =
+ GLint uniform_proctex_alpha_map =
glGetUniformLocation(shader->shader.handle, "proctex_alpha_map");
if (uniform_proctex_alpha_map != -1) {
- glUniform1i(uniform_proctex_alpha_map, 12);
+ glUniform1i(uniform_proctex_alpha_map, TextureUnits::ProcTexAlphaMap.id);
}
- GLuint uniform_proctex_lut = glGetUniformLocation(shader->shader.handle, "proctex_lut");
+ GLint uniform_proctex_lut = glGetUniformLocation(shader->shader.handle, "proctex_lut");
if (uniform_proctex_lut != -1) {
- glUniform1i(uniform_proctex_lut, 13);
+ glUniform1i(uniform_proctex_lut, TextureUnits::ProcTexLUT.id);
}
- GLuint uniform_proctex_diff_lut =
+ GLint uniform_proctex_diff_lut =
glGetUniformLocation(shader->shader.handle, "proctex_diff_lut");
if (uniform_proctex_diff_lut != -1) {
- glUniform1i(uniform_proctex_diff_lut, 14);
+ glUniform1i(uniform_proctex_diff_lut, TextureUnits::ProcTexDiffLUT.id);
}
current_shader = shader_cache.emplace(config, std::move(shader)).first->second.get();
@@ -1343,16 +1354,17 @@ void RasterizerOpenGL::SyncFogColor() {
}
void RasterizerOpenGL::SyncFogLUT() {
- std::array<GLuint, 128> new_data;
+ std::array<GLvec2, 128> new_data;
std::transform(Pica::g_state.fog.lut.begin(), Pica::g_state.fog.lut.end(), new_data.begin(),
- [](const auto& entry) { return entry.raw; });
+ [](const auto& entry) {
+ return GLvec2{entry.ToFloat(), entry.DiffToFloat()};
+ });
if (new_data != fog_lut_data) {
fog_lut_data = new_data;
- glActiveTexture(GL_TEXTURE9);
- glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 128, GL_RED_INTEGER, GL_UNSIGNED_INT,
- fog_lut_data.data());
+ glBindBuffer(GL_TEXTURE_BUFFER, fog_lut_buffer.handle);
+ glBufferSubData(GL_TEXTURE_BUFFER, 0, new_data.size() * sizeof(GLvec2), new_data.data());
}
}
@@ -1389,17 +1401,18 @@ static void SyncProcTexValueLUT(const std::array<Pica::State::ProcTex::ValueEntr
}
void RasterizerOpenGL::SyncProcTexNoiseLUT() {
- SyncProcTexValueLUT(Pica::g_state.proctex.noise_table, proctex_noise_lut_data, GL_TEXTURE10);
+ SyncProcTexValueLUT(Pica::g_state.proctex.noise_table, proctex_noise_lut_data,
+ TextureUnits::ProcTexNoiseLUT.Enum());
}
void RasterizerOpenGL::SyncProcTexColorMap() {
SyncProcTexValueLUT(Pica::g_state.proctex.color_map_table, proctex_color_map_data,
- GL_TEXTURE11);
+ TextureUnits::ProcTexColorMap.Enum());
}
void RasterizerOpenGL::SyncProcTexAlphaMap() {
SyncProcTexValueLUT(Pica::g_state.proctex.alpha_map_table, proctex_alpha_map_data,
- GL_TEXTURE12);
+ TextureUnits::ProcTexAlphaMap.Enum());
}
void RasterizerOpenGL::SyncProcTexLUT() {
@@ -1414,7 +1427,7 @@ void RasterizerOpenGL::SyncProcTexLUT() {
if (new_data != proctex_lut_data) {
proctex_lut_data = new_data;
- glActiveTexture(GL_TEXTURE13);
+ glActiveTexture(TextureUnits::ProcTexLUT.Enum());
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 256, GL_RGBA, GL_FLOAT, proctex_lut_data.data());
}
}
@@ -1431,7 +1444,7 @@ void RasterizerOpenGL::SyncProcTexDiffLUT() {
if (new_data != proctex_diff_lut_data) {
proctex_diff_lut_data = new_data;
- glActiveTexture(GL_TEXTURE14);
+ glActiveTexture(TextureUnits::ProcTexDiffLUT.Enum());
glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 256, GL_RGBA, GL_FLOAT, proctex_diff_lut_data.data());
}
}
@@ -1534,20 +1547,17 @@ void RasterizerOpenGL::SyncGlobalAmbient() {
}
void RasterizerOpenGL::SyncLightingLUT(unsigned lut_index) {
- std::array<GLvec4, 256> new_data;
-
- for (unsigned offset = 0; offset < new_data.size(); ++offset) {
- new_data[offset][0] = Pica::g_state.lighting.luts[(lut_index * 4) + 0][offset].ToFloat();
- new_data[offset][1] = Pica::g_state.lighting.luts[(lut_index * 4) + 1][offset].ToFloat();
- new_data[offset][2] = Pica::g_state.lighting.luts[(lut_index * 4) + 2][offset].ToFloat();
- new_data[offset][3] = Pica::g_state.lighting.luts[(lut_index * 4) + 3][offset].ToFloat();
- }
+ std::array<GLvec2, 256> new_data;
+ const auto& source_lut = Pica::g_state.lighting.luts[lut_index];
+ std::transform(source_lut.begin(), source_lut.end(), new_data.begin(), [](const auto& entry) {
+ return GLvec2{entry.ToFloat(), entry.DiffToFloat()};
+ });
if (new_data != lighting_lut_data[lut_index]) {
lighting_lut_data[lut_index] = new_data;
- glActiveTexture(GL_TEXTURE3 + lut_index);
- glTexSubImage1D(GL_TEXTURE_1D, 0, 0, 256, GL_RGBA, GL_FLOAT,
- lighting_lut_data[lut_index].data());
+ glBindBuffer(GL_TEXTURE_BUFFER, lighting_lut_buffer.handle);
+ glBufferSubData(GL_TEXTURE_BUFFER, lut_index * new_data.size() * sizeof(GLvec2),
+ new_data.size() * sizeof(GLvec2), new_data.data());
}
}
@@ -1595,6 +1605,17 @@ void RasterizerOpenGL::SyncLightPosition(int light_index) {
}
}
+void RasterizerOpenGL::SyncLightSpotDirection(int light_index) {
+ const auto& light = Pica::g_state.regs.lighting.light[light_index];
+ GLvec3 spot_direction = {light.spot_x / 2047.0f, light.spot_y / 2047.0f,
+ light.spot_z / 2047.0f};
+
+ if (spot_direction != uniform_block_data.data.light_src[light_index].spot_direction) {
+ uniform_block_data.data.light_src[light_index].spot_direction = spot_direction;
+ 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)
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index a9ad7d660..a433c1d4a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -125,6 +125,7 @@ private:
alignas(16) GLvec3 diffuse;
alignas(16) GLvec3 ambient;
alignas(16) GLvec3 position;
+ alignas(16) GLvec3 spot_direction; // negated
GLfloat dist_atten_bias;
GLfloat dist_atten_scale;
};
@@ -153,7 +154,7 @@ private:
};
static_assert(
- sizeof(UniformData) == 0x3E0,
+ sizeof(UniformData) == 0x460,
"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");
@@ -241,6 +242,9 @@ private:
/// Syncs the specified light's position to match the PICA register
void SyncLightPosition(int light_index);
+ /// Syncs the specified spot light direcition to match the PICA register
+ void SyncLightSpotDirection(int light_index);
+
/// Syncs the specified light's distance attenuation bias to match the PICA register
void SyncLightDistanceAttenuationBias(int light_index);
@@ -259,7 +263,7 @@ private:
struct {
UniformData data;
- bool lut_dirty[6];
+ std::array<bool, Pica::LightingRegs::NumLightingSampler> lut_dirty;
bool fog_lut_dirty;
bool proctex_noise_lut_dirty;
bool proctex_color_map_dirty;
@@ -275,11 +279,13 @@ private:
OGLBuffer uniform_buffer;
OGLFramebuffer framebuffer;
- std::array<OGLTexture, 6> lighting_luts;
- std::array<std::array<GLvec4, 256>, 6> lighting_lut_data{};
+ OGLBuffer lighting_lut_buffer;
+ OGLTexture lighting_lut;
+ std::array<std::array<GLvec2, 256>, Pica::LightingRegs::NumLightingSampler> lighting_lut_data{};
+ OGLBuffer fog_lut_buffer;
OGLTexture fog_lut;
- std::array<GLuint, 128> fog_lut_data{};
+ std::array<GLvec2, 128> fog_lut_data{};
OGLTexture proctex_noise_lut;
std::array<GLvec2, 128> proctex_noise_lut_data{};
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index ffe419863..c93b108fb 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -73,8 +73,12 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {
state.lighting.light[light_index].num = num;
state.lighting.light[light_index].directional = light.config.directional != 0;
state.lighting.light[light_index].two_sided_diffuse = light.config.two_sided_diffuse != 0;
+ state.lighting.light[light_index].geometric_factor_0 = light.config.geometric_factor_0 != 0;
+ state.lighting.light[light_index].geometric_factor_1 = light.config.geometric_factor_1 != 0;
state.lighting.light[light_index].dist_atten_enable =
!regs.lighting.IsDistAttenDisabled(num);
+ state.lighting.light[light_index].spot_atten_enable =
+ !regs.lighting.IsSpotAttenDisabled(num);
}
state.lighting.lut_d0.enable = regs.lighting.config1.disable_lut_d0 == 0;
@@ -87,6 +91,12 @@ PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) {
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);
+ // this is a dummy field due to lack of the corresponding register
+ state.lighting.lut_sp.enable = true;
+ state.lighting.lut_sp.abs_input = regs.lighting.abs_lut_input.disable_sp == 0;
+ state.lighting.lut_sp.type = regs.lighting.lut_input.sp.Value();
+ state.lighting.lut_sp.scale = regs.lighting.lut_scale.GetScale(regs.lighting.lut_scale.sp);
+
state.lighting.lut_fr.enable = regs.lighting.config1.disable_lut_fr == 0;
state.lighting.lut_fr.abs_input = regs.lighting.abs_lut_input.disable_fr == 0;
state.lighting.lut_fr.type = regs.lighting.lut_input.fr.Value();
@@ -509,14 +519,17 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
out += "vec4 diffuse_sum = vec4(0.0, 0.0, 0.0, 1.0);\n"
"vec4 specular_sum = vec4(0.0, 0.0, 0.0, 1.0);\n"
"vec3 light_vector = vec3(0.0);\n"
- "vec3 refl_value = vec3(0.0);\n";
-
- // Compute fragment normals
+ "vec3 refl_value = vec3(0.0);\n"
+ "vec3 spot_dir = vec3(0.0);\n"
+ "vec3 half_vector = vec3(0.0);\n"
+ "float geo_factor = 1.0;\n";
+
+ // Compute fragment normals and tangents
+ const std::string pertubation =
+ "2.0 * (" + SampleTexture(config, lighting.bump_selector) + ").rgb - 1.0";
if (lighting.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {
- // Bump mapping is enabled using a normal map, read perturbation vector from the selected
- // texture
- out += "vec3 surface_normal = 2.0 * (" + SampleTexture(config, lighting.bump_selector) +
- ").rgb - 1.0;\n";
+ // Bump mapping is enabled using a normal map
+ out += "vec3 surface_normal = " + pertubation + ";\n";
// Recompute Z-component of perturbation if 'renorm' is enabled, this provides a higher
// precision result
@@ -525,31 +538,41 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
"(1.0 - (surface_normal.x*surface_normal.x + surface_normal.y*surface_normal.y))";
out += "surface_normal.z = sqrt(max(" + val + ", 0.0));\n";
}
+
+ // The tangent vector is not perturbed by the normal map and is just a unit vector.
+ out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";
} else if (lighting.bump_mode == LightingRegs::LightingBumpMode::TangentMap) {
// Bump mapping is enabled using a tangent map
- LOG_CRITICAL(HW_GPU, "unimplemented bump mapping mode (tangent mapping)");
- UNIMPLEMENTED();
+ out += "vec3 surface_tangent = " + pertubation + ";\n";
+ // Mathematically, recomputing Z-component of the tangent vector won't affect the relevant
+ // computation below, which is also confirmed on 3DS. So we don't bother recomputing here
+ // even if 'renorm' is enabled.
+
+ // The normal vector is not perturbed by the tangent map and is just a unit vector.
+ out += "vec3 surface_normal = vec3(0.0, 0.0, 1.0);\n";
} else {
- // No bump mapping - surface local normal is just a unit normal
+ // No bump mapping - surface local normal and tangent are just unit vectors
out += "vec3 surface_normal = vec3(0.0, 0.0, 1.0);\n";
+ out += "vec3 surface_tangent = vec3(1.0, 0.0, 0.0);\n";
}
// Rotate the surface-local normal by the interpolated normal quaternion to convert it to
// eyespace.
- out += "vec3 normal = quaternion_rotate(normalize(normquat), surface_normal);\n";
+ out += "vec4 normalized_normquat = normalize(normquat);\n";
+ out += "vec3 normal = quaternion_rotate(normalized_normquat, surface_normal);\n";
+ out += "vec3 tangent = quaternion_rotate(normalized_normquat, surface_tangent);\n";
- // Gets the index into the specified lookup table for specular lighting
- auto GetLutIndex = [&lighting](unsigned light_num, LightingRegs::LightingLutInput input,
- bool abs) {
- const std::string half_angle = "normalize(normalize(view) + light_vector)";
+ // Samples the specified lookup table for specular lighting
+ auto GetLutValue = [&lighting](LightingRegs::LightingSampler sampler, unsigned light_num,
+ LightingRegs::LightingLutInput input, bool abs) {
std::string index;
switch (input) {
case LightingRegs::LightingLutInput::NH:
- index = "dot(normal, " + half_angle + ")";
+ index = "dot(normal, normalize(half_vector))";
break;
case LightingRegs::LightingLutInput::VH:
- index = std::string("dot(normalize(view), " + half_angle + ")");
+ index = std::string("dot(normalize(view), normalize(half_vector))");
break;
case LightingRegs::LightingLutInput::NV:
@@ -560,6 +583,26 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
index = std::string("dot(light_vector, normal)");
break;
+ case LightingRegs::LightingLutInput::SP:
+ index = std::string("dot(light_vector, spot_dir)");
+ break;
+
+ case LightingRegs::LightingLutInput::CP:
+ // CP input is only available with configuration 7
+ if (lighting.config == LightingRegs::LightingConfig::Config7) {
+ // Note: even if the normal vector is modified by normal map, which is not the
+ // normal of the tangent plane anymore, the half angle vector is still projected
+ // using the modified normal vector.
+ std::string half_angle_proj = "normalize(half_vector) - normal / dot(normal, "
+ "normal) * dot(normal, normalize(half_vector))";
+ // Note: the half angle vector projection is confirmed not normalized before the dot
+ // product. The result is in fact not cos(phi) as the name suggested.
+ index = "dot(" + half_angle_proj + ", tangent)";
+ } else {
+ index = "0.0";
+ }
+ break;
+
default:
LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %d\n", (int)input);
UNIMPLEMENTED();
@@ -567,22 +610,18 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
break;
}
+ std::string sampler_string = std::to_string(static_cast<unsigned>(sampler));
+
if (abs) {
// LUT index is in the range of (0.0, 1.0)
index = lighting.light[light_num].two_sided_diffuse ? "abs(" + index + ")"
: "max(" + index + ", 0.0)";
+ return "LookupLightingLUTUnsigned(" + sampler_string + ", " + index + ")";
} else {
// LUT index is in the range of (-1.0, 1.0)
- index = "((" + index + " < 0) ? " + index + " + 2.0 : " + index + ") / 2.0";
+ return "LookupLightingLUTSigned(" + sampler_string + ", " + index + ")";
}
- return "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))";
- };
-
- // Gets the lighting lookup table value given the specified sampler and index
- auto GetLutValue = [](LightingRegs::LightingSampler sampler, std::string lut_index) {
- return std::string("texture(lut[" + std::to_string((unsigned)sampler / 4) + "], " +
- lut_index + ")[" + std::to_string((unsigned)sampler & 3) + "]");
};
// Write the code to emulate each enabled light
@@ -596,48 +635,71 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
else
out += "light_vector = normalize(" + light_src + ".position + view);\n";
+ out += "spot_dir = " + light_src + ".spot_direction;\n";
+ out += "half_vector = normalize(view) + light_vector;\n";
+
// Compute dot product of light_vector and normal, adjust if lighting is one-sided or
// two-sided
std::string dot_product = light_config.two_sided_diffuse
? "abs(dot(light_vector, normal))"
: "max(dot(light_vector, normal), 0.0)";
+ // If enabled, compute spot light attenuation value
+ std::string spot_atten = "1.0";
+ if (light_config.spot_atten_enable &&
+ LightingRegs::IsLightingSamplerSupported(
+ lighting.config, LightingRegs::LightingSampler::SpotlightAttenuation)) {
+ std::string value =
+ GetLutValue(LightingRegs::SpotlightAttenuationSampler(light_config.num),
+ light_config.num, lighting.lut_sp.type, lighting.lut_sp.abs_input);
+ spot_atten = "(" + std::to_string(lighting.lut_sp.scale) + " * " + value + ")";
+ }
+
// If enabled, compute distance attenuation value
std::string dist_atten = "1.0";
if (light_config.dist_atten_enable) {
- std::string index = "(" + light_src + ".dist_atten_scale * length(-view - " +
- light_src + ".position) + " + light_src + ".dist_atten_bias)";
- index = "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))";
- const unsigned lut_num =
- ((unsigned)LightingRegs::LightingSampler::DistanceAttenuation + light_config.num);
- dist_atten = GetLutValue((LightingRegs::LightingSampler)lut_num, index);
+ std::string index = "clamp(" + light_src + ".dist_atten_scale * length(-view - " +
+ light_src + ".position) + " + light_src +
+ ".dist_atten_bias, 0.0, 1.0)";
+ auto sampler = LightingRegs::DistanceAttenuationSampler(light_config.num);
+ dist_atten = "LookupLightingLUTUnsigned(" +
+ std::to_string(static_cast<unsigned>(sampler)) + "," + index + ")";
}
// If enabled, clamp specular component if lighting result is negative
std::string clamp_highlights =
lighting.clamp_highlights ? "(dot(light_vector, normal) <= 0.0 ? 0.0 : 1.0)" : "1.0";
+ if (light_config.geometric_factor_0 || light_config.geometric_factor_1) {
+ out += "geo_factor = dot(half_vector, half_vector);\n"
+ "geo_factor = geo_factor == 0.0 ? 0.0 : min(" +
+ dot_product + " / geo_factor, 1.0);\n";
+ }
+
// Specular 0 component
std::string d0_lut_value = "1.0";
if (lighting.lut_d0.enable &&
LightingRegs::IsLightingSamplerSupported(
lighting.config, LightingRegs::LightingSampler::Distribution0)) {
// Lookup specular "distribution 0" LUT value
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_d0.type, lighting.lut_d0.abs_input);
- d0_lut_value = "(" + std::to_string(lighting.lut_d0.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::Distribution0, index) + ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::Distribution0, light_config.num,
+ lighting.lut_d0.type, lighting.lut_d0.abs_input);
+ d0_lut_value = "(" + std::to_string(lighting.lut_d0.scale) + " * " + value + ")";
}
std::string specular_0 = "(" + d0_lut_value + " * " + light_src + ".specular_0)";
+ if (light_config.geometric_factor_0) {
+ specular_0 = "(" + specular_0 + " * geo_factor)";
+ }
// If enabled, lookup ReflectRed value, otherwise, 1.0 is used
if (lighting.lut_rr.enable &&
LightingRegs::IsLightingSamplerSupported(lighting.config,
LightingRegs::LightingSampler::ReflectRed)) {
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_rr.type, lighting.lut_rr.abs_input);
- std::string value = "(" + std::to_string(lighting.lut_rr.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::ReflectRed, index) + ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::ReflectRed, light_config.num,
+ lighting.lut_rr.type, lighting.lut_rr.abs_input);
+ value = "(" + std::to_string(lighting.lut_rr.scale) + " * " + value + ")";
out += "refl_value.r = " + value + ";\n";
} else {
out += "refl_value.r = 1.0;\n";
@@ -647,11 +709,10 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
if (lighting.lut_rg.enable &&
LightingRegs::IsLightingSamplerSupported(lighting.config,
LightingRegs::LightingSampler::ReflectGreen)) {
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_rg.type, lighting.lut_rg.abs_input);
- std::string value = "(" + std::to_string(lighting.lut_rg.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::ReflectGreen, index) +
- ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::ReflectGreen, light_config.num,
+ lighting.lut_rg.type, lighting.lut_rg.abs_input);
+ value = "(" + std::to_string(lighting.lut_rg.scale) + " * " + value + ")";
out += "refl_value.g = " + value + ";\n";
} else {
out += "refl_value.g = refl_value.r;\n";
@@ -661,11 +722,10 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
if (lighting.lut_rb.enable &&
LightingRegs::IsLightingSamplerSupported(lighting.config,
LightingRegs::LightingSampler::ReflectBlue)) {
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_rb.type, lighting.lut_rb.abs_input);
- std::string value = "(" + std::to_string(lighting.lut_rb.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::ReflectBlue, index) +
- ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::ReflectBlue, light_config.num,
+ lighting.lut_rb.type, lighting.lut_rb.abs_input);
+ value = "(" + std::to_string(lighting.lut_rb.scale) + " * " + value + ")";
out += "refl_value.b = " + value + ";\n";
} else {
out += "refl_value.b = refl_value.r;\n";
@@ -677,23 +737,26 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
LightingRegs::IsLightingSamplerSupported(
lighting.config, LightingRegs::LightingSampler::Distribution1)) {
// Lookup specular "distribution 1" LUT value
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_d1.type, lighting.lut_d1.abs_input);
- d1_lut_value = "(" + std::to_string(lighting.lut_d1.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::Distribution1, index) + ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::Distribution1, light_config.num,
+ lighting.lut_d1.type, lighting.lut_d1.abs_input);
+ d1_lut_value = "(" + std::to_string(lighting.lut_d1.scale) + " * " + value + ")";
}
std::string specular_1 =
"(" + d1_lut_value + " * refl_value * " + light_src + ".specular_1)";
+ if (light_config.geometric_factor_1) {
+ specular_1 = "(" + specular_1 + " * geo_factor)";
+ }
// Fresnel
if (lighting.lut_fr.enable &&
LightingRegs::IsLightingSamplerSupported(lighting.config,
LightingRegs::LightingSampler::Fresnel)) {
// Lookup fresnel LUT value
- std::string index =
- GetLutIndex(light_config.num, lighting.lut_fr.type, lighting.lut_fr.abs_input);
- std::string value = "(" + std::to_string(lighting.lut_fr.scale) + " * " +
- GetLutValue(LightingRegs::LightingSampler::Fresnel, index) + ")";
+ std::string value =
+ GetLutValue(LightingRegs::LightingSampler::Fresnel, light_config.num,
+ lighting.lut_fr.type, lighting.lut_fr.abs_input);
+ value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + value + ")";
// Enabled for difffuse lighting alpha component
if (lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::PrimaryAlpha ||
@@ -711,11 +774,11 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
// Compute primary fragment color (diffuse lighting) function
out += "diffuse_sum.rgb += ((" + light_src + ".diffuse * " + dot_product + ") + " +
- light_src + ".ambient) * " + dist_atten + ";\n";
+ light_src + ".ambient) * " + dist_atten + " * " + spot_atten + ";\n";
// Compute secondary fragment color (specular lighting) function
out += "specular_sum.rgb += (" + specular_0 + " + " + specular_1 + ") * " +
- clamp_highlights + " * " + dist_atten + ";\n";
+ clamp_highlights + " * " + dist_atten + " * " + spot_atten + ";\n";
}
// Sum final lighting result
@@ -947,10 +1010,6 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) {
#define NUM_TEV_STAGES 6
#define NUM_LIGHTS 8
-// Texture coordinate offsets and scales
-#define OFFSET_256 (0.5 / 256.0)
-#define SCALE_256 (255.0 / 256.0)
-
in vec4 primary_color;
in vec2 texcoord[3];
in float texcoord0_w;
@@ -967,6 +1026,7 @@ struct LightSrc {
vec3 diffuse;
vec3 ambient;
vec3 position;
+ vec3 spot_direction;
float dist_atten_bias;
float dist_atten_scale;
};
@@ -991,8 +1051,8 @@ layout (std140) uniform shader_data {
};
uniform sampler2D tex[3];
-uniform sampler1D lut[6];
-uniform usampler1D fog_lut;
+uniform samplerBuffer lighting_lut;
+uniform samplerBuffer fog_lut;
uniform sampler1D proctex_noise_lut;
uniform sampler1D proctex_color_map;
uniform sampler1D proctex_alpha_map;
@@ -1004,6 +1064,24 @@ vec3 quaternion_rotate(vec4 q, vec3 v) {
return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v);
}
+float LookupLightingLUT(int lut_index, int index, float delta) {
+ vec2 entry = texelFetch(lighting_lut, lut_index * 256 + index).rg;
+ return entry.r + entry.g * delta;
+}
+
+float LookupLightingLUTUnsigned(int lut_index, float pos) {
+ int index = clamp(int(pos * 256.0), 0, 255);
+ float delta = pos * 256.0 - index;
+ return LookupLightingLUT(lut_index, index, delta);
+}
+
+float LookupLightingLUTSigned(int lut_index, float pos) {
+ int index = clamp(int(pos * 128.0), -128, 127);
+ float delta = pos * 128.0 - index;
+ if (index < 0) index += 256;
+ return LookupLightingLUT(lut_index, index, delta);
+}
+
)";
if (config.state.proctex.enable)
@@ -1067,12 +1145,8 @@ vec4 secondary_fragment_color = vec4(0.0);
// Generate clamped fog factor from LUT for given fog index
out += "float fog_i = clamp(floor(fog_index), 0.0, 127.0);\n";
out += "float fog_f = fog_index - fog_i;\n";
- out += "uint fog_lut_entry = texelFetch(fog_lut, int(fog_i), 0).r;\n";
- out += "float fog_lut_entry_difference = float(int((fog_lut_entry & 0x1FFFU) << 19U) >> "
- "19);\n"; // Extract signed difference
- out += "float fog_lut_entry_value = float((fog_lut_entry >> 13U) & 0x7FFU);\n";
- out += "float fog_factor = (fog_lut_entry_value + fog_lut_entry_difference * fog_f) / "
- "2047.0;\n";
+ out += "vec2 fog_lut_entry = texelFetch(fog_lut, int(fog_i)).rg;\n";
+ out += "float fog_factor = fog_lut_entry.r + fog_lut_entry.g * fog_f;\n";
out += "fog_factor = clamp(fog_factor, 0.0, 1.0);\n";
// Blend the fog
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.h b/src/video_core/renderer_opengl/gl_shader_gen.h
index ea6d216d1..2302ae453 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.h
+++ b/src/video_core/renderer_opengl/gl_shader_gen.h
@@ -93,6 +93,9 @@ union PicaShaderConfig {
bool directional;
bool two_sided_diffuse;
bool dist_atten_enable;
+ bool spot_atten_enable;
+ bool geometric_factor_0;
+ bool geometric_factor_1;
} light[8];
bool enable;
@@ -110,7 +113,7 @@ union PicaShaderConfig {
bool abs_input;
Pica::LightingRegs::LightingLutInput type;
float scale;
- } lut_d0, lut_d1, lut_fr, lut_rr, lut_rg, lut_rb;
+ } lut_d0, lut_d1, lut_sp, lut_fr, lut_rr, lut_rg, lut_rb;
} lighting;
struct {
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index bf837a7fb..eface2dea 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -52,11 +52,9 @@ OpenGLState::OpenGLState() {
texture_unit.sampler = 0;
}
- for (auto& lut : lighting_luts) {
- lut.texture_1d = 0;
- }
+ lighting_lut.texture_buffer = 0;
- fog_lut.texture_1d = 0;
+ fog_lut.texture_buffer = 0;
proctex_lut.texture_1d = 0;
proctex_diff_lut.texture_1d = 0;
@@ -185,7 +183,7 @@ void OpenGLState::Apply() const {
// Textures
for (unsigned i = 0; i < ARRAY_SIZE(texture_units); ++i) {
if (texture_units[i].texture_2d != cur_state.texture_units[i].texture_2d) {
- glActiveTexture(GL_TEXTURE0 + i);
+ glActiveTexture(TextureUnits::PicaTexture(i).Enum());
glBindTexture(GL_TEXTURE_2D, texture_units[i].texture_2d);
}
if (texture_units[i].sampler != cur_state.texture_units[i].sampler) {
@@ -194,46 +192,44 @@ void OpenGLState::Apply() const {
}
// Lighting LUTs
- for (unsigned i = 0; i < ARRAY_SIZE(lighting_luts); ++i) {
- if (lighting_luts[i].texture_1d != cur_state.lighting_luts[i].texture_1d) {
- glActiveTexture(GL_TEXTURE3 + i);
- glBindTexture(GL_TEXTURE_1D, lighting_luts[i].texture_1d);
- }
+ if (lighting_lut.texture_buffer != cur_state.lighting_lut.texture_buffer) {
+ glActiveTexture(TextureUnits::LightingLUT.Enum());
+ glBindTexture(GL_TEXTURE_BUFFER, cur_state.lighting_lut.texture_buffer);
}
// Fog LUT
- if (fog_lut.texture_1d != cur_state.fog_lut.texture_1d) {
- glActiveTexture(GL_TEXTURE9);
- glBindTexture(GL_TEXTURE_1D, fog_lut.texture_1d);
+ if (fog_lut.texture_buffer != cur_state.fog_lut.texture_buffer) {
+ glActiveTexture(TextureUnits::FogLUT.Enum());
+ glBindTexture(GL_TEXTURE_BUFFER, fog_lut.texture_buffer);
}
// ProcTex Noise LUT
if (proctex_noise_lut.texture_1d != cur_state.proctex_noise_lut.texture_1d) {
- glActiveTexture(GL_TEXTURE10);
+ glActiveTexture(TextureUnits::ProcTexNoiseLUT.Enum());
glBindTexture(GL_TEXTURE_1D, proctex_noise_lut.texture_1d);
}
// ProcTex Color Map
if (proctex_color_map.texture_1d != cur_state.proctex_color_map.texture_1d) {
- glActiveTexture(GL_TEXTURE11);
+ glActiveTexture(TextureUnits::ProcTexColorMap.Enum());
glBindTexture(GL_TEXTURE_1D, proctex_color_map.texture_1d);
}
// ProcTex Alpha Map
if (proctex_alpha_map.texture_1d != cur_state.proctex_alpha_map.texture_1d) {
- glActiveTexture(GL_TEXTURE12);
+ glActiveTexture(TextureUnits::ProcTexAlphaMap.Enum());
glBindTexture(GL_TEXTURE_1D, proctex_alpha_map.texture_1d);
}
// ProcTex LUT
if (proctex_lut.texture_1d != cur_state.proctex_lut.texture_1d) {
- glActiveTexture(GL_TEXTURE13);
+ glActiveTexture(TextureUnits::ProcTexLUT.Enum());
glBindTexture(GL_TEXTURE_1D, proctex_lut.texture_1d);
}
// ProcTex Diff LUT
if (proctex_diff_lut.texture_1d != cur_state.proctex_diff_lut.texture_1d) {
- glActiveTexture(GL_TEXTURE14);
+ glActiveTexture(TextureUnits::ProcTexDiffLUT.Enum());
glBindTexture(GL_TEXTURE_1D, proctex_diff_lut.texture_1d);
}
@@ -274,6 +270,20 @@ void OpenGLState::ResetTexture(GLuint handle) {
unit.texture_2d = 0;
}
}
+ if (cur_state.lighting_lut.texture_buffer == handle)
+ cur_state.lighting_lut.texture_buffer = 0;
+ if (cur_state.fog_lut.texture_buffer == handle)
+ cur_state.fog_lut.texture_buffer = 0;
+ if (cur_state.proctex_noise_lut.texture_1d == handle)
+ cur_state.proctex_noise_lut.texture_1d = 0;
+ if (cur_state.proctex_color_map.texture_1d == handle)
+ cur_state.proctex_color_map.texture_1d = 0;
+ if (cur_state.proctex_alpha_map.texture_1d == handle)
+ cur_state.proctex_alpha_map.texture_1d = 0;
+ if (cur_state.proctex_lut.texture_1d == handle)
+ cur_state.proctex_lut.texture_1d = 0;
+ if (cur_state.proctex_diff_lut.texture_1d == handle)
+ cur_state.proctex_diff_lut.texture_1d = 0;
}
void OpenGLState::ResetSampler(GLuint handle) {
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index 7dcc03bd5..1efcf0811 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -6,6 +6,29 @@
#include <glad/glad.h>
+namespace TextureUnits {
+
+struct TextureUnit {
+ GLint id;
+ constexpr GLenum Enum() const {
+ return static_cast<GLenum>(GL_TEXTURE0 + id);
+ }
+};
+
+constexpr TextureUnit PicaTexture(int unit) {
+ return TextureUnit{unit};
+}
+
+constexpr TextureUnit LightingLUT{3};
+constexpr TextureUnit FogLUT{4};
+constexpr TextureUnit ProcTexNoiseLUT{5};
+constexpr TextureUnit ProcTexColorMap{6};
+constexpr TextureUnit ProcTexAlphaMap{7};
+constexpr TextureUnit ProcTexLUT{8};
+constexpr TextureUnit ProcTexDiffLUT{9};
+
+} // namespace TextureUnits
+
class OpenGLState {
public:
struct {
@@ -64,11 +87,11 @@ public:
} texture_units[3];
struct {
- GLuint texture_1d; // GL_TEXTURE_BINDING_1D
- } lighting_luts[6];
+ GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
+ } lighting_lut;
struct {
- GLuint texture_1d; // GL_TEXTURE_BINDING_1D
+ GLuint texture_buffer; // GL_TEXTURE_BINDING_BUFFER
} fog_lut;
struct {
diff --git a/src/video_core/renderer_opengl/pica_to_gl.h b/src/video_core/renderer_opengl/pica_to_gl.h
index 93d7b0b71..c7fa1f873 100644
--- a/src/video_core/renderer_opengl/pica_to_gl.h
+++ b/src/video_core/renderer_opengl/pica_to_gl.h
@@ -12,6 +12,7 @@
#include "common/common_funcs.h"
#include "common/common_types.h"
#include "common/logging/log.h"
+#include "core/core.h"
#include "video_core/regs_framebuffer.h"
#include "video_core/regs_lighting.h"
#include "video_core/regs_texturing.h"
@@ -55,6 +56,12 @@ inline GLenum WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) {
GL_CLAMP_TO_BORDER, // WrapMode::ClampToBorder
GL_REPEAT, // WrapMode::Repeat
GL_MIRRORED_REPEAT, // WrapMode::MirroredRepeat
+ // TODO(wwylele): ClampToEdge2 and ClampToBorder2 are not properly implemented here. See the
+ // comments in enum WrapMode.
+ GL_CLAMP_TO_EDGE, // WrapMode::ClampToEdge2
+ GL_CLAMP_TO_BORDER, // WrapMode::ClampToBorder2
+ GL_REPEAT, // WrapMode::Repeat2
+ GL_REPEAT, // WrapMode::Repeat3
};
// Range check table for input
@@ -65,6 +72,13 @@ inline GLenum WrapMode(Pica::TexturingRegs::TextureConfig::WrapMode mode) {
return GL_CLAMP_TO_EDGE;
}
+ if (static_cast<u32>(mode) > 3) {
+ Core::Telemetry().AddField(Telemetry::FieldType::Session,
+ "VideoCore_Pica_UnsupportedTextureWrapMode",
+ static_cast<u32>(mode));
+ LOG_WARNING(Render_OpenGL, "Using texture wrap mode %u", static_cast<u32>(mode));
+ }
+
GLenum gl_mode = wrap_mode_table[mode];
// Check for dummy values indicating an unknown mode
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index d90c776f9..65c18aecc 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -481,9 +481,18 @@ bool RendererOpenGL::Init() {
glDebugMessageCallback(DebugHandler, nullptr);
}
- LOG_INFO(Render_OpenGL, "GL_VERSION: %s", glGetString(GL_VERSION));
- LOG_INFO(Render_OpenGL, "GL_VENDOR: %s", glGetString(GL_VENDOR));
- LOG_INFO(Render_OpenGL, "GL_RENDERER: %s", glGetString(GL_RENDERER));
+ const char* gl_version{reinterpret_cast<char const*>(glGetString(GL_VERSION))};
+ const char* gpu_vendor{reinterpret_cast<char const*>(glGetString(GL_VENDOR))};
+ const char* gpu_model{reinterpret_cast<char const*>(glGetString(GL_RENDERER))};
+
+ LOG_INFO(Render_OpenGL, "GL_VERSION: %s", gl_version);
+ LOG_INFO(Render_OpenGL, "GL_VENDOR: %s", gpu_vendor);
+ LOG_INFO(Render_OpenGL, "GL_RENDERER: %s", gpu_model);
+
+ Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_Vendor", gpu_vendor);
+ Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_Model", gpu_model);
+ Core::Telemetry().AddField(Telemetry::FieldType::UserSystem, "GPU_OpenGL_Version", gl_version);
+
if (!GLAD_GL_VERSION_3_3) {
return false;
}
diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp
index 5d9b6448c..42a57aab1 100644
--- a/src/video_core/shader/shader_jit_x64_compiler.cpp
+++ b/src/video_core/shader/shader_jit_x64_compiler.cpp
@@ -321,27 +321,27 @@ void JitShader::Compile_EvaluateCondition(Instruction instr) {
case Instruction::FlowControlType::Or:
mov(eax, COND0);
mov(ebx, COND1);
- xor(eax, (instr.flow_control.refx.Value() ^ 1));
- xor(ebx, (instr.flow_control.refy.Value() ^ 1));
- or (eax, ebx);
+ xor_(eax, (instr.flow_control.refx.Value() ^ 1));
+ xor_(ebx, (instr.flow_control.refy.Value() ^ 1));
+ or_(eax, ebx);
break;
case Instruction::FlowControlType::And:
mov(eax, COND0);
mov(ebx, COND1);
- xor(eax, (instr.flow_control.refx.Value() ^ 1));
- xor(ebx, (instr.flow_control.refy.Value() ^ 1));
- and(eax, ebx);
+ xor_(eax, (instr.flow_control.refx.Value() ^ 1));
+ xor_(ebx, (instr.flow_control.refy.Value() ^ 1));
+ and_(eax, ebx);
break;
case Instruction::FlowControlType::JustX:
mov(eax, COND0);
- xor(eax, (instr.flow_control.refx.Value() ^ 1));
+ xor_(eax, (instr.flow_control.refx.Value() ^ 1));
break;
case Instruction::FlowControlType::JustY:
mov(eax, COND1);
- xor(eax, (instr.flow_control.refy.Value() ^ 1));
+ xor_(eax, (instr.flow_control.refy.Value() ^ 1));
break;
}
}
@@ -734,10 +734,10 @@ void JitShader::Compile_LOOP(Instruction instr) {
mov(LOOPCOUNT, dword[SETUP + offset]);
mov(LOOPCOUNT_REG, LOOPCOUNT);
shr(LOOPCOUNT_REG, 4);
- and(LOOPCOUNT_REG, 0xFF0); // Y-component is the start
+ and_(LOOPCOUNT_REG, 0xFF0); // Y-component is the start
mov(LOOPINC, LOOPCOUNT);
shr(LOOPINC, 12);
- and(LOOPINC, 0xFF0); // Z-component is the incrementer
+ and_(LOOPINC, 0xFF0); // Z-component is the incrementer
movzx(LOOPCOUNT, LOOPCOUNT.cvt8()); // X-component is iteration count
add(LOOPCOUNT, 1); // Iteration count is X-component + 1
@@ -858,9 +858,9 @@ void JitShader::Compile(const std::array<u32, MAX_PROGRAM_CODE_LENGTH>* program_
mov(STATE, ABI_PARAM2);
// Zero address/loop registers
- xor(ADDROFFS_REG_0.cvt32(), ADDROFFS_REG_0.cvt32());
- xor(ADDROFFS_REG_1.cvt32(), ADDROFFS_REG_1.cvt32());
- xor(LOOPCOUNT_REG, LOOPCOUNT_REG);
+ xor_(ADDROFFS_REG_0.cvt32(), ADDROFFS_REG_0.cvt32());
+ xor_(ADDROFFS_REG_1.cvt32(), ADDROFFS_REG_1.cvt32());
+ xor_(LOOPCOUNT_REG, LOOPCOUNT_REG);
// Used to set a register to one
static const __m128 one = {1.f, 1.f, 1.f, 1.f};
diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp
index 8b7b1defb..512e81c08 100644
--- a/src/video_core/swrasterizer/rasterizer.cpp
+++ b/src/video_core/swrasterizer/rasterizer.cpp
@@ -357,10 +357,22 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
int t = (int)(v * float24::FromFloat32(static_cast<float>(texture.config.height)))
.ToFloat32();
- if ((texture.config.wrap_s == TexturingRegs::TextureConfig::ClampToBorder &&
- (s < 0 || static_cast<u32>(s) >= texture.config.width)) ||
- (texture.config.wrap_t == TexturingRegs::TextureConfig::ClampToBorder &&
- (t < 0 || static_cast<u32>(t) >= texture.config.height))) {
+ bool use_border_s = false;
+ bool use_border_t = false;
+
+ if (texture.config.wrap_s == TexturingRegs::TextureConfig::ClampToBorder) {
+ use_border_s = s < 0 || s >= static_cast<int>(texture.config.width);
+ } else if (texture.config.wrap_s == TexturingRegs::TextureConfig::ClampToBorder2) {
+ use_border_s = s >= static_cast<int>(texture.config.width);
+ }
+
+ if (texture.config.wrap_t == TexturingRegs::TextureConfig::ClampToBorder) {
+ use_border_t = t < 0 || t >= static_cast<int>(texture.config.height);
+ } else if (texture.config.wrap_t == TexturingRegs::TextureConfig::ClampToBorder2) {
+ use_border_t = t >= static_cast<int>(texture.config.height);
+ }
+
+ if (use_border_s || use_border_t) {
auto border_color = texture.config.border_color;
texture_color[i] = {border_color.r, border_color.g, border_color.b,
border_color.a};
@@ -572,8 +584,7 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
float fog_i = MathUtil::Clamp(floorf(fog_index), 0.0f, 127.0f);
float fog_f = fog_index - fog_i;
const auto& fog_lut_entry = g_state.fog.lut[static_cast<unsigned int>(fog_i)];
- float fog_factor = (fog_lut_entry.value + fog_lut_entry.difference * fog_f) /
- 2047.0f; // This is signed fixed point 1.11
+ float fog_factor = fog_lut_entry.ToFloat() + fog_lut_entry.DiffToFloat() * fog_f;
fog_factor = MathUtil::Clamp(fog_factor, 0.0f, 1.0f);
// Blend the fog
diff --git a/src/video_core/swrasterizer/texturing.cpp b/src/video_core/swrasterizer/texturing.cpp
index aeb6aeb8c..4f02b93f2 100644
--- a/src/video_core/swrasterizer/texturing.cpp
+++ b/src/video_core/swrasterizer/texturing.cpp
@@ -18,22 +18,33 @@ using TevStageConfig = TexturingRegs::TevStageConfig;
int GetWrappedTexCoord(TexturingRegs::TextureConfig::WrapMode mode, int val, unsigned size) {
switch (mode) {
+ case TexturingRegs::TextureConfig::ClampToEdge2:
+ // For negative coordinate, ClampToEdge2 behaves the same as Repeat
+ if (val < 0) {
+ return static_cast<int>(static_cast<unsigned>(val) % size);
+ }
+ // [[fallthrough]]
case TexturingRegs::TextureConfig::ClampToEdge:
val = std::max(val, 0);
- val = std::min(val, (int)size - 1);
+ val = std::min(val, static_cast<int>(size) - 1);
return val;
case TexturingRegs::TextureConfig::ClampToBorder:
return val;
+ case TexturingRegs::TextureConfig::ClampToBorder2:
+ // For ClampToBorder2, the case of positive coordinate beyond the texture size is already
+ // handled outside. Here we only handle the negative coordinate in the same way as Repeat.
+ case TexturingRegs::TextureConfig::Repeat2:
+ case TexturingRegs::TextureConfig::Repeat3:
case TexturingRegs::TextureConfig::Repeat:
- return (int)((unsigned)val % size);
+ return static_cast<int>(static_cast<unsigned>(val) % size);
case TexturingRegs::TextureConfig::MirroredRepeat: {
- unsigned int coord = ((unsigned)val % (2 * size));
+ unsigned int coord = (static_cast<unsigned>(val) % (2 * size));
if (coord >= size)
coord = 2 * size - 1 - coord;
- return (int)coord;
+ return static_cast<int>(coord);
}
default:
diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt
new file mode 100644
index 000000000..334d82a8a
--- /dev/null
+++ b/src/web_service/CMakeLists.txt
@@ -0,0 +1,14 @@
+set(SRCS
+ telemetry_json.cpp
+ web_backend.cpp
+ )
+
+set(HEADERS
+ telemetry_json.h
+ web_backend.h
+ )
+
+create_directory_groups(${SRCS} ${HEADERS})
+
+add_library(web_service STATIC ${SRCS} ${HEADERS})
+target_link_libraries(web_service PUBLIC common cpr json-headers)
diff --git a/src/web_service/telemetry_json.cpp b/src/web_service/telemetry_json.cpp
new file mode 100644
index 000000000..a2d007e77
--- /dev/null
+++ b/src/web_service/telemetry_json.cpp
@@ -0,0 +1,87 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "core/settings.h"
+#include "web_service/telemetry_json.h"
+#include "web_service/web_backend.h"
+
+namespace WebService {
+
+template <class T>
+void TelemetryJson::Serialize(Telemetry::FieldType type, const std::string& name, T value) {
+ sections[static_cast<u8>(type)][name] = value;
+}
+
+void TelemetryJson::SerializeSection(Telemetry::FieldType type, const std::string& name) {
+ TopSection()[name] = sections[static_cast<unsigned>(type)];
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<bool>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<double>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<float>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<u8>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<u16>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<u32>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<u64>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<s8>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<s16>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<s32>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<s64>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<std::string>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue());
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<const char*>& field) {
+ Serialize(field.GetType(), field.GetName(), std::string(field.GetValue()));
+}
+
+void TelemetryJson::Visit(const Telemetry::Field<std::chrono::microseconds>& field) {
+ Serialize(field.GetType(), field.GetName(), field.GetValue().count());
+}
+
+void TelemetryJson::Complete() {
+ SerializeSection(Telemetry::FieldType::App, "App");
+ SerializeSection(Telemetry::FieldType::Session, "Session");
+ SerializeSection(Telemetry::FieldType::Performance, "Performance");
+ SerializeSection(Telemetry::FieldType::UserFeedback, "UserFeedback");
+ SerializeSection(Telemetry::FieldType::UserConfig, "UserConfig");
+ SerializeSection(Telemetry::FieldType::UserSystem, "UserSystem");
+ PostJson(Settings::values.telemetry_endpoint_url, TopSection().dump());
+}
+
+} // namespace WebService
diff --git a/src/web_service/telemetry_json.h b/src/web_service/telemetry_json.h
new file mode 100644
index 000000000..39038b4f9
--- /dev/null
+++ b/src/web_service/telemetry_json.h
@@ -0,0 +1,54 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <string>
+#include <json.hpp>
+#include "common/telemetry.h"
+
+namespace WebService {
+
+/**
+ * Implementation of VisitorInterface that serialized telemetry into JSON, and submits it to the
+ * Citra web service
+ */
+class TelemetryJson : public Telemetry::VisitorInterface {
+public:
+ TelemetryJson() = default;
+ ~TelemetryJson() = default;
+
+ void Visit(const Telemetry::Field<bool>& field) override;
+ void Visit(const Telemetry::Field<double>& field) override;
+ void Visit(const Telemetry::Field<float>& field) override;
+ void Visit(const Telemetry::Field<u8>& field) override;
+ void Visit(const Telemetry::Field<u16>& field) override;
+ void Visit(const Telemetry::Field<u32>& field) override;
+ void Visit(const Telemetry::Field<u64>& field) override;
+ void Visit(const Telemetry::Field<s8>& field) override;
+ void Visit(const Telemetry::Field<s16>& field) override;
+ void Visit(const Telemetry::Field<s32>& field) override;
+ void Visit(const Telemetry::Field<s64>& field) override;
+ void Visit(const Telemetry::Field<std::string>& field) override;
+ void Visit(const Telemetry::Field<const char*>& field) override;
+ void Visit(const Telemetry::Field<std::chrono::microseconds>& field) override;
+
+ void Complete() override;
+
+private:
+ nlohmann::json& TopSection() {
+ return sections[static_cast<u8>(Telemetry::FieldType::None)];
+ }
+
+ template <class T>
+ void Serialize(Telemetry::FieldType type, const std::string& name, T value);
+
+ void SerializeSection(Telemetry::FieldType type, const std::string& name);
+
+ nlohmann::json output;
+ std::array<nlohmann::json, 7> sections;
+};
+
+} // namespace WebService
diff --git a/src/web_service/web_backend.cpp b/src/web_service/web_backend.cpp
new file mode 100644
index 000000000..13e4555ac
--- /dev/null
+++ b/src/web_service/web_backend.cpp
@@ -0,0 +1,52 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cpr/cpr.h>
+#include <stdlib.h>
+#include "common/logging/log.h"
+#include "web_service/web_backend.h"
+
+namespace WebService {
+
+static constexpr char API_VERSION[]{"1"};
+static constexpr char ENV_VAR_USERNAME[]{"CITRA_WEB_SERVICES_USERNAME"};
+static constexpr char ENV_VAR_TOKEN[]{"CITRA_WEB_SERVICES_TOKEN"};
+
+static std::string GetEnvironmentVariable(const char* name) {
+ const char* value{getenv(name)};
+ if (value) {
+ return value;
+ }
+ return {};
+}
+
+const std::string& GetUsername() {
+ static const std::string username{GetEnvironmentVariable(ENV_VAR_USERNAME)};
+ return username;
+}
+
+const std::string& GetToken() {
+ static const std::string token{GetEnvironmentVariable(ENV_VAR_TOKEN)};
+ return token;
+}
+
+void PostJson(const std::string& url, const std::string& data) {
+ if (url.empty()) {
+ LOG_ERROR(WebService, "URL is invalid");
+ return;
+ }
+
+ if (GetUsername().empty() || GetToken().empty()) {
+ LOG_ERROR(WebService, "Environment variables %s and %s must be set to POST JSON",
+ ENV_VAR_USERNAME, ENV_VAR_TOKEN);
+ return;
+ }
+
+ cpr::PostAsync(cpr::Url{url}, cpr::Body{data}, cpr::Header{{"Content-Type", "application/json"},
+ {"x-username", GetUsername()},
+ {"x-token", GetToken()},
+ {"api-version", API_VERSION}});
+}
+
+} // namespace WebService
diff --git a/src/web_service/web_backend.h b/src/web_service/web_backend.h
new file mode 100644
index 000000000..2753d3b68
--- /dev/null
+++ b/src/web_service/web_backend.h
@@ -0,0 +1,31 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include "common/common_types.h"
+
+namespace WebService {
+
+/**
+ * Gets the current username for accessing services.citra-emu.org.
+ * @returns Username as a string, empty if not set.
+ */
+const std::string& GetUsername();
+
+/**
+ * Gets the current token for accessing services.citra-emu.org.
+ * @returns Token as a string, empty if not set.
+ */
+const std::string& GetToken();
+
+/**
+ * Posts JSON to services.citra-emu.org.
+ * @param url URL of the services.citra-emu.org endpoint to post data to.
+ * @param data String of JSON data to use for the body of the POST request.
+ */
+void PostJson(const std::string& url, const std::string& data);
+
+} // namespace WebService