summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitmodules9
-rw-r--r--CMakeLists.txt7
-rw-r--r--README.md4
-rw-r--r--externals/CMakeLists.txt16
m---------externals/cpr0
-rw-r--r--externals/cryptopp/CMakeLists.txt5
m---------externals/enet0
m---------externals/json0
-rw-r--r--src/CMakeLists.txt4
-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_qt/CMakeLists.txt2
-rw-r--r--src/citra_qt/bootmanager.cpp3
-rw-r--r--src/citra_qt/configuration/config.cpp18
-rw-r--r--src/citra_qt/configuration/configure_debug.ui7
-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.txt5
-rw-r--r--src/core/hle/function_wrappers.h38
-rw-r--r--src/core/hle/kernel/client_session.cpp10
-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.cpp19
-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.cpp22
-rw-r--r--src/core/hle/kernel/server_session.h14
-rw-r--r--src/core/hle/service/boss/boss_p.cpp3
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp77
-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/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/memory.cpp25
-rw-r--r--src/core/settings.h3
-rw-r--r--src/core/telemetry_session.cpp10
-rw-r--r--src/network/CMakeLists.txt16
-rw-r--r--src/network/network.cpp50
-rw-r--r--src/network/network.h25
-rw-r--r--src/network/room.cpp60
-rw-r--r--src/network/room.h60
-rw-r--r--src/network/room_member.cpp74
-rw-r--r--src/network/room_member.h65
-rw-r--r--src/tests/core/hle/kernel/hle_ipc.cpp25
-rw-r--r--src/video_core/pica_state.h8
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp29
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h3
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp10
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp10
-rw-r--r--src/video_core/renderer_opengl/gl_state.h2
-rw-r--r--src/video_core/swrasterizer/rasterizer.cpp3
-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
60 files changed, 1482 insertions, 110 deletions
diff --git a/.gitmodules b/.gitmodules
index 36caa59f8..45ff650ef 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -25,3 +25,12 @@
[submodule "fmt"]
path = externals/fmt
url = https://github.com/fmtlib/fmt.git
+[submodule "externals/enet"]
+ path = externals/enet
+ url = https://github.com/lsalzman/enet
+[submodule "cpr"]
+ path = externals/cpr
+ url = https://github.com/whoshuu/cpr.git
+[submodule "json"]
+ path = externals/json
+ url = https://github.com/nlohmann/json.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a61dee6e0..ad73cf495 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -11,6 +11,8 @@ option(CITRA_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" OFF)
option(ENABLE_QT "Enable the Qt frontend" ON)
option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF)
+option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
+
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
message(STATUS "Copying pre-commit hook")
file(COPY hooks/pre-commit
@@ -79,6 +81,8 @@ else()
add_definitions(/D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE /D_SCL_SECURE_NO_WARNINGS)
# Avoid windows.h junk
add_definitions(/DNOMINMAX)
+ # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
+ add_definitions(/DWIN32_LEAN_AND_MEAN)
# set up output paths for executable binaries (.exe-files, and .dll-files on DLL-capable platforms)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
@@ -221,6 +225,9 @@ if (ENABLE_QT)
find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
endif()
+if (ENABLE_WEB_SERVICE)
+ add_definitions(-DENABLE_WEB_SERVICE)
+endif()
# Platform-specific library requirements
# ======================================
diff --git a/README.md b/README.md
index 8de62d0ef..e766918f7 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,7 @@ Citra Emulator
[![Travis CI Build Status](https://travis-ci.org/citra-emu/citra.svg?branch=master)](https://travis-ci.org/citra-emu/citra)
[![AppVeyor CI Build Status](https://ci.appveyor.com/api/projects/status/sdf1o4kh3g1e68m9?svg=true)](https://ci.appveyor.com/project/bunnei/citra)
-Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward.
+Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. Citra only emulates a subset of 3DS hardware and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward.
Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://citra-emu.org/wiki/faq/) before getting started with the project.
@@ -27,7 +27,7 @@ If you want to contribute please take a look at the [Contributor's Guide](CONTRI
### Support
-We happily accept monetary donations, or donated games and hardware. Please see our [donations page](https://citra-emu.org/donate/) for more information on how you can contribute to Citra. Any donations received will go towards things like:
+We happily accept monetary donations or donated games and hardware. Please see our [donations page](https://citra-emu.org/donate/) for more information on how you can contribute to Citra. Any donations received will go towards things like:
* 3DS consoles for developers to explore the hardware
* 3DS games for testing
* Any equipment required for homebrew
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt
index 02e02350c..ccc7f13b6 100644
--- a/externals/CMakeLists.txt
+++ b/externals/CMakeLists.txt
@@ -48,3 +48,19 @@ if (ARCHITECTURE_x86_64)
target_include_directories(xbyak INTERFACE ./xbyak/xbyak)
target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES)
endif()
+
+# ENet
+add_subdirectory(enet)
+target_include_directories(enet INTERFACE ./enet/include)
+
+if (ENABLE_WEB_SERVICE)
+ # CPR
+ option(BUILD_TESTING OFF)
+ option(BUILD_CPR_TESTS OFF)
+ add_subdirectory(cpr)
+ target_include_directories(cpr INTERFACE ./cpr/include)
+
+ # JSON
+ add_library(json-headers INTERFACE)
+ target_include_directories(json-headers INTERFACE ./json/src)
+endif()
diff --git a/externals/cpr b/externals/cpr
new file mode 160000
+Subproject b5758fbc88021437f968fe5174f121b8b92f5d5
diff --git a/externals/cryptopp/CMakeLists.txt b/externals/cryptopp/CMakeLists.txt
index 864de18bb..8a626e44a 100644
--- a/externals/cryptopp/CMakeLists.txt
+++ b/externals/cryptopp/CMakeLists.txt
@@ -44,6 +44,11 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Intel")
add_definitions(-wd68 -wd186 -wd279 -wd327 -wd161 -wd3180)
endif()
+if(MSVC)
+ # Disable C4390: empty controlled statement found: is this the intent?
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4390")
+endif()
+
# Endianness
TEST_BIG_ENDIAN(IS_BIG_ENDIAN)
if(IS_BIG_ENDIAN)
diff --git a/externals/enet b/externals/enet
new file mode 160000
+Subproject a84c120eff13d2fa3eadb41ef7afe0f7819f4d6
diff --git a/externals/json b/externals/json
new file mode 160000
+Subproject d3496347fcd1382896fca3aaf78a0d803c2f52e
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/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_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 4841cbf05..9572d3e28 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -91,7 +91,7 @@ if (APPLE)
else()
add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})
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..40142b6d9 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,6 +133,13 @@ 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");
qt_config->beginGroup("UILayout");
@@ -268,6 +275,11 @@ 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->beginGroup("UILayout");
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/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 2408e659c..360f407f3 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -145,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
@@ -343,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
@@ -388,3 +390,6 @@ 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 fmt)
+if (ENABLE_WEB_SERVICE)
+ target_link_libraries(core PUBLIC json-headers web_service)
+endif()
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/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp
index fef97af1f..646a5cc64 100644
--- a/src/core/hle/kernel/client_session.cpp
+++ b/src/core/hle/kernel/client_session.cpp
@@ -9,6 +9,7 @@
#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 {
@@ -27,19 +28,24 @@ ClientSession::~ClientSession() {
// 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() {
+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;
// Signal the server session that new data is available
- return server->HandleSyncRequest();
+ 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 1cac1d0c9..5ebe2eca4 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -67,10 +67,13 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const u32_le* sr
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 = src_table.GetGeneric(handle);
- ASSERT(object != nullptr); // TODO(yuriks): Return error
- if (descriptor == IPC::DescriptorType::MoveHandle) {
- src_table.Close(handle);
+ 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));
@@ -112,9 +115,11 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(u32_le* dst_cmdbuf, P
ASSERT(i + num_handles <= command_size);
for (u32 j = 0; j < num_handles; ++j) {
SharedPtr<Object> object = GetIncomingHandle(cmd_buf[i]);
-
- // TODO(yuriks): Figure out the proper error handling for if this fails
- Handle handle = dst_table.Create(object).Unwrap();
+ 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;
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 d197137c3..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,11 +67,14 @@ 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;
}
@@ -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 5365605da..f4360ddf3 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -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/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/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index a7149c9e8..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 {
@@ -373,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:
@@ -600,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/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/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/memory.cpp b/src/core/memory.cpp
index b8438e490..9024f4922 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -139,7 +139,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 +152,8 @@ static u8* GetPointerFromVMA(VAddr vaddr) {
case Kernel::VMAType::BackingMemory:
direct_pointer = vma.backing_memory;
break;
+ case Kernel::VMAType::Free:
+ return nullptr;
default:
UNREACHABLE();
}
@@ -341,11 +348,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;
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..70eff4340 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -7,12 +7,18 @@
#include "common/scm_rev.h"
#include "core/telemetry_session.h"
+#ifdef ENABLE_WEB_SERVICE
+#include "web_service/telemetry_json.h"
+#endif
+
namespace Core {
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()};
diff --git a/src/network/CMakeLists.txt b/src/network/CMakeLists.txt
new file mode 100644
index 000000000..aeabe430e
--- /dev/null
+++ b/src/network/CMakeLists.txt
@@ -0,0 +1,16 @@
+set(SRCS
+ network.cpp
+ room.cpp
+ room_member.cpp
+ )
+
+set(HEADERS
+ network.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/room.cpp b/src/network/room.cpp
new file mode 100644
index 000000000..48de2f5cb
--- /dev/null
+++ b/src/network/room.cpp
@@ -0,0 +1,60 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "enet/enet.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:
+ ENetHost* server = nullptr; ///< Network interface.
+
+ std::atomic<State> state{State::Closed}; ///< Current state of the room.
+ RoomInformation room_information; ///< Information about this 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;
+ 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;
+
+ // TODO(B3N30): Start the receiving thread
+}
+
+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;
+ // TODO(B3n30): Join the receiving thread
+
+ if (room_impl->server) {
+ enet_host_destroy(room_impl->server);
+ }
+ room_impl->room_information = {};
+ room_impl->server = nullptr;
+}
+
+} // namespace Network
diff --git a/src/network/room.h b/src/network/room.h
new file mode 100644
index 000000000..70c64d5f1
--- /dev/null
+++ b/src/network/room.h
@@ -0,0 +1,60 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+
+namespace Network {
+
+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
+};
+
+/// 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..c87f009f4
--- /dev/null
+++ b/src/network/room_member.cpp
@@ -0,0 +1,74 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/assert.h"
+#include "enet/enet.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
+
+ std::atomic<State> state{State::Idle}; ///< Current state of the RoomMember.
+
+ std::string nickname; ///< The nickname of this member.
+};
+
+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;
+}
+
+void RoomMember::Join(const std::string& nick, const char* server_addr, u16 server_port,
+ u16 client_port) {
+ 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->state = 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->state = State::Joining;
+ // TODO(B3N30): Send a join request with the nickname to the server
+ // TODO(B3N30): Start the receive thread
+ } else {
+ room_member_impl->state = State::CouldNotConnect;
+ }
+}
+
+bool RoomMember::IsConnected() const {
+ return room_member_impl->state == State::Joining || room_member_impl->state == State::Joined;
+}
+
+void RoomMember::Leave() {
+ enet_peer_disconnect(room_member_impl->server, 0);
+ room_member_impl->state = State::Idle;
+ // TODO(B3N30): Close the receive thread
+ enet_peer_reset(room_member_impl->server);
+}
+
+} // namespace Network
diff --git a/src/network/room_member.h b/src/network/room_member.h
new file mode 100644
index 000000000..177622b69
--- /dev/null
+++ b/src/network/room_member.h
@@ -0,0 +1,65 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <string>
+#include "common/common_types.h"
+#include "network/room.h"
+
+namespace Network {
+
+/**
+ * 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
+ CouldNotConnect ///< The room is not responding to a connection attempt
+ };
+
+ RoomMember();
+ ~RoomMember();
+
+ /**
+ * Returns the status of our connection to the room.
+ */
+ State GetState() 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);
+
+ /**
+ * Leaves the current room.
+ */
+ void Leave();
+
+private:
+ class RoomMemberImpl;
+ std::unique_ptr<RoomMemberImpl> room_member_impl;
+};
+
+} // namespace Network
diff --git a/src/tests/core/hle/kernel/hle_ipc.cpp b/src/tests/core/hle/kernel/hle_ipc.cpp
index e07a28c5b..52336d027 100644
--- a/src/tests/core/hle/kernel/hle_ipc.cpp
+++ b/src/tests/core/hle/kernel/hle_ipc.cpp
@@ -18,7 +18,7 @@ static SharedPtr<Object> MakeObject() {
return Event::Create(ResetType::OneShot);
}
-TEST_CASE("HLERequestContext::PopoulateFromIncomingCommandBuffer", "[core][kernel]") {
+TEST_CASE("HLERequestContext::PopulateFromIncomingCommandBuffer", "[core][kernel]") {
auto session = std::get<SharedPtr<ServerSession>>(ServerSession::CreateSessionPair());
HLERequestContext context(std::move(session));
@@ -94,6 +94,18 @@ TEST_CASE("HLERequestContext::PopoulateFromIncomingCommandBuffer", "[core][kerne
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,
@@ -171,6 +183,17 @@ TEST_CASE("HLERequestContext::WriteToOutgoingCommandBuffer", "[core][kernel]") {
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();
diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h
index 3b00df0b3..2d23d34e6 100644
--- a/src/video_core/pica_state.h
+++ b/src/video_core/pica_state.h
@@ -111,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/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 8b7991c04..ff3f69ba3 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -94,10 +94,10 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
framebuffer.Create();
// Allocate and bind lighting lut textures
- lighting_lut_buffer.Create();
+ lighting_lut.Create();
state.lighting_lut.texture_buffer = lighting_lut.handle;
state.Apply();
- lighting_lut.Create();
+ lighting_lut_buffer.Create();
glBindBuffer(GL_TEXTURE_BUFFER, lighting_lut_buffer.handle);
glBufferData(GL_TEXTURE_BUFFER,
sizeof(GLfloat) * 2 * 256 * Pica::LightingRegs::NumLightingSampler, nullptr,
@@ -106,16 +106,14 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) {
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();
-
+ 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());
- 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);
+ glTexBuffer(GL_TEXTURE_BUFFER, GL_RG32F, fog_lut_buffer.handle);
// Setup the noise LUT for proctex
proctex_noise_lut.Create();
@@ -1356,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(TextureUnits::FogLUT.Enum());
- 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());
}
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 79acd4230..a433c1d4a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -283,8 +283,9 @@ private:
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 0c7c4dd5c..c93b108fb 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -1052,7 +1052,7 @@ layout (std140) uniform shader_data {
uniform sampler2D tex[3];
uniform samplerBuffer lighting_lut;
-uniform usampler1D fog_lut;
+uniform samplerBuffer fog_lut;
uniform sampler1D proctex_noise_lut;
uniform sampler1D proctex_color_map;
uniform sampler1D proctex_alpha_map;
@@ -1145,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_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index 14e63115c..eface2dea 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -54,7 +54,7 @@ OpenGLState::OpenGLState() {
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;
@@ -198,9 +198,9 @@ void OpenGLState::Apply() const {
}
// Fog LUT
- if (fog_lut.texture_1d != cur_state.fog_lut.texture_1d) {
+ if (fog_lut.texture_buffer != cur_state.fog_lut.texture_buffer) {
glActiveTexture(TextureUnits::FogLUT.Enum());
- glBindTexture(GL_TEXTURE_1D, fog_lut.texture_1d);
+ glBindTexture(GL_TEXTURE_BUFFER, fog_lut.texture_buffer);
}
// ProcTex Noise LUT
@@ -272,8 +272,8 @@ void OpenGLState::ResetTexture(GLuint handle) {
}
if (cur_state.lighting_lut.texture_buffer == handle)
cur_state.lighting_lut.texture_buffer = 0;
- if (cur_state.fog_lut.texture_1d == handle)
- cur_state.fog_lut.texture_1d = 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)
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index bb0218708..1efcf0811 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -91,7 +91,7 @@ public:
} 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/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp
index cd7b6c39d..512e81c08 100644
--- a/src/video_core/swrasterizer/rasterizer.cpp
+++ b/src/video_core/swrasterizer/rasterizer.cpp
@@ -584,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/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