summaryrefslogtreecommitdiffstats
path: root/src/core/hle/service
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/core/hle/service/apt/apt.cpp729
-rw-r--r--src/core/hle/service/apt/apt.h20
-rw-r--r--src/core/hle/service/apt/apt_s.cpp8
-rw-r--r--src/core/hle/service/cam/cam.cpp2
-rw-r--r--src/core/hle/service/cfg/cfg.cpp6
-rw-r--r--src/core/hle/service/cfg/cfg.h2
-rw-r--r--src/core/hle/service/csnd_snd.cpp2
-rw-r--r--src/core/hle/service/dlp/dlp_clnt.cpp21
-rw-r--r--src/core/hle/service/dlp/dlp_fkcl.cpp18
-rw-r--r--src/core/hle/service/dlp/dlp_srvr.cpp9
-rw-r--r--src/core/hle/service/dsp_dsp.cpp7
-rw-r--r--src/core/hle/service/fs/archive.cpp20
-rw-r--r--src/core/hle/service/fs/archive.h7
-rw-r--r--src/core/hle/service/hid/hid.cpp44
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/hle/service/ir/ir_rst.cpp2
-rw-r--r--src/core/hle/service/ldr_ro/cro_helper.h6
-rw-r--r--src/core/hle/service/nim/nim.cpp18
-rw-r--r--src/core/hle/service/nim/nim.h11
-rw-r--r--src/core/hle/service/nim/nim_u.cpp2
-rw-r--r--src/core/hle/service/ns/ns.cpp16
-rw-r--r--src/core/hle/service/ns/ns.h16
-rw-r--r--src/core/hle/service/ns/ns_s.cpp34
-rw-r--r--src/core/hle/service/ns/ns_s.h (renamed from src/core/hle/service/ns_s.h)9
-rw-r--r--src/core/hle/service/ns_s.cpp33
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp485
-rw-r--r--src/core/hle/service/nwm/nwm_uds.h12
-rw-r--r--src/core/hle/service/nwm/uds_beacon.cpp7
-rw-r--r--src/core/hle/service/nwm/uds_beacon.h30
-rw-r--r--src/core/hle/service/nwm/uds_connection.cpp88
-rw-r--r--src/core/hle/service/nwm/uds_connection.h56
-rw-r--r--src/core/hle/service/nwm/uds_data.cpp103
-rw-r--r--src/core/hle/service/nwm/uds_data.h86
-rw-r--r--src/core/hle/service/service.cpp5
-rw-r--r--src/core/hle/service/sm/sm.cpp4
-rw-r--r--src/core/hle/service/sm/sm.h3
-rw-r--r--src/core/hle/service/sm/srv.cpp28
-rw-r--r--src/core/hle/service/sm/srv.h1
38 files changed, 1544 insertions, 408 deletions
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp
index 5c44b43bb..912ab550d 100644
--- a/src/core/hle/service/apt/apt.cpp
+++ b/src/core/hle/service/apt/apt.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <boost/optional.hpp>
#include "common/common_paths.h"
#include "common/file_util.h"
#include "common/logging/log.h"
@@ -18,6 +19,7 @@
#include "core/hle/service/apt/apt_s.h"
#include "core/hle/service/apt/apt_u.h"
#include "core/hle/service/apt/bcfnt/bcfnt.h"
+#include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/fs/archive.h"
#include "core/hle/service/ptm/ptm.h"
#include "core/hle/service/service.h"
@@ -33,8 +35,6 @@ static bool shared_font_loaded = false;
static bool shared_font_relocated = false;
static Kernel::SharedPtr<Kernel::Mutex> lock;
-static Kernel::SharedPtr<Kernel::Event> notification_event; ///< APT notification event
-static Kernel::SharedPtr<Kernel::Event> parameter_event; ///< APT parameter event
static u32 cpu_percent; ///< CPU time available to the running application
@@ -43,43 +43,344 @@ static u8 unknown_ns_state_field;
static ScreencapPostPermission screen_capture_post_permission;
-/// Parameter data to be returned in the next call to Glance/ReceiveParameter
-static MessageParameter next_parameter;
+/// Parameter data to be returned in the next call to Glance/ReceiveParameter.
+/// TODO(Subv): Use std::optional once we migrate to C++17.
+static boost::optional<MessageParameter> next_parameter;
+
+enum class AppletPos { Application = 0, Library = 1, System = 2, SysLibrary = 3, Resident = 4 };
+
+static constexpr size_t NumAppletSlot = 4;
+
+enum class AppletSlot : u8 {
+ Application,
+ SystemApplet,
+ HomeMenu,
+ LibraryApplet,
+
+ // An invalid tag
+ Error,
+};
+
+union AppletAttributes {
+ u32 raw;
+
+ BitField<0, 3, u32> applet_pos;
+ BitField<29, 1, u32> is_home_menu;
+
+ AppletAttributes() : raw(0) {}
+ AppletAttributes(u32 attributes) : raw(attributes) {}
+};
+
+struct AppletSlotData {
+ AppletId applet_id;
+ AppletSlot slot;
+ bool registered;
+ AppletAttributes attributes;
+ Kernel::SharedPtr<Kernel::Event> notification_event;
+ Kernel::SharedPtr<Kernel::Event> parameter_event;
+};
+
+// Holds data about the concurrently running applets in the system.
+static std::array<AppletSlotData, NumAppletSlot> applet_slots = {};
+
+// This overload returns nullptr if no applet with the specified id has been started.
+static AppletSlotData* GetAppletSlotData(AppletId id) {
+ auto GetSlot = [](AppletSlot slot) -> AppletSlotData* {
+ return &applet_slots[static_cast<size_t>(slot)];
+ };
+
+ if (id == AppletId::Application) {
+ auto* slot = GetSlot(AppletSlot::Application);
+ if (slot->applet_id != AppletId::None)
+ return slot;
+
+ return nullptr;
+ }
+
+ if (id == AppletId::AnySystemApplet) {
+ auto* system_slot = GetSlot(AppletSlot::SystemApplet);
+ if (system_slot->applet_id != AppletId::None)
+ return system_slot;
+
+ // The Home Menu is also a system applet, but it lives in its own slot to be able to run
+ // concurrently with other system applets.
+ auto* home_slot = GetSlot(AppletSlot::HomeMenu);
+ if (home_slot->applet_id != AppletId::None)
+ return home_slot;
+
+ return nullptr;
+ }
+
+ if (id == AppletId::AnyLibraryApplet || id == AppletId::AnySysLibraryApplet) {
+ auto* slot = GetSlot(AppletSlot::LibraryApplet);
+ if (slot->applet_id == AppletId::None)
+ return nullptr;
+
+ u32 applet_pos = slot->attributes.applet_pos;
+
+ if (id == AppletId::AnyLibraryApplet && applet_pos == static_cast<u32>(AppletPos::Library))
+ return slot;
+
+ if (id == AppletId::AnySysLibraryApplet &&
+ applet_pos == static_cast<u32>(AppletPos::SysLibrary))
+ return slot;
+
+ return nullptr;
+ }
+
+ if (id == AppletId::HomeMenu || id == AppletId::AlternateMenu) {
+ auto* slot = GetSlot(AppletSlot::HomeMenu);
+ if (slot->applet_id != AppletId::None)
+ return slot;
+
+ return nullptr;
+ }
+
+ for (auto& slot : applet_slots) {
+ if (slot.applet_id == id)
+ return &slot;
+ }
+
+ return nullptr;
+}
+
+static AppletSlotData* GetAppletSlotData(AppletAttributes attributes) {
+ // Mapping from AppletPos to AppletSlot
+ static constexpr std::array<AppletSlot, 6> applet_position_slots = {
+ AppletSlot::Application, AppletSlot::LibraryApplet, AppletSlot::SystemApplet,
+ AppletSlot::LibraryApplet, AppletSlot::Error, AppletSlot::LibraryApplet};
+
+ u32 applet_pos = attributes.applet_pos;
+ if (applet_pos >= applet_position_slots.size())
+ return nullptr;
+
+ AppletSlot slot = applet_position_slots[applet_pos];
+
+ if (slot == AppletSlot::Error)
+ return nullptr;
+
+ // The Home Menu is a system applet, however, it has its own applet slot so that it can run
+ // concurrently with other system applets.
+ if (slot == AppletSlot::SystemApplet && attributes.is_home_menu)
+ return &applet_slots[static_cast<size_t>(AppletSlot::HomeMenu)];
+
+ return &applet_slots[static_cast<size_t>(slot)];
+}
void SendParameter(const MessageParameter& parameter) {
next_parameter = parameter;
- // Signal the event to let the application know that a new parameter is ready to be read
- parameter_event->Signal();
+ // Signal the event to let the receiver know that a new parameter is ready to be read
+ auto* const slot_data = GetAppletSlotData(static_cast<AppletId>(parameter.destination_id));
+ if (slot_data == nullptr) {
+ LOG_DEBUG(Service_APT, "No applet was registered with the id %03X",
+ parameter.destination_id);
+ return;
+ }
+
+ slot_data->parameter_event->Signal();
}
void Initialize(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x2, 2, 0); // 0x20080
u32 app_id = rp.Pop<u32>();
- u32 flags = rp.Pop<u32>();
+ u32 attributes = rp.Pop<u32>();
+
+ LOG_DEBUG(Service_APT, "called app_id=0x%08X, attributes=0x%08X", app_id, attributes);
+
+ auto* const slot_data = GetAppletSlotData(attributes);
+
+ // Note: The real NS service does not check if the attributes value is valid before accessing
+ // the data in the array
+ ASSERT_MSG(slot_data, "Invalid application attributes");
+
+ if (slot_data->registered) {
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(ResultCode(ErrorDescription::AlreadyExists, ErrorModule::Applet,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+ return;
+ }
+
+ slot_data->applet_id = static_cast<AppletId>(app_id);
+ slot_data->attributes.raw = attributes;
+
IPC::RequestBuilder rb = rp.MakeBuilder(1, 3);
rb.Push(RESULT_SUCCESS);
- rb.PushCopyHandles(Kernel::g_handle_table.Create(notification_event).Unwrap(),
- Kernel::g_handle_table.Create(parameter_event).Unwrap());
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(slot_data->notification_event).Unwrap(),
+ Kernel::g_handle_table.Create(slot_data->parameter_event).Unwrap());
+
+ if (slot_data->applet_id == AppletId::Application ||
+ slot_data->applet_id == AppletId::HomeMenu) {
+ // Initialize the APT parameter to wake up the application.
+ next_parameter.emplace();
+ next_parameter->signal = static_cast<u32>(SignalType::Wakeup);
+ next_parameter->sender_id = static_cast<u32>(AppletId::None);
+ next_parameter->destination_id = app_id;
+ // Not signaling the parameter event will cause the application (or Home Menu) to hang
+ // during startup. In the real console, it is usually the Kernel and Home Menu who cause NS
+ // to signal the HomeMenu and Application parameter events, respectively.
+ slot_data->parameter_event->Signal();
+ }
+}
- // TODO(bunnei): Check if these events are cleared every time Initialize is called.
- notification_event->Clear();
- parameter_event->Clear();
+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;
- ASSERT_MSG((nullptr != lock), "Cannot initialize without lock");
- lock->Release();
+ 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;
+ }
- LOG_DEBUG(Service_APT, "called app_id=0x%08X, flags=0x%08X", app_id, flags);
+ 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() {
+ u8 font_region_code;
+ switch (CFG::GetRegionValue()) {
+ case 4: // CHN
+ font_region_code = 2;
+ break;
+ case 5: // KOR
+ font_region_code = 3;
+ break;
+ case 6: // TWN
+ font_region_code = 4;
+ break;
+ default: // JPN/EUR/USA
+ font_region_code = 1;
+ break;
+ }
+
+ const u64_le shared_font_archive_id_low = 0x0004009b00014002 | ((font_region_code - 1) << 8);
+ 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 char16_t* file_name[4] = {u"cbf_std.bcfnt.lz", u"cbf_zh-Hans-CN.bcfnt.lz",
+ u"cbf_ko-Hang-KR.bcfnt.lz", u"cbf_zh-Hant-TW.bcfnt.lz"};
+ const u8* font_file =
+ RomFS::GetFilePointer(romfs_buffer.data(), {file_name[font_region_code - 1]});
+ 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 = font_region_code;
+ 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()) {
+ file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize());
+ return true;
+ }
+
+ return false;
}
void GetSharedFont(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
+
+ // Log in telemetry if the game uses the shared font
+ Core::Telemetry().AddField(Telemetry::FieldType::Session, "RequiresSharedFont", true);
+
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);
- Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSharedFont);
- return;
+ // On real 3DS, font loading happens on booting. However, we load it on demand to coordinate
+ // with CFG region auto configuration, which happens later than APT initialization.
+ 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_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);
+ Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSharedFont);
+ return;
+ }
}
// The shared font has to be relocated to the new address before being passed to the
@@ -115,7 +416,12 @@ void GetLockHandle(Service::Interface* self) {
// this will cause the app to wait until parameter_event is signaled.
u32 applet_attributes = rp.Pop<u32>();
IPC::RequestBuilder rb = rp.MakeBuilder(3, 2);
- rb.Push(RESULT_SUCCESS); // No error
+ rb.Push(RESULT_SUCCESS); // No error
+
+ // TODO(Subv): The output attributes should have an AppletPos of either Library or System |
+ // Library (depending on the type of the last launched applet) if the input attributes'
+ // AppletPos has the Library bit set.
+
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).Unwrap();
@@ -128,10 +434,22 @@ void GetLockHandle(Service::Interface* self) {
void Enable(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x3, 1, 0); // 0x30040
u32 attributes = rp.Pop<u32>();
+
+ LOG_DEBUG(Service_APT, "called attributes=0x%08X", attributes);
+
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
- rb.Push(RESULT_SUCCESS); // No error
- parameter_event->Signal(); // Let the application know that it has been started
- LOG_WARNING(Service_APT, "(STUBBED) called attributes=0x%08X", attributes);
+
+ auto* const slot_data = GetAppletSlotData(attributes);
+
+ if (!slot_data) {
+ rb.Push(ResultCode(ErrCodes::InvalidAppletSlot, ErrorModule::Applet,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+ return;
+ }
+
+ slot_data->registered = true;
+
+ rb.Push(RESULT_SUCCESS);
}
void GetAppletManInfo(Service::Interface* self) {
@@ -149,22 +467,27 @@ void GetAppletManInfo(Service::Interface* self) {
void IsRegistered(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 1, 0); // 0x90040
- u32 app_id = rp.Pop<u32>();
+ AppletId app_id = static_cast<AppletId>(rp.Pop<u32>());
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
rb.Push(RESULT_SUCCESS); // No error
- // TODO(Subv): An application is considered "registered" if it has already called APT::Enable
- // handle this properly once we implement multiprocess support.
- bool is_registered = false; // Set to not registered by default
+ auto* const slot_data = GetAppletSlotData(app_id);
+
+ // Check if an LLE applet was registered first, then fallback to HLE applets
+ bool is_registered = slot_data && slot_data->registered;
- if (app_id == static_cast<u32>(AppletId::AnyLibraryApplet)) {
- is_registered = HLE::Applets::IsLibraryAppletRunning();
- } else if (auto applet = HLE::Applets::Applet::Get(static_cast<AppletId>(app_id))) {
- is_registered = true; // Set to registered
+ if (!is_registered) {
+ if (app_id == AppletId::AnyLibraryApplet) {
+ is_registered = HLE::Applets::IsLibraryAppletRunning();
+ } else if (auto applet = HLE::Applets::Applet::Get(app_id)) {
+ // The applet exists, set it as registered.
+ is_registered = true;
+ }
}
+
rb.Push(is_registered);
- LOG_WARNING(Service_APT, "(STUBBED) called app_id=0x%08X", app_id);
+ LOG_DEBUG(Service_APT, "called app_id=0x%08X", static_cast<u32>(app_id));
}
void InquireNotification(Service::Interface* self) {
@@ -186,14 +509,17 @@ void SendParameter(Service::Interface* self) {
size_t size;
VAddr buffer = rp.PopStaticBuffer(&size);
- std::shared_ptr<HLE::Applets::Applet> dest_applet =
- HLE::Applets::Applet::Get(static_cast<AppletId>(dst_app_id));
+ LOG_DEBUG(Service_APT,
+ "called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X,"
+ "buffer_size=0x%08X, handle=0x%08X, size=0x%08zX, in_param_buffer_ptr=0x%08X",
+ src_app_id, dst_app_id, signal_type, buffer_size, handle, size, buffer);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
- if (dest_applet == nullptr) {
- LOG_ERROR(Service_APT, "Unknown applet id=0x%08X", dst_app_id);
- rb.Push<u32>(-1); // TODO(Subv): Find the right error code
+ // A new parameter can not be sent if the previous one hasn't been consumed yet
+ if (next_parameter) {
+ rb.Push(ResultCode(ErrCodes::ParameterPresent, ErrorModule::Applet,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
return;
}
@@ -205,12 +531,14 @@ void SendParameter(Service::Interface* self) {
param.buffer.resize(buffer_size);
Memory::ReadBlock(buffer, param.buffer.data(), param.buffer.size());
- rb.Push(dest_applet->ReceiveParameter(param));
+ SendParameter(param);
- LOG_WARNING(Service_APT,
- "(STUBBED) called src_app_id=0x%08X, dst_app_id=0x%08X, signal_type=0x%08X,"
- "buffer_size=0x%08X, handle=0x%08X, size=0x%08zX, in_param_buffer_ptr=0x%08X",
- src_app_id, dst_app_id, signal_type, buffer_size, handle, size, buffer);
+ // If the applet is running in HLE mode, use the HLE interface to communicate with it.
+ if (auto dest_applet = HLE::Applets::Applet::Get(static_cast<AppletId>(dst_app_id))) {
+ rb.Push(dest_applet->ReceiveParameter(param));
+ } else {
+ rb.Push(RESULT_SUCCESS);
+ }
}
void ReceiveParameter(Service::Interface* self) {
@@ -226,21 +554,40 @@ void ReceiveParameter(Service::Interface* self) {
"buffer_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)",
buffer_size, static_buff_size);
+ LOG_DEBUG(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size);
+
+ if (!next_parameter) {
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::Applet,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+ return;
+ }
+
+ if (next_parameter->destination_id != app_id) {
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound,
+ ErrorLevel::Status));
+ return;
+ }
+
IPC::RequestBuilder rb = rp.MakeBuilder(4, 4);
+
rb.Push(RESULT_SUCCESS); // No error
- rb.Push(next_parameter.sender_id);
- rb.Push(next_parameter.signal); // Signal type
- ASSERT_MSG(next_parameter.buffer.size() <= buffer_size, "Input static buffer is too small !");
- rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size
+ rb.Push(next_parameter->sender_id);
+ rb.Push(next_parameter->signal); // Signal type
+ ASSERT_MSG(next_parameter->buffer.size() <= buffer_size, "Input static buffer is too small !");
+ 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).Unwrap()
+ rb.PushMoveHandles((next_parameter->object != nullptr)
+ ? Kernel::g_handle_table.Create(next_parameter->object).Unwrap()
: 0);
- rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0);
- Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size());
+ rb.PushStaticBuffer(buffer, next_parameter->buffer.size(), 0);
+
+ Memory::WriteBlock(buffer, next_parameter->buffer.data(), next_parameter->buffer.size());
- LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size);
+ // Clear the parameter
+ next_parameter = boost::none;
}
void GlanceParameter(Service::Interface* self) {
@@ -256,37 +603,74 @@ void GlanceParameter(Service::Interface* self) {
"buffer_size is bigger than the size in the buffer descriptor (0x%08X > 0x%08zX)",
buffer_size, static_buff_size);
+ LOG_DEBUG(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size);
+
+ if (!next_parameter) {
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(ResultCode(ErrorDescription::NoData, ErrorModule::Applet,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+ return;
+ }
+
+ if (next_parameter->destination_id != app_id) {
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::Applet, ErrorSummary::NotFound,
+ ErrorLevel::Status));
+ return;
+ }
+
IPC::RequestBuilder rb = rp.MakeBuilder(4, 4);
rb.Push(RESULT_SUCCESS); // No error
- rb.Push(next_parameter.sender_id);
- rb.Push(next_parameter.signal); // Signal type
- ASSERT_MSG(next_parameter.buffer.size() <= buffer_size, "Input static buffer is too small !");
- rb.Push(static_cast<u32>(next_parameter.buffer.size())); // Parameter buffer size
+ rb.Push(next_parameter->sender_id);
+ rb.Push(next_parameter->signal); // Signal type
+ ASSERT_MSG(next_parameter->buffer.size() <= buffer_size, "Input static buffer is too small !");
+ 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).Unwrap()
+ rb.PushMoveHandles((next_parameter->object != nullptr)
+ ? Kernel::g_handle_table.Create(next_parameter->object).Unwrap()
: 0);
- rb.PushStaticBuffer(buffer, static_cast<u32>(next_parameter.buffer.size()), 0);
- Memory::WriteBlock(buffer, next_parameter.buffer.data(), next_parameter.buffer.size());
+ rb.PushStaticBuffer(buffer, next_parameter->buffer.size(), 0);
+
+ Memory::WriteBlock(buffer, next_parameter->buffer.data(), next_parameter->buffer.size());
- LOG_WARNING(Service_APT, "called app_id=0x%08X, buffer_size=0x%08zX", app_id, buffer_size);
+ // Note: The NS module always clears the DSPSleep and DSPWakeup signals even in GlanceParameter.
+ if (next_parameter->signal == static_cast<u32>(SignalType::DspSleep) ||
+ next_parameter->signal == static_cast<u32>(SignalType::DspWakeup))
+ next_parameter = boost::none;
}
void CancelParameter(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xF, 4, 0); // 0xF0100
- u32 check_sender = rp.Pop<u32>();
+ bool check_sender = rp.Pop<bool>();
u32 sender_appid = rp.Pop<u32>();
- u32 check_receiver = rp.Pop<u32>();
+ bool check_receiver = rp.Pop<bool>();
u32 receiver_appid = rp.Pop<u32>();
+
+ bool cancellation_success = true;
+
+ if (!next_parameter) {
+ cancellation_success = false;
+ } else {
+ if (check_sender && next_parameter->sender_id != sender_appid)
+ cancellation_success = false;
+
+ if (check_receiver && next_parameter->destination_id != receiver_appid)
+ cancellation_success = false;
+ }
+
+ if (cancellation_success)
+ next_parameter = boost::none;
+
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+
rb.Push(RESULT_SUCCESS); // No error
- rb.Push(true); // Set to Success
+ rb.Push(cancellation_success);
- LOG_WARNING(Service_APT, "(STUBBED) called check_sender=0x%08X, sender_appid=0x%08X, "
- "check_receiver=0x%08X, receiver_appid=0x%08X",
- check_sender, sender_appid, check_receiver, receiver_appid);
+ LOG_DEBUG(Service_APT, "called check_sender=%u, sender_appid=0x%08X, "
+ "check_receiver=%u, receiver_appid=0x%08X",
+ check_sender, sender_appid, check_receiver, receiver_appid);
}
void PrepareToStartApplication(Service::Interface* self) {
@@ -383,7 +767,12 @@ void PrepareToStartLibraryApplet(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x18, 1, 0); // 0x180040
AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>());
+ LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id);
+
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+
+ // TODO(Subv): Launch the requested applet application.
+
auto applet = HLE::Applets::Applet::Get(applet_id);
if (applet) {
LOG_WARNING(Service_APT, "applet has already been started id=%08X", applet_id);
@@ -391,14 +780,32 @@ void PrepareToStartLibraryApplet(Service::Interface* self) {
} else {
rb.Push(HLE::Applets::Applet::Create(applet_id));
}
- LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id);
+}
+
+void PrepareToStartNewestHomeMenu(Service::Interface* self) {
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 0, 0); // 0x1A0000
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+
+ // TODO(Subv): This command can only be called by a System Applet (return 0xC8A0CC04 otherwise).
+
+ // This command must return an error when called, otherwise the Home Menu will try to reboot the
+ // system.
+ rb.Push(ResultCode(ErrorDescription::AlreadyExists, ErrorModule::Applet,
+ ErrorSummary::InvalidState, ErrorLevel::Status));
+
+ LOG_DEBUG(Service_APT, "called");
}
void PreloadLibraryApplet(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x16, 1, 0); // 0x160040
AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>());
+ LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id);
+
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+
+ // TODO(Subv): Launch the requested applet application.
+
auto applet = HLE::Applets::Applet::Get(applet_id);
if (applet) {
LOG_WARNING(Service_APT, "applet has already been started id=%08X", applet_id);
@@ -406,34 +813,40 @@ void PreloadLibraryApplet(Service::Interface* self) {
} else {
rb.Push(HLE::Applets::Applet::Create(applet_id));
}
- LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id);
}
void StartLibraryApplet(Service::Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1E, 2, 4); // 0x1E0084
AppletId applet_id = static_cast<AppletId>(rp.Pop<u32>());
- std::shared_ptr<HLE::Applets::Applet> applet = HLE::Applets::Applet::Get(applet_id);
-
- LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id);
-
- if (applet == nullptr) {
- LOG_ERROR(Service_APT, "unknown applet id=%08X", applet_id);
- IPC::RequestBuilder rb = rp.MakeBuilder(1, 0, false);
- rb.Push<u32>(-1); // TODO(Subv): Find the right error code
- return;
- }
size_t buffer_size = rp.Pop<u32>();
Kernel::Handle handle = rp.PopHandle();
VAddr buffer_addr = rp.PopStaticBuffer();
- AppletStartupParameter parameter;
- parameter.object = Kernel::g_handle_table.GetGeneric(handle);
- parameter.buffer.resize(buffer_size);
- Memory::ReadBlock(buffer_addr, parameter.buffer.data(), parameter.buffer.size());
+ LOG_DEBUG(Service_APT, "called applet_id=%08X", applet_id);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
- rb.Push(applet->Start(parameter));
+
+ // Send the Wakeup signal to the applet
+ MessageParameter param;
+ param.destination_id = static_cast<u32>(applet_id);
+ param.sender_id = static_cast<u32>(AppletId::Application);
+ param.object = Kernel::g_handle_table.GetGeneric(handle);
+ param.signal = static_cast<u32>(SignalType::Wakeup);
+ param.buffer.resize(buffer_size);
+ Memory::ReadBlock(buffer_addr, param.buffer.data(), param.buffer.size());
+ SendParameter(param);
+
+ // In case the applet is being HLEd, attempt to communicate with it.
+ if (auto applet = HLE::Applets::Applet::Get(applet_id)) {
+ AppletStartupParameter parameter;
+ parameter.object = Kernel::g_handle_table.GetGeneric(handle);
+ parameter.buffer.resize(buffer_size);
+ Memory::ReadBlock(buffer_addr, parameter.buffer.data(), parameter.buffer.size());
+ rb.Push(applet->Start(parameter));
+ } else {
+ rb.Push(RESULT_SUCCESS);
+ }
}
void CancelLibraryApplet(Service::Interface* self) {
@@ -647,125 +1060,6 @@ void CheckNew3DS(Service::Interface* self) {
LOG_WARNING(Service_APT, "(STUBBED) called");
}
-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()) {
- file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize());
- return true;
- }
-
- return false;
-}
-
void Init() {
AddService(new APT_A_Interface);
AddService(new APT_S_Interface);
@@ -789,19 +1083,24 @@ void Init() {
shared_font_loaded = false;
}
- lock = Kernel::Mutex::Create(false, "APT_U:Lock");
+ lock = Kernel::Mutex::Create(false, 0, "APT_U:Lock");
cpu_percent = 0;
unknown_ns_state_field = 0;
screen_capture_post_permission =
ScreencapPostPermission::CleanThePermission; // TODO(JamePeng): verify the initial value
- // TODO(bunnei): Check if these are created in Initialize or on APT process startup.
- notification_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Notification");
- parameter_event = Kernel::Event::Create(Kernel::ResetType::OneShot, "APT_U:Start");
-
- next_parameter.signal = static_cast<u32>(SignalType::Wakeup);
- next_parameter.destination_id = 0x300;
+ for (size_t slot = 0; slot < applet_slots.size(); ++slot) {
+ auto& slot_data = applet_slots[slot];
+ slot_data.slot = static_cast<AppletSlot>(slot);
+ slot_data.applet_id = AppletId::None;
+ slot_data.attributes.raw = 0;
+ slot_data.registered = false;
+ slot_data.notification_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "APT:Notification");
+ slot_data.parameter_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "APT:Parameter");
+ }
}
void Shutdown() {
@@ -809,10 +1108,14 @@ void Shutdown() {
shared_font_loaded = false;
shared_font_relocated = false;
lock = nullptr;
- notification_event = nullptr;
- parameter_event = nullptr;
- next_parameter.object = nullptr;
+ for (auto& slot : applet_slots) {
+ slot.registered = false;
+ slot.notification_event = nullptr;
+ slot.parameter_event = nullptr;
+ }
+
+ next_parameter = boost::none;
HLE::Applets::Shutdown();
}
diff --git a/src/core/hle/service/apt/apt.h b/src/core/hle/service/apt/apt.h
index ee80926d2..7b79e1f3e 100644
--- a/src/core/hle/service/apt/apt.h
+++ b/src/core/hle/service/apt/apt.h
@@ -72,6 +72,8 @@ enum class SignalType : u32 {
/// App Id's used by APT functions
enum class AppletId : u32 {
+ None = 0,
+ AnySystemApplet = 0x100,
HomeMenu = 0x101,
AlternateMenu = 0x103,
Camera = 0x110,
@@ -83,6 +85,7 @@ enum class AppletId : u32 {
Miiverse = 0x117,
MiiversePost = 0x118,
AmiiboSettings = 0x119,
+ AnySysLibraryApplet = 0x200,
SoftwareKeyboard1 = 0x201,
Ed1 = 0x202,
PnoteApp = 0x204,
@@ -116,6 +119,13 @@ enum class ScreencapPostPermission : u32 {
DisableScreenshotPostingToMiiverse = 3
};
+namespace ErrCodes {
+enum {
+ ParameterPresent = 2,
+ InvalidAppletSlot = 4,
+};
+} // namespace ErrCodes
+
/// Send a parameter to the currently-running application, which will read it via ReceiveParameter
void SendParameter(const MessageParameter& parameter);
@@ -410,6 +420,16 @@ void GetAppCpuTimeLimit(Service::Interface* self);
void PrepareToStartLibraryApplet(Service::Interface* self);
/**
+ * APT::PrepareToStartNewestHomeMenu service function
+ * Inputs:
+ * 0 : Command header [0x001A0000]
+ * Outputs:
+ * 0 : Return header
+ * 1 : Result of function
+ */
+void PrepareToStartNewestHomeMenu(Service::Interface* self);
+
+/**
* APT::PreloadLibraryApplet service function
* Inputs:
* 0 : Command header [0x00160040]
diff --git a/src/core/hle/service/apt/apt_s.cpp b/src/core/hle/service/apt/apt_s.cpp
index ec5668d05..bb78ee7d7 100644
--- a/src/core/hle/service/apt/apt_s.cpp
+++ b/src/core/hle/service/apt/apt_s.cpp
@@ -17,10 +17,10 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00060040, GetAppletInfo, "GetAppletInfo"},
{0x00070000, nullptr, "GetLastSignaledAppletId"},
{0x00080000, nullptr, "CountRegisteredApplet"},
- {0x00090040, nullptr, "IsRegistered"},
+ {0x00090040, IsRegistered, "IsRegistered"},
{0x000A0040, nullptr, "GetAttribute"},
{0x000B0040, InquireNotification, "InquireNotification"},
- {0x000C0104, nullptr, "SendParameter"},
+ {0x000C0104, SendParameter, "SendParameter"},
{0x000D0080, ReceiveParameter, "ReceiveParameter"},
{0x000E0080, GlanceParameter, "GlanceParameter"},
{0x000F0100, nullptr, "CancelParameter"},
@@ -34,11 +34,11 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00170040, nullptr, "FinishPreloadingLibraryApplet"},
{0x00180040, PrepareToStartLibraryApplet, "PrepareToStartLibraryApplet"},
{0x00190040, nullptr, "PrepareToStartSystemApplet"},
- {0x001A0000, nullptr, "PrepareToStartNewestHomeMenu"},
+ {0x001A0000, PrepareToStartNewestHomeMenu, "PrepareToStartNewestHomeMenu"},
{0x001B00C4, nullptr, "StartApplication"},
{0x001C0000, nullptr, "WakeupApplication"},
{0x001D0000, nullptr, "CancelApplication"},
- {0x001E0084, nullptr, "StartLibraryApplet"},
+ {0x001E0084, StartLibraryApplet, "StartLibraryApplet"},
{0x001F0084, nullptr, "StartSystemApplet"},
{0x00200044, nullptr, "StartNewestHomeMenu"},
{0x00210000, nullptr, "OrderToCloseApplication"},
diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp
index c9f9e9d95..8172edae8 100644
--- a/src/core/hle/service/cam/cam.cpp
+++ b/src/core/hle/service/cam/cam.cpp
@@ -177,7 +177,7 @@ void CompletionEventCallBack(u64 port_id, int) {
LOG_ERROR(Service_CAM, "The destination size (%u) doesn't match the source (%zu)!",
port.dest_size, buffer_size);
}
- Memory::WriteBlock(port.dest, buffer.data(), std::min<u32>(port.dest_size, buffer_size));
+ Memory::WriteBlock(port.dest, buffer.data(), std::min<size_t>(port.dest_size, buffer_size));
}
port.is_receiving = false;
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index 6624f1711..f78c25fb2 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -141,7 +141,7 @@ void GetCountryCodeString(Service::Interface* self) {
void GetCountryCodeID(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u16 country_code = cmd_buff[1];
+ u16 country_code = static_cast<u16>(cmd_buff[1]);
u16 country_code_id = 0;
// The following algorithm will fail if the first country code isn't 0.
@@ -168,7 +168,7 @@ void GetCountryCodeID(Service::Interface* self) {
cmd_buff[2] = country_code_id;
}
-static u32 GetRegionValue() {
+u32 GetRegionValue() {
if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT)
return preferred_region_code;
@@ -681,7 +681,7 @@ void GenerateConsoleUniqueId(u32& random_number, u64& console_id) {
CryptoPP::AutoSeededRandomPool rng;
random_number = rng.GenerateWord32(0, 0xFFFF);
u64_le local_friend_code_seed;
- rng.GenerateBlock(reinterpret_cast<byte*>(&local_friend_code_seed),
+ rng.GenerateBlock(reinterpret_cast<CryptoPP::byte*>(&local_friend_code_seed),
sizeof(local_friend_code_seed));
console_id = (local_friend_code_seed & 0x3FFFFFFFF) | (static_cast<u64>(random_number) << 48);
}
diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h
index 1659ebf32..282b6936b 100644
--- a/src/core/hle/service/cfg/cfg.h
+++ b/src/core/hle/service/cfg/cfg.h
@@ -101,6 +101,8 @@ void GetCountryCodeString(Service::Interface* self);
*/
void GetCountryCodeID(Service::Interface* self);
+u32 GetRegionValue();
+
/**
* CFG::SecureInfoGetRegion service function
* Inputs:
diff --git a/src/core/hle/service/csnd_snd.cpp b/src/core/hle/service/csnd_snd.cpp
index 9471ec1ef..aac903ccb 100644
--- a/src/core/hle/service/csnd_snd.cpp
+++ b/src/core/hle/service/csnd_snd.cpp
@@ -47,7 +47,7 @@ static void Initialize(Interface* self) {
MemoryPermission::ReadWrite, 0,
Kernel::MemoryRegion::BASE, "CSND:SharedMemory");
- mutex = Kernel::Mutex::Create(false, "CSND:mutex");
+ mutex = Kernel::Mutex::Create(false, 0, "CSND:mutex");
cmd_buff[1] = RESULT_SUCCESS.raw;
cmd_buff[2] = IPC::CopyHandleDesc(2);
diff --git a/src/core/hle/service/dlp/dlp_clnt.cpp b/src/core/hle/service/dlp/dlp_clnt.cpp
index 56f934b3f..6f2bf2061 100644
--- a/src/core/hle/service/dlp/dlp_clnt.cpp
+++ b/src/core/hle/service/dlp/dlp_clnt.cpp
@@ -8,7 +8,26 @@ namespace Service {
namespace DLP {
const Interface::FunctionInfo FunctionTable[] = {
- {0x000100C3, nullptr, "Initialize"}, {0x00110000, nullptr, "GetWirelessRebootPassphrase"},
+ {0x000100C3, nullptr, "Initialize"},
+ {0x00020000, nullptr, "Finalize"},
+ {0x00030000, nullptr, "GetEventDesc"},
+ {0x00040000, nullptr, "GetChannel"},
+ {0x00050180, nullptr, "StartScan"},
+ {0x00060000, nullptr, "StopScan"},
+ {0x00070080, nullptr, "GetServerInfo"},
+ {0x00080100, nullptr, "GetTitleInfo"},
+ {0x00090040, nullptr, "GetTitleInfoInOrder"},
+ {0x000A0080, nullptr, "DeleteScanInfo"},
+ {0x000B0100, nullptr, "PrepareForSystemDownload"},
+ {0x000C0000, nullptr, "StartSystemDownload"},
+ {0x000D0100, nullptr, "StartTitleDownload"},
+ {0x000E0000, nullptr, "GetMyStatus"},
+ {0x000F0040, nullptr, "GetConnectingNodes"},
+ {0x00100040, nullptr, "GetNodeInfo"},
+ {0x00110000, nullptr, "GetWirelessRebootPassphrase"},
+ {0x00120000, nullptr, "StopSession"},
+ {0x00130100, nullptr, "GetCupVersion"},
+ {0x00140100, nullptr, "GetDupAvailability"},
};
DLP_CLNT_Interface::DLP_CLNT_Interface() {
diff --git a/src/core/hle/service/dlp/dlp_fkcl.cpp b/src/core/hle/service/dlp/dlp_fkcl.cpp
index 29b9d52e0..fe6be7d32 100644
--- a/src/core/hle/service/dlp/dlp_fkcl.cpp
+++ b/src/core/hle/service/dlp/dlp_fkcl.cpp
@@ -8,7 +8,23 @@ namespace Service {
namespace DLP {
const Interface::FunctionInfo FunctionTable[] = {
- {0x00010083, nullptr, "Initialize"}, {0x000F0000, nullptr, "GetWirelessRebootPassphrase"},
+ {0x00010083, nullptr, "Initialize"},
+ {0x00020000, nullptr, "Finalize"},
+ {0x00030000, nullptr, "GetEventDesc"},
+ {0x00040000, nullptr, "GetChannels"},
+ {0x00050180, nullptr, "StartScan"},
+ {0x00060000, nullptr, "StopScan"},
+ {0x00070080, nullptr, "GetServerInfo"},
+ {0x00080100, nullptr, "GetTitleInfo"},
+ {0x00090040, nullptr, "GetTitleInfoInOrder"},
+ {0x000A0080, nullptr, "DeleteScanInfo"},
+ {0x000B0100, nullptr, "StartFakeSession"},
+ {0x000C0000, nullptr, "GetMyStatus"},
+ {0x000D0040, nullptr, "GetConnectingNodes"},
+ {0x000E0040, nullptr, "GetNodeInfo"},
+ {0x000F0000, nullptr, "GetWirelessRebootPassphrase"},
+ {0x00100000, nullptr, "StopSession"},
+ {0x00110203, nullptr, "Initialize2"},
};
DLP_FKCL_Interface::DLP_FKCL_Interface() {
diff --git a/src/core/hle/service/dlp/dlp_srvr.cpp b/src/core/hle/service/dlp/dlp_srvr.cpp
index 32cfa2c44..1bcea43d3 100644
--- a/src/core/hle/service/dlp/dlp_srvr.cpp
+++ b/src/core/hle/service/dlp/dlp_srvr.cpp
@@ -11,7 +11,7 @@
namespace Service {
namespace DLP {
-static void unk_0x000E0040(Interface* self) {
+static void IsChild(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = RESULT_SUCCESS.raw;
@@ -24,14 +24,19 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00010183, nullptr, "Initialize"},
{0x00020000, nullptr, "Finalize"},
{0x00030000, nullptr, "GetServerState"},
+ {0x00040000, nullptr, "GetEventDescription"},
{0x00050080, nullptr, "StartAccepting"},
+ {0x00060000, nullptr, "EndAccepting"},
{0x00070000, nullptr, "StartDistribution"},
{0x000800C0, nullptr, "SendWirelessRebootPassphrase"},
{0x00090040, nullptr, "AcceptClient"},
+ {0x000A0040, nullptr, "DisconnectClient"},
{0x000B0042, nullptr, "GetConnectingClients"},
{0x000C0040, nullptr, "GetClientInfo"},
{0x000D0040, nullptr, "GetClientState"},
- {0x000E0040, unk_0x000E0040, "unk_0x000E0040"},
+ {0x000E0040, IsChild, "IsChild"},
+ {0x000F0303, nullptr, "InitializeWithName"},
+ {0x00100000, nullptr, "GetDupNoticeNeed"},
};
DLP_SRVR_Interface::DLP_SRVR_Interface() {
diff --git a/src/core/hle/service/dsp_dsp.cpp b/src/core/hle/service/dsp_dsp.cpp
index 7d746054f..42f8950f9 100644
--- a/src/core/hle/service/dsp_dsp.cpp
+++ b/src/core/hle/service/dsp_dsp.cpp
@@ -147,9 +147,10 @@ static void LoadComponent(Service::Interface* self) {
LOG_INFO(Service_DSP, "Firmware hash: %#" PRIx64,
Common::ComputeHash64(component_data.data(), component_data.size()));
// Some versions of the firmware have the location of DSP structures listed here.
- ASSERT(size > 0x37C);
- LOG_INFO(Service_DSP, "Structures hash: %#" PRIx64,
- Common::ComputeHash64(component_data.data() + 0x340, 60));
+ if (size > 0x37C) {
+ LOG_INFO(Service_DSP, "Structures hash: %#" PRIx64,
+ Common::ComputeHash64(component_data.data() + 0x340, 60));
+ }
LOG_WARNING(Service_DSP,
"(STUBBED) called size=0x%X, prog_mask=0x%08X, data_mask=0x%08X, buffer=0x%08X",
diff --git a/src/core/hle/service/fs/archive.cpp b/src/core/hle/service/fs/archive.cpp
index 033fbc9aa..4ee7df73c 100644
--- a/src/core/hle/service/fs/archive.cpp
+++ b/src/core/hle/service/fs/archive.cpp
@@ -20,6 +20,7 @@
#include "core/file_sys/archive_savedata.h"
#include "core/file_sys/archive_sdmc.h"
#include "core/file_sys/archive_sdmcwriteonly.h"
+#include "core/file_sys/archive_selfncch.h"
#include "core/file_sys/archive_systemsavedata.h"
#include "core/file_sys/directory_backend.h"
#include "core/file_sys/errors.h"
@@ -48,7 +49,7 @@ struct hash<Service::FS::ArchiveIdCode> {
return std::hash<Type>()(static_cast<Type>(id_code));
}
};
-}
+} // namespace std
static constexpr Kernel::Handle INVALID_HANDLE{};
@@ -216,7 +217,7 @@ void Directory::HandleSyncRequest(Kernel::SharedPtr<Kernel::ServerSession> serve
LOG_TRACE(Service_FS, "Read %s: count=%d", GetName().c_str(), count);
// Number of entries actually read
- u32 read = backend->Read(entries.size(), entries.data());
+ u32 read = backend->Read(static_cast<u32>(entries.size()), entries.data());
cmd_buff[2] = read;
Memory::WriteBlock(address, entries.data(), read * sizeof(FileSys::Entry));
break;
@@ -564,6 +565,21 @@ void RegisterArchiveTypes() {
auto systemsavedata_factory =
std::make_unique<FileSys::ArchiveFactory_SystemSaveData>(nand_directory);
RegisterArchiveType(std::move(systemsavedata_factory), ArchiveIdCode::SystemSaveData);
+
+ auto selfncch_factory = std::make_unique<FileSys::ArchiveFactory_SelfNCCH>();
+ RegisterArchiveType(std::move(selfncch_factory), ArchiveIdCode::SelfNCCH);
+}
+
+void RegisterSelfNCCH(Loader::AppLoader& app_loader) {
+ auto itr = id_code_map.find(ArchiveIdCode::SelfNCCH);
+ if (itr == id_code_map.end()) {
+ LOG_ERROR(Service_FS,
+ "Could not register a new NCCH because the SelfNCCH archive hasn't been created");
+ return;
+ }
+
+ auto* factory = static_cast<FileSys::ArchiveFactory_SelfNCCH*>(itr->second.get());
+ factory->Register(app_loader);
}
void UnregisterArchiveTypes() {
diff --git a/src/core/hle/service/fs/archive.h b/src/core/hle/service/fs/archive.h
index 3a3371c88..e3c8fc2ef 100644
--- a/src/core/hle/service/fs/archive.h
+++ b/src/core/hle/service/fs/archive.h
@@ -21,6 +21,10 @@ static constexpr char SYSTEM_ID[]{"00000000000000000000000000000000"};
/// The scrambled SD card CID, also known as ID1
static constexpr char SDCARD_ID[]{"00000000000000000000000000000000"};
+namespace Loader {
+class AppLoader;
+}
+
namespace Service {
namespace FS {
@@ -259,6 +263,9 @@ void ArchiveInit();
/// Shutdown archives
void ArchiveShutdown();
+/// Registers a new NCCH file with the SelfNCCH archive factory
+void RegisterSelfNCCH(Loader::AppLoader& app_loader);
+
/// Register all archive types
void RegisterArchiveTypes();
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 2014b8461..379fbd71c 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -7,8 +7,9 @@
#include <cmath>
#include <memory>
#include "common/logging/log.h"
+#include "core/3ds.h"
+#include "core/core.h"
#include "core/core_timing.h"
-#include "core/frontend/emu_window.h"
#include "core/frontend/input.h"
#include "core/hle/ipc.h"
#include "core/hle/kernel/event.h"
@@ -18,7 +19,6 @@
#include "core/hle/service/hid/hid_spvr.h"
#include "core/hle/service/hid/hid_user.h"
#include "core/hle/service/service.h"
-#include "video_core/video_core.h"
namespace Service {
namespace HID {
@@ -50,10 +50,15 @@ constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
+constexpr float accelerometer_coef = 512.0f; // measured from hw test result
+constexpr float gyroscope_coef = 14.375f; // got from hwtest GetGyroscopeLowRawToDpsCoefficient call
+
static std::atomic<bool> is_device_reload_pending;
static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::NUM_BUTTONS_HID>
buttons;
static std::unique_ptr<Input::AnalogDevice> circle_pad;
+static std::unique_ptr<Input::MotionDevice> motion_device;
+static std::unique_ptr<Input::TouchDevice> touch_device;
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
// 30 degree and 60 degree are angular thresholds for directions
@@ -90,6 +95,8 @@ static void LoadInputDevices() {
buttons.begin(), Input::CreateDevice<Input::ButtonDevice>);
circle_pad = Input::CreateDevice<Input::AnalogDevice>(
Settings::values.analogs[Settings::NativeAnalog::CirclePad]);
+ motion_device = Input::CreateDevice<Input::MotionDevice>(Settings::values.motion_device);
+ touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
}
static void UnloadInputDevices() {
@@ -97,6 +104,8 @@ static void UnloadInputDevices() {
button.reset();
}
circle_pad.reset();
+ motion_device.reset();
+ touch_device.reset();
}
static void UpdatePadCallback(u64 userdata, int cycles_late) {
@@ -165,8 +174,10 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) {
// Get the current touch entry
TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index];
bool pressed = false;
-
- std::tie(touch_entry.x, touch_entry.y, pressed) = VideoCore::g_emu_window->GetTouchState();
+ float x, y;
+ std::tie(x, y, pressed) = touch_device->GetStatus();
+ touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth);
+ touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight);
touch_entry.valid.Assign(pressed ? 1 : 0);
// TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which
@@ -193,10 +204,19 @@ static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
mem->accelerometer.index = next_accelerometer_index;
next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size();
+ Math::Vec3<float> accel;
+ std::tie(accel, std::ignore) = motion_device->GetStatus();
+ accel *= accelerometer_coef;
+ // TODO(wwylele): do a time stretch like the one in UpdateGyroscopeCallback
+ // The time stretch formula should be like
+ // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
+
AccelerometerDataEntry& accelerometer_entry =
mem->accelerometer.entries[mem->accelerometer.index];
- std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) =
- VideoCore::g_emu_window->GetAccelerometerState();
+
+ accelerometer_entry.x = static_cast<s16>(accel.x);
+ accelerometer_entry.y = static_cast<s16>(accel.y);
+ accelerometer_entry.z = static_cast<s16>(accel.z);
// Make up "raw" entry
// TODO(wwylele):
@@ -227,8 +247,14 @@ static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size();
GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index];
- std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) =
- VideoCore::g_emu_window->GetGyroscopeState();
+
+ Math::Vec3<float> gyro;
+ std::tie(std::ignore, gyro) = motion_device->GetStatus();
+ double stretch = Core::System::GetInstance().perf_stats.GetLastFrameTimeScale();
+ gyro *= gyroscope_coef * static_cast<float>(stretch);
+ gyroscope_entry.x = static_cast<s16>(gyro.x);
+ gyroscope_entry.y = static_cast<s16>(gyro.y);
+ gyroscope_entry.z = static_cast<s16>(gyro.z);
// Make up "raw" entry
mem->gyroscope.raw_entry.x = gyroscope_entry.x;
@@ -326,7 +352,7 @@ void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self) {
cmd_buff[1] = RESULT_SUCCESS.raw;
- f32 coef = VideoCore::g_emu_window->GetGyroscopeRawToDpsCoefficient();
+ f32 coef = gyroscope_coef;
memcpy(&cmd_buff[2], &coef, 4);
}
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 1ef972e70..ef25926b5 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -24,7 +24,7 @@ namespace HID {
*/
struct PadState {
union {
- u32 hex;
+ u32 hex{};
BitField<0, 1, u32> a;
BitField<1, 1, u32> b;
diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp
index 837413f93..0912d5756 100644
--- a/src/core/hle/service/ir/ir_rst.cpp
+++ b/src/core/hle/service/ir/ir_rst.cpp
@@ -18,7 +18,7 @@ namespace Service {
namespace IR {
union PadState {
- u32_le hex;
+ u32_le hex{};
BitField<14, 1, u32_le> zl;
BitField<15, 1, u32_le> zr;
diff --git a/src/core/hle/service/ldr_ro/cro_helper.h b/src/core/hle/service/ldr_ro/cro_helper.h
index 3bc10dbdc..57b4fb6df 100644
--- a/src/core/hle/service/ldr_ro/cro_helper.h
+++ b/src/core/hle/service/ldr_ro/cro_helper.h
@@ -413,7 +413,8 @@ private:
*/
template <typename T>
void GetEntry(std::size_t index, T& data) const {
- Memory::ReadBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T));
+ Memory::ReadBlock(GetField(T::TABLE_OFFSET_FIELD) + static_cast<u32>(index * sizeof(T)),
+ &data, sizeof(T));
}
/**
@@ -425,7 +426,8 @@ private:
*/
template <typename T>
void SetEntry(std::size_t index, const T& data) {
- Memory::WriteBlock(GetField(T::TABLE_OFFSET_FIELD) + index * sizeof(T), &data, sizeof(T));
+ Memory::WriteBlock(GetField(T::TABLE_OFFSET_FIELD) + static_cast<u32>(index * sizeof(T)),
+ &data, sizeof(T));
}
/**
diff --git a/src/core/hle/service/nim/nim.cpp b/src/core/hle/service/nim/nim.cpp
index d5624fe54..b10d5852b 100644
--- a/src/core/hle/service/nim/nim.cpp
+++ b/src/core/hle/service/nim/nim.cpp
@@ -5,6 +5,8 @@
#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/event.h"
#include "core/hle/service/nim/nim.h"
#include "core/hle/service/nim/nim_aoc.h"
#include "core/hle/service/nim/nim_s.h"
@@ -14,6 +16,16 @@
namespace Service {
namespace NIM {
+static Kernel::SharedPtr<Kernel::Event> nim_system_update_event;
+
+void CheckForSysUpdateEvent(Service::Interface* self) {
+ IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 0, 0); // 0x50000
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(RESULT_SUCCESS);
+ rb.PushCopyHandles(Kernel::g_handle_table.Create(nim_system_update_event).Unwrap());
+ LOG_TRACE(Service_NIM, "called");
+}
+
void CheckSysUpdateAvailable(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
@@ -29,9 +41,13 @@ void Init() {
AddService(new NIM_AOC_Interface);
AddService(new NIM_S_Interface);
AddService(new NIM_U_Interface);
+
+ nim_system_update_event = Kernel::Event::Create(ResetType::OneShot, "NIM System Update Event");
}
-void Shutdown() {}
+void Shutdown() {
+ nim_system_update_event = nullptr;
+}
} // namespace NIM
diff --git a/src/core/hle/service/nim/nim.h b/src/core/hle/service/nim/nim.h
index c3106f18b..dbf605e5a 100644
--- a/src/core/hle/service/nim/nim.h
+++ b/src/core/hle/service/nim/nim.h
@@ -11,6 +11,17 @@ class Interface;
namespace NIM {
/**
+ * NIM::CheckForSysUpdateEvent service function
+ * Inputs:
+ * 1 : None
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Copy handle descriptor
+ * 3 : System Update event handle
+ */
+void CheckForSysUpdateEvent(Service::Interface* self);
+
+/**
* NIM::CheckSysUpdateAvailable service function
* Inputs:
* 1 : None
diff --git a/src/core/hle/service/nim/nim_u.cpp b/src/core/hle/service/nim/nim_u.cpp
index 7664bad60..569660278 100644
--- a/src/core/hle/service/nim/nim_u.cpp
+++ b/src/core/hle/service/nim/nim_u.cpp
@@ -12,7 +12,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00010000, nullptr, "StartSysUpdate"},
{0x00020000, nullptr, "GetUpdateDownloadProgress"},
{0x00040000, nullptr, "FinishTitlesInstall"},
- {0x00050000, nullptr, "CheckForSysUpdateEvent"},
+ {0x00050000, CheckForSysUpdateEvent, "CheckForSysUpdateEvent"},
{0x00090000, CheckSysUpdateAvailable, "CheckSysUpdateAvailable"},
{0x000A0000, nullptr, "GetState"},
{0x000B0000, nullptr, "GetSystemTitleHash"},
diff --git a/src/core/hle/service/ns/ns.cpp b/src/core/hle/service/ns/ns.cpp
new file mode 100644
index 000000000..9e19c38bf
--- /dev/null
+++ b/src/core/hle/service/ns/ns.cpp
@@ -0,0 +1,16 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/ns/ns.h"
+#include "core/hle/service/ns/ns_s.h"
+
+namespace Service {
+namespace NS {
+
+void InstallInterfaces(SM::ServiceManager& service_manager) {
+ std::make_shared<NS_S>()->InstallAsService(service_manager);
+}
+
+} // namespace NS
+} // namespace Service
diff --git a/src/core/hle/service/ns/ns.h b/src/core/hle/service/ns/ns.h
new file mode 100644
index 000000000..c3d67d98c
--- /dev/null
+++ b/src/core/hle/service/ns/ns.h
@@ -0,0 +1,16 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NS {
+
+/// Registers all NS services with the specified service manager.
+void InstallInterfaces(SM::ServiceManager& service_manager);
+
+} // namespace NS
+} // namespace Service
diff --git a/src/core/hle/service/ns/ns_s.cpp b/src/core/hle/service/ns/ns_s.cpp
new file mode 100644
index 000000000..d952888dc
--- /dev/null
+++ b/src/core/hle/service/ns/ns_s.cpp
@@ -0,0 +1,34 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/ns/ns_s.h"
+
+namespace Service {
+namespace NS {
+
+NS_S::NS_S() : ServiceFramework("ns:s", 2) {
+ static const FunctionInfo functions[] = {
+ {0x000100C0, nullptr, "LaunchFIRM"},
+ {0x000200C0, nullptr, "LaunchTitle"},
+ {0x00030000, nullptr, "TerminateApplication"},
+ {0x00040040, nullptr, "TerminateProcess"},
+ {0x000500C0, nullptr, "LaunchApplicationFIRM"},
+ {0x00060042, nullptr, "SetFIRMParams4A0"},
+ {0x00070042, nullptr, "CardUpdateInitialize"},
+ {0x00080000, nullptr, "CardUpdateShutdown"},
+ {0x000D0140, nullptr, "SetTWLBannerHMAC"},
+ {0x000E0000, nullptr, "ShutdownAsync"},
+ {0x00100180, nullptr, "RebootSystem"},
+ {0x00110100, nullptr, "TerminateTitle"},
+ {0x001200C0, nullptr, "SetApplicationCpuTimeLimit"},
+ {0x00150140, nullptr, "LaunchApplication"},
+ {0x00160000, nullptr, "RebootSystemClean"},
+ };
+ RegisterHandlers(functions);
+}
+
+NS_S::~NS_S() = default;
+
+} // namespace NS
+} // namespace Service
diff --git a/src/core/hle/service/ns_s.h b/src/core/hle/service/ns/ns_s.h
index 90288a521..660ae453f 100644
--- a/src/core/hle/service/ns_s.h
+++ b/src/core/hle/service/ns/ns_s.h
@@ -4,18 +4,17 @@
#pragma once
+#include "core/hle/kernel/kernel.h"
#include "core/hle/service/service.h"
namespace Service {
namespace NS {
-class NS_S final : public Interface {
+/// Interface to "ns:s" service
+class NS_S final : public ServiceFramework<NS_S> {
public:
NS_S();
-
- std::string GetPortName() const override {
- return "ns:s";
- }
+ ~NS_S();
};
} // namespace NS
diff --git a/src/core/hle/service/ns_s.cpp b/src/core/hle/service/ns_s.cpp
deleted file mode 100644
index 215c9aacc..000000000
--- a/src/core/hle/service/ns_s.cpp
+++ /dev/null
@@ -1,33 +0,0 @@
-// Copyright 2015 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "core/hle/service/ns_s.h"
-
-namespace Service {
-namespace NS {
-
-const Interface::FunctionInfo FunctionTable[] = {
- {0x000100C0, nullptr, "LaunchFIRM"},
- {0x000200C0, nullptr, "LaunchTitle"},
- {0x00030000, nullptr, "TerminateApplication"},
- {0x00040040, nullptr, "TerminateProcess"},
- {0x000500C0, nullptr, "LaunchApplicationFIRM"},
- {0x00060042, nullptr, "SetFIRMParams4A0"},
- {0x00070042, nullptr, "CardUpdateInitialize"},
- {0x00080000, nullptr, "CardUpdateShutdown"},
- {0x000D0140, nullptr, "SetTWLBannerHMAC"},
- {0x000E0000, nullptr, "ShutdownAsync"},
- {0x00100180, nullptr, "RebootSystem"},
- {0x00110100, nullptr, "TerminateTitle"},
- {0x001200C0, nullptr, "SetApplicationCpuTimeLimit"},
- {0x00150140, nullptr, "LaunchApplication"},
- {0x00160000, nullptr, "RebootSystemClean"},
-};
-
-NS_S::NS_S() {
- Register(FunctionTable);
-}
-
-} // namespace NS
-} // namespace Service
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp
index 6dbdff044..87a6b0eca 100644
--- a/src/core/hle/service/nwm/nwm_uds.cpp
+++ b/src/core/hle/service/nwm/nwm_uds.cpp
@@ -2,8 +2,11 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <array>
#include <cstring>
+#include <list>
+#include <mutex>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
@@ -12,11 +15,14 @@
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/event.h"
#include "core/hle/kernel/shared_memory.h"
+#include "core/hle/lock.h"
#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_connection.h"
#include "core/hle/service/nwm/uds_data.h"
#include "core/memory.h"
+#include "network/network.h"
namespace Service {
namespace NWM {
@@ -34,9 +40,12 @@ static ConnectionStatus connection_status{};
/* Node information about the current network.
* The amount of elements in this vector is always the maximum number
* of nodes specified in the network configuration.
- * The first node is always the host, so this always contains at least 1 entry.
+ * The first node is always the host.
*/
-static NodeList node_info(1);
+static NodeList node_info;
+
+// Node information about our own system.
+static NodeInfo current_node;
// Mapping of bind node ids to their respective events.
static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events;
@@ -51,6 +60,298 @@ static NetworkInfo network_info;
// Event that will generate and send the 802.11 beacon frames.
static int beacon_broadcast_event;
+// Mutex to synchronize access to the connection status between the emulation thread and the
+// network thread.
+static std::mutex connection_status_mutex;
+
+// Mutex to synchronize access to the list of received beacons between the emulation thread and the
+// network thread.
+static std::mutex beacon_mutex;
+
+// Number of beacons to store before we start dropping the old ones.
+// TODO(Subv): Find a more accurate value for this limit.
+constexpr size_t MaxBeaconFrames = 15;
+
+// List of the last <MaxBeaconFrames> beacons received from the network.
+static std::list<Network::WifiPacket> received_beacons;
+
+/**
+ * Returns a list of received 802.11 beacon frames from the specified sender since the last call.
+ */
+std::list<Network::WifiPacket> GetReceivedBeacons(const MacAddress& sender) {
+ std::lock_guard<std::mutex> lock(beacon_mutex);
+ if (sender != Network::BroadcastMac) {
+ std::list<Network::WifiPacket> filtered_list;
+ const auto beacon = std::find_if(received_beacons.begin(), received_beacons.end(),
+ [&sender](const Network::WifiPacket& packet) {
+ return packet.transmitter_address == sender;
+ });
+ if (beacon != received_beacons.end()) {
+ filtered_list.push_back(*beacon);
+ // TODO(B3N30): Check if the complete deque is cleared or just the fetched entries
+ received_beacons.erase(beacon);
+ }
+ return filtered_list;
+ }
+ return std::move(received_beacons);
+}
+
+/// Sends a WifiPacket to the room we're currently connected to.
+void SendPacket(Network::WifiPacket& packet) {
+ // TODO(Subv): Implement.
+}
+
+/*
+ * Returns an available index in the nodes array for the
+ * currently-hosted UDS network.
+ */
+static u16 GetNextAvailableNodeId() {
+ for (u16 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");
+}
+
+// Inserts the received beacon frame in the beacon queue and removes any older beacons if the size
+// limit is exceeded.
+void HandleBeaconFrame(const Network::WifiPacket& packet) {
+ std::lock_guard<std::mutex> lock(beacon_mutex);
+ const auto unique_beacon =
+ std::find_if(received_beacons.begin(), received_beacons.end(),
+ [&packet](const Network::WifiPacket& new_packet) {
+ return new_packet.transmitter_address == packet.transmitter_address;
+ });
+ if (unique_beacon != received_beacons.end()) {
+ // We already have a beacon from the same mac in the deque, remove the old one;
+ received_beacons.erase(unique_beacon);
+ }
+
+ received_beacons.emplace_back(packet);
+
+ // Discard old beacons if the buffer is full.
+ if (received_beacons.size() > MaxBeaconFrames)
+ received_beacons.pop_front();
+}
+
+void HandleAssociationResponseFrame(const Network::WifiPacket& packet) {
+ auto assoc_result = GetAssociationResult(packet.data);
+
+ ASSERT_MSG(std::get<AssocStatus>(assoc_result) == AssocStatus::Successful,
+ "Could not join network");
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::Connecting));
+ }
+
+ // Send the EAPoL-Start packet to the server.
+ using Network::WifiPacket;
+ WifiPacket eapol_start;
+ eapol_start.channel = network_channel;
+ eapol_start.data = GenerateEAPoLStartFrame(std::get<u16>(assoc_result), current_node);
+ // TODO(B3N30): Encrypt the packet.
+ eapol_start.destination_address = packet.transmitter_address;
+ eapol_start.type = WifiPacket::PacketType::Data;
+
+ SendPacket(eapol_start);
+}
+
+static void HandleEAPoLPacket(const Network::WifiPacket& packet) {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+
+ if (GetEAPoLFrameType(packet.data) == EAPoLStartMagic) {
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is %u",
+ connection_status.status);
+ return;
+ }
+
+ auto node = DeserializeNodeInfoFromFrame(packet.data);
+
+ if (connection_status.max_nodes == connection_status.total_nodes) {
+ // Reject connection attempt
+ LOG_ERROR(Service_NWM, "Reached maximum nodes, but reject packet wasn't sent.");
+ // TODO(B3N30): Figure out what packet is sent here
+ return;
+ }
+
+ // Get an unused network node id
+ u16 node_id = GetNextAvailableNodeId();
+ node.network_node_id = node_id + 1;
+
+ connection_status.node_bitmask |= 1 << node_id;
+ connection_status.changed_nodes |= 1 << node_id;
+ connection_status.nodes[node_id] = node.network_node_id;
+ connection_status.total_nodes++;
+
+ u8 current_nodes = network_info.total_nodes;
+ node_info[current_nodes] = node;
+
+ network_info.total_nodes++;
+
+ // Send the EAPoL-Logoff packet.
+ using Network::WifiPacket;
+ WifiPacket eapol_logoff;
+ eapol_logoff.channel = network_channel;
+ eapol_logoff.data =
+ GenerateEAPoLLogoffFrame(packet.transmitter_address, node.network_node_id, node_info,
+ network_info.max_nodes, network_info.total_nodes);
+ // TODO(Subv): Encrypt the packet.
+ eapol_logoff.destination_address = packet.transmitter_address;
+ eapol_logoff.type = WifiPacket::PacketType::Data;
+
+ SendPacket(eapol_logoff);
+ // TODO(B3N30): Broadcast updated node list
+ // The 3ds does this presumably to support spectators.
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ connection_status_event->Signal();
+ } else {
+ if (connection_status.status != static_cast<u32>(NetworkStatus::NotConnected)) {
+ LOG_DEBUG(Service_NWM, "Connection sequence aborted, because connection status is %u",
+ connection_status.status);
+ return;
+ }
+ auto logoff = ParseEAPoLLogoffFrame(packet.data);
+
+ network_info.total_nodes = logoff.connected_nodes;
+ network_info.max_nodes = logoff.max_nodes;
+
+ connection_status.network_node_id = logoff.assigned_node_id;
+ connection_status.total_nodes = logoff.connected_nodes;
+ connection_status.max_nodes = logoff.max_nodes;
+
+ node_info.clear();
+ node_info.reserve(network_info.max_nodes);
+ for (size_t index = 0; index < logoff.connected_nodes; ++index) {
+ connection_status.node_bitmask |= 1 << index;
+ connection_status.changed_nodes |= 1 << index;
+ connection_status.nodes[index] = logoff.nodes[index].network_node_id;
+
+ node_info.emplace_back(DeserializeNodeInfo(logoff.nodes[index]));
+ }
+
+ // We're now connected, signal the application
+ connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsClient);
+ // Some games require ConnectToNetwork to block, for now it doesn't
+ // If blocking is implemented this lock needs to be changed,
+ // otherwise it might cause deadlocks
+ std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
+ connection_status_event->Signal();
+ }
+}
+
+/*
+ * Start a connection sequence with an UDS server. The sequence starts by sending an 802.11
+ * authentication frame with SEQ1.
+ */
+void StartConnectionSequence(const MacAddress& server) {
+ using Network::WifiPacket;
+ WifiPacket auth_request;
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::NotConnected));
+
+ // TODO(Subv): Handle timeout.
+
+ // Send an authentication frame with SEQ1
+ auth_request.channel = network_channel;
+ auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ1);
+ auth_request.destination_address = server;
+ auth_request.type = WifiPacket::PacketType::Authentication;
+ }
+
+ SendPacket(auth_request);
+}
+
+/// Sends an Association Response frame to the specified mac address
+void SendAssociationResponseFrame(const MacAddress& address) {
+ using Network::WifiPacket;
+ WifiPacket assoc_response;
+
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ LOG_ERROR(Service_NWM, "Connection sequence aborted, because connection status is %u",
+ connection_status.status);
+ return;
+ }
+
+ assoc_response.channel = network_channel;
+ // TODO(Subv): This will cause multiple clients to end up with the same association id, but
+ // we're not using that for anything.
+ u16 association_id = 1;
+ assoc_response.data = GenerateAssocResponseFrame(AssocStatus::Successful, association_id,
+ network_info.network_id);
+ assoc_response.destination_address = address;
+ assoc_response.type = WifiPacket::PacketType::AssociationResponse;
+ }
+
+ SendPacket(assoc_response);
+}
+
+/*
+ * Handles the authentication request frame and sends the authentication response and association
+ * response frames. Once an Authentication frame with SEQ1 is received by the server, it responds
+ * with an Authentication frame containing SEQ2, and immediately sends an Association response frame
+ * containing the details of the access point and the assigned association id for the new client.
+ */
+void HandleAuthenticationFrame(const Network::WifiPacket& packet) {
+ // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior
+ if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) {
+ using Network::WifiPacket;
+ WifiPacket auth_request;
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) {
+ LOG_ERROR(Service_NWM,
+ "Connection sequence aborted, because connection status is %u",
+ connection_status.status);
+ return;
+ }
+
+ // Respond with an authentication response frame with SEQ2
+ auth_request.channel = network_channel;
+ auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2);
+ auth_request.destination_address = packet.transmitter_address;
+ auth_request.type = WifiPacket::PacketType::Authentication;
+ }
+ SendPacket(auth_request);
+
+ SendAssociationResponseFrame(packet.transmitter_address);
+ }
+}
+
+static void HandleDataFrame(const Network::WifiPacket& packet) {
+ switch (GetFrameEtherType(packet.data)) {
+ case EtherType::EAPoL:
+ HandleEAPoLPacket(packet);
+ break;
+ case EtherType::SecureData:
+ // TODO(B3N30): Handle SecureData packets
+ break;
+ }
+}
+
+/// Callback to parse and handle a received wifi packet.
+void OnWifiPacketReceived(const Network::WifiPacket& packet) {
+ switch (packet.type) {
+ case Network::WifiPacket::PacketType::Beacon:
+ HandleBeaconFrame(packet);
+ break;
+ case Network::WifiPacket::PacketType::Authentication:
+ HandleAuthenticationFrame(packet);
+ break;
+ case Network::WifiPacket::PacketType::AssociationResponse:
+ HandleAssociationResponseFrame(packet);
+ break;
+ case Network::WifiPacket::PacketType::Data:
+ HandleDataFrame(packet);
+ break;
+ }
+}
+
/**
* NWM_UDS::Shutdown service function
* Inputs:
@@ -111,11 +412,10 @@ static void RecvBeaconBroadcastData(Interface* self) {
u32 total_size = sizeof(BeaconDataReplyHeader);
// Retrieve all beacon frames that were received from the desired mac address.
- std::deque<WifiPacket> beacons =
- GetReceivedPackets(WifiPacket::PacketType::Beacon, mac_address);
+ auto beacons = GetReceivedBeacons(mac_address);
BeaconDataReplyHeader data_reply_header{};
- data_reply_header.total_entries = beacons.size();
+ data_reply_header.total_entries = static_cast<u32>(beacons.size());
data_reply_header.max_output_size = out_buffer_size;
Memory::WriteBlock(current_buffer_pos, &data_reply_header, sizeof(BeaconDataReplyHeader));
@@ -125,8 +425,8 @@ static void RecvBeaconBroadcastData(Interface* self) {
for (const auto& beacon : beacons) {
BeaconEntryHeader entry{};
// TODO(Subv): Figure out what this size is used for.
- entry.unk_size = sizeof(BeaconEntryHeader) + beacon.data.size();
- entry.total_size = sizeof(BeaconEntryHeader) + beacon.data.size();
+ entry.unk_size = static_cast<u32>(sizeof(BeaconEntryHeader) + beacon.data.size());
+ entry.total_size = static_cast<u32>(sizeof(BeaconEntryHeader) + beacon.data.size());
entry.wifi_channel = beacon.channel;
entry.header_size = sizeof(BeaconEntryHeader);
entry.mac_address = beacon.transmitter_address;
@@ -137,9 +437,9 @@ static void RecvBeaconBroadcastData(Interface* self) {
current_buffer_pos += sizeof(BeaconEntryHeader);
Memory::WriteBlock(current_buffer_pos, beacon.data.data(), beacon.data.size());
- current_buffer_pos += beacon.data.size();
+ current_buffer_pos += static_cast<VAddr>(beacon.data.size());
- total_size += sizeof(BeaconEntryHeader) + beacon.data.size();
+ total_size += static_cast<u32>(sizeof(BeaconEntryHeader) + beacon.data.size());
}
// Update the total size in the structure and write it to the buffer again.
@@ -174,7 +474,7 @@ static void InitializeWithVersion(Interface* self) {
u32 sharedmem_size = rp.Pop<u32>();
// Update the node information with the data the game gave us.
- rp.PopRaw(node_info[0]);
+ rp.PopRaw(current_node);
u16 version = rp.Pop<u16>();
@@ -184,15 +484,22 @@ static void InitializeWithVersion(Interface* self) {
ASSERT_MSG(recv_buffer_memory->size == sharedmem_size, "Invalid shared memory size.");
- // Reset the connection status, it contains all zeros after initialization,
- // except for the actual status value.
- connection_status = {};
- connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+
+ // Reset the connection status, it contains all zeros after initialization,
+ // except for the actual status value.
+ connection_status = {};
+ connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
+ }
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap());
+ // TODO(Subv): Connect the OnWifiPacketReceived function to the wifi packet received callback of
+ // the room we're currently in.
+
LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X",
sharedmem_size, version, sharedmem_handle);
}
@@ -214,12 +521,16 @@ static void GetConnectionStatus(Interface* self) {
IPC::RequestBuilder rb = rp.MakeBuilder(13, 0);
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;
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ 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.
+ // TODO(Subv): Find exactly where the NWM module resets this value.
+ connection_status.changed_nodes = 0;
+ }
LOG_DEBUG(Service_NWM, "called");
}
@@ -300,31 +611,36 @@ static void BeginHostingNetwork(Interface* self) {
// The real UDS module throws a fatal error if this assert fails.
ASSERT_MSG(network_info.max_nodes > 1, "Trying to host a network of only one member.");
- connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost);
-
- // Ensure the application data size is less than the maximum value.
- ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big.");
-
- // Set up basic information for this network.
- network_info.oui_value = NintendoOUI;
- network_info.oui_type = static_cast<u8>(NintendoTagId::NetworkInfo);
-
- connection_status.max_nodes = network_info.max_nodes;
-
- // Resize the nodes list to hold max_nodes.
- node_info.resize(network_info.max_nodes);
-
- // There's currently only one node in the network (the host).
- connection_status.total_nodes = 1;
- network_info.total_nodes = 1;
- // 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;
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ connection_status.status = static_cast<u32>(NetworkStatus::ConnectedAsHost);
+
+ // Ensure the application data size is less than the maximum value.
+ ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize,
+ "Data size is too big.");
+
+ // Set up basic information for this network.
+ network_info.oui_value = NintendoOUI;
+ network_info.oui_type = static_cast<u8>(NintendoTagId::NetworkInfo);
+
+ connection_status.max_nodes = network_info.max_nodes;
+
+ // Resize the nodes list to hold max_nodes.
+ node_info.resize(network_info.max_nodes);
+
+ // There's currently only one node in the network (the host).
+ connection_status.total_nodes = 1;
+ network_info.total_nodes = 1;
+ // The host is always the first node
+ connection_status.network_node_id = 1;
+ current_node.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;
+ node_info[0] = current_node;
+ }
// If the game has a preferred channel, use that instead.
if (network_info.channel != 0)
@@ -361,9 +677,13 @@ 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);
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+
+ // 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();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
@@ -406,17 +726,24 @@ static void SendTo(Interface* self) {
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;
- }
+ u16 network_node_id;
- if (dest_node_id == connection_status.network_node_id) {
- rb.Push(ResultCode(ErrorDescription::NotFound, ErrorModule::UDS,
- ErrorSummary::WrongArgument, ErrorLevel::Status));
- return;
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ 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;
+ }
+
+ network_node_id = connection_status.network_node_id;
}
// TODO(Subv): Do something with the flags.
@@ -433,8 +760,8 @@ static void SendTo(Interface* self) {
// 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);
+ std::vector<u8> data_payload =
+ GenerateDataPayload(data, data_channel, dest_node_id, 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.
@@ -461,6 +788,7 @@ static void GetChannel(Interface* self) {
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x1A, 0, 0);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
bool is_connected = connection_status.status != static_cast<u32>(NetworkStatus::NotConnected);
u8 channel = is_connected ? network_channel : 0;
@@ -610,37 +938,29 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {
if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost))
return;
- // TODO(Subv): Actually send the beacon.
std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info);
+ using Network::WifiPacket;
+ WifiPacket packet;
+ packet.type = WifiPacket::PacketType::Beacon;
+ packet.data = std::move(frame);
+ packet.destination_address = Network::BroadcastMac;
+ packet.channel = network_channel;
+
+ SendPacket(packet);
+
// Start broadcasting the network, send a beacon frame every 102.4ms.
CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - 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) {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
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,
@@ -655,7 +975,7 @@ void OnClientConnected(u16 network_node_id) {
}
const Interface::FunctionInfo FunctionTable[] = {
- {0x00010442, nullptr, "Initialize (deprecated)"},
+ {0x000102C2, nullptr, "Initialize (deprecated)"},
{0x00020000, nullptr, "Scrap"},
{0x00030000, Shutdown, "Shutdown"},
{0x00040402, nullptr, "CreateNetwork (deprecated)"},
@@ -702,8 +1022,11 @@ NWM_UDS::~NWM_UDS() {
connection_status_event = nullptr;
recv_buffer_memory = nullptr;
- connection_status = {};
- connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
+ {
+ std::lock_guard<std::mutex> lock(connection_status_mutex);
+ connection_status = {};
+ connection_status.status = static_cast<u32>(NetworkStatus::NotConnected);
+ }
CoreTiming::UnscheduleEvent(beacon_broadcast_event, 0);
}
diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h
index 141f49f9c..f1caaf974 100644
--- a/src/core/hle/service/nwm/nwm_uds.h
+++ b/src/core/hle/service/nwm/nwm_uds.h
@@ -42,6 +42,7 @@ using NodeList = std::vector<NodeInfo>;
enum class NetworkStatus {
NotConnected = 3,
ConnectedAsHost = 6,
+ Connecting = 7,
ConnectedAsClient = 9,
ConnectedAsSpectator = 10,
};
@@ -85,6 +86,17 @@ static_assert(offsetof(NetworkInfo, oui_value) == 0xC, "oui_value is at the wron
static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset.");
static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size.");
+/// Additional block tag ids in the Beacon and Association Response frames
+enum class TagId : u8 {
+ SSID = 0,
+ SupportedRates = 1,
+ DSParameterSet = 2,
+ TrafficIndicationMap = 5,
+ CountryInformation = 7,
+ ERPInformation = 42,
+ VendorSpecific = 221
+};
+
class NWM_UDS final : public Interface {
public:
NWM_UDS();
diff --git a/src/core/hle/service/nwm/uds_beacon.cpp b/src/core/hle/service/nwm/uds_beacon.cpp
index 6332b404c..73a80d940 100644
--- a/src/core/hle/service/nwm/uds_beacon.cpp
+++ b/src/core/hle/service/nwm/uds_beacon.cpp
@@ -243,7 +243,7 @@ std::vector<u8> GenerateNintendoFirstEncryptedDataTag(const NetworkInfo& network
EncryptedDataTag tag{};
tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific);
- tag.header.length = sizeof(tag) - sizeof(TagHeader) + payload_size;
+ tag.header.length = static_cast<u8>(sizeof(tag) - sizeof(TagHeader) + payload_size);
tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData0);
tag.oui = NintendoOUI;
@@ -279,7 +279,7 @@ std::vector<u8> GenerateNintendoSecondEncryptedDataTag(const NetworkInfo& networ
EncryptedDataTag tag{};
tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific);
- tag.header.length = tag_length;
+ tag.header.length = static_cast<u8>(tag_length);
tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData1);
tag.oui = NintendoOUI;
@@ -325,8 +325,5 @@ std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeL
return buffer;
}
-std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender) {
- return {};
-}
} // namespace NWM
} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h
index caacf4c6f..50cc76da2 100644
--- a/src/core/hle/service/nwm/uds_beacon.h
+++ b/src/core/hle/service/nwm/uds_beacon.h
@@ -17,17 +17,6 @@ namespace NWM {
using MacAddress = std::array<u8, 6>;
constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32};
-/// Additional block tag ids in the Beacon frames
-enum class TagId : u8 {
- SSID = 0,
- SupportedRates = 1,
- DSParameterSet = 2,
- TrafficIndicationMap = 5,
- CountryInformation = 7,
- ERPInformation = 42,
- VendorSpecific = 221
-};
-
/**
* Internal vendor-specific tag ids as stored inside
* VendorSpecific blocks in the Beacon frames.
@@ -135,20 +124,6 @@ struct BeaconData {
static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size.");
-/// Information about a received WiFi packet.
-/// Acts as our own 802.11 header.
-struct WifiPacket {
- enum class PacketType { Beacon, Data };
-
- PacketType type; ///< The type of 802.11 frame, Beacon / Data.
-
- /// Raw 802.11 frame data, starting at the management frame header for management frames.
- std::vector<u8> data;
- 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.
-};
-
/**
* Decrypts the beacon data buffer for the network described by `network_info`.
*/
@@ -161,10 +136,5 @@ void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer)
*/
std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes);
-/**
- * Returns a list of received 802.11 frames from the specified sender
- * matching the type since the last call.
- */
-std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender);
} // namespace NWM
} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_connection.cpp b/src/core/hle/service/nwm/uds_connection.cpp
new file mode 100644
index 000000000..c74f51253
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_connection.cpp
@@ -0,0 +1,88 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/nwm/nwm_uds.h"
+#include "core/hle/service/nwm/uds_connection.h"
+#include "fmt/format.h"
+
+namespace Service {
+namespace NWM {
+
+// Note: These values were taken from a packet capture of an o3DS XL
+// broadcasting a Super Smash Bros. 4 lobby.
+constexpr u16 DefaultExtraCapabilities = 0x0431;
+
+std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq) {
+ AuthenticationFrame frame{};
+ frame.auth_seq = static_cast<u16>(seq);
+
+ std::vector<u8> data(sizeof(frame));
+ std::memcpy(data.data(), &frame, sizeof(frame));
+
+ return data;
+}
+
+AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body) {
+ AuthenticationFrame frame;
+ std::memcpy(&frame, body.data(), sizeof(frame));
+
+ return static_cast<AuthenticationSeq>(frame.auth_seq);
+}
+
+/**
+ * Generates an SSID tag of an 802.11 Beacon frame with an 8-byte character representation of the
+ * specified network id as the SSID value.
+ * @param network_id The network id to use.
+ * @returns A buffer with the SSID tag.
+ */
+static std::vector<u8> GenerateSSIDTag(u32 network_id) {
+ constexpr u8 SSIDSize = 8;
+
+ struct {
+ u8 id = static_cast<u8>(TagId::SSID);
+ u8 size = SSIDSize;
+ } tag_header;
+
+ std::vector<u8> buffer(sizeof(tag_header) + SSIDSize);
+
+ std::memcpy(buffer.data(), &tag_header, sizeof(tag_header));
+
+ std::string network_name = fmt::format("{0:08X}", network_id);
+
+ std::memcpy(buffer.data() + sizeof(tag_header), network_name.c_str(), SSIDSize);
+
+ return buffer;
+}
+
+std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id) {
+ AssociationResponseFrame frame{};
+ frame.capabilities = DefaultExtraCapabilities;
+ frame.status_code = static_cast<u16>(status);
+ // The association id is ORed with this magic value (0xC000)
+ constexpr u16 AssociationIdMagic = 0xC000;
+ frame.assoc_id = association_id | AssociationIdMagic;
+
+ std::vector<u8> data(sizeof(frame));
+ std::memcpy(data.data(), &frame, sizeof(frame));
+
+ auto ssid_tag = GenerateSSIDTag(network_id);
+ data.insert(data.end(), ssid_tag.begin(), ssid_tag.end());
+
+ // TODO(Subv): Add the SupportedRates tag.
+ // TODO(Subv): Add the DSParameterSet tag.
+ // TODO(Subv): Add the ERPInformation tag.
+ return data;
+}
+
+std::tuple<AssocStatus, u16> GetAssociationResult(const std::vector<u8>& body) {
+ AssociationResponseFrame frame;
+ memcpy(&frame, body.data(), sizeof(frame));
+
+ constexpr u16 AssociationIdMask = 0x3FFF;
+ return std::make_tuple(static_cast<AssocStatus>(frame.status_code),
+ frame.assoc_id & AssociationIdMask);
+}
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_connection.h b/src/core/hle/service/nwm/uds_connection.h
new file mode 100644
index 000000000..a664f8471
--- /dev/null
+++ b/src/core/hle/service/nwm/uds_connection.h
@@ -0,0 +1,56 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <tuple>
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NWM {
+
+/// Sequence number of the 802.11 authentication frames.
+enum class AuthenticationSeq : u16 { SEQ1 = 1, SEQ2 = 2 };
+
+enum class AuthAlgorithm : u16 { OpenSystem = 0 };
+
+enum class AuthStatus : u16 { Successful = 0 };
+
+enum class AssocStatus : u16 { Successful = 0 };
+
+struct AuthenticationFrame {
+ u16_le auth_algorithm = static_cast<u16>(AuthAlgorithm::OpenSystem);
+ u16_le auth_seq;
+ u16_le status_code = static_cast<u16>(AuthStatus::Successful);
+};
+
+static_assert(sizeof(AuthenticationFrame) == 6, "AuthenticationFrame has wrong size");
+
+struct AssociationResponseFrame {
+ u16_le capabilities;
+ u16_le status_code;
+ u16_le assoc_id;
+};
+
+static_assert(sizeof(AssociationResponseFrame) == 6, "AssociationResponseFrame has wrong size");
+
+/// Generates an 802.11 authentication frame, starting at the frame body.
+std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq);
+
+/// Returns the sequence number from the body of an Authentication frame.
+AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body);
+
+/// Generates an 802.11 association response frame with the specified status, association id and
+/// network id, starting at the frame body.
+std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id);
+
+/// Returns a tuple of (association status, association id) from the body of an AssociationResponse
+/// frame.
+std::tuple<AssocStatus, u16> GetAssociationResult(const std::vector<u8>& body);
+
+} // namespace NWM
+} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_data.cpp b/src/core/hle/service/nwm/uds_data.cpp
index 8c6742dba..4b389710f 100644
--- a/src/core/hle/service/nwm/uds_data.cpp
+++ b/src/core/hle/service/nwm/uds_data.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <cstring>
#include <cryptopp/aes.h>
#include <cryptopp/ccm.h>
@@ -197,7 +198,7 @@ static std::vector<u8> DecryptDataFrame(const std::vector<u8>& encrypted_payload
df.ChannelMessageEnd(CryptoPP::DEFAULT_CHANNEL);
df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
- int size = df.MaxRetrievable();
+ size_t size = df.MaxRetrievable();
std::vector<u8> pdata(size);
df.Get(pdata.data(), size);
@@ -251,7 +252,7 @@ static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload,
df.SetRetrievalChannel(CryptoPP::DEFAULT_CHANNEL);
- int size = df.MaxRetrievable();
+ size_t size = df.MaxRetrievable();
std::vector<u8> cipher(size);
df.Get(cipher.data(), size);
@@ -266,13 +267,107 @@ static std::vector<u8> EncryptDataFrame(const std::vector<u8>& payload,
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);
+ std::vector<u8> securedata_header = GenerateSecureDataHeader(
+ static_cast<u16>(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;
}
+std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info) {
+ EAPoLStartPacket eapol_start{};
+ eapol_start.association_id = association_id;
+ eapol_start.node.friend_code_seed = node_info.friend_code_seed;
+
+ std::copy(node_info.username.begin(), node_info.username.end(),
+ eapol_start.node.username.begin());
+
+ // Note: The network_node_id and unknown bytes seem to be uninitialized in the NWM module.
+ // TODO(B3N30): The last 8 bytes seem to have a fixed value of 07 88 15 00 04 e9 13 00 in
+ // EAPoL-Start packets from different 3DSs to the same host during a Super Smash Bros. 4 game.
+ // Find out what that means.
+
+ std::vector<u8> eapol_buffer(sizeof(EAPoLStartPacket));
+ std::memcpy(eapol_buffer.data(), &eapol_start, sizeof(eapol_start));
+
+ std::vector<u8> buffer = GenerateLLCHeader(EtherType::EAPoL);
+ buffer.insert(buffer.end(), eapol_buffer.begin(), eapol_buffer.end());
+ return buffer;
+}
+
+EtherType GetFrameEtherType(const std::vector<u8>& frame) {
+ LLCHeader header;
+ std::memcpy(&header, frame.data(), sizeof(header));
+
+ u16 ethertype = header.protocol;
+ return static_cast<EtherType>(ethertype);
+}
+
+u16 GetEAPoLFrameType(const std::vector<u8>& frame) {
+ // Ignore the LLC header
+ u16_be eapol_type;
+ std::memcpy(&eapol_type, frame.data() + sizeof(LLCHeader), sizeof(eapol_type));
+ return eapol_type;
+}
+
+NodeInfo DeserializeNodeInfoFromFrame(const std::vector<u8>& frame) {
+ EAPoLStartPacket eapol_start;
+
+ // Skip the LLC header
+ std::memcpy(&eapol_start, frame.data() + sizeof(LLCHeader), sizeof(eapol_start));
+
+ NodeInfo node{};
+ node.friend_code_seed = eapol_start.node.friend_code_seed;
+
+ std::copy(eapol_start.node.username.begin(), eapol_start.node.username.end(),
+ node.username.begin());
+
+ return node;
+}
+
+NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node) {
+ NodeInfo node_info{};
+ node_info.friend_code_seed = node.friend_code_seed;
+ node_info.network_node_id = node.network_node_id;
+
+ std::copy(node.username.begin(), node.username.end(), node_info.username.begin());
+
+ return node_info;
+}
+
+std::vector<u8> GenerateEAPoLLogoffFrame(const MacAddress& mac_address, u16 network_node_id,
+ const NodeList& nodes, u8 max_nodes, u8 total_nodes) {
+ EAPoLLogoffPacket eapol_logoff{};
+ eapol_logoff.assigned_node_id = network_node_id;
+ eapol_logoff.connected_nodes = total_nodes;
+ eapol_logoff.max_nodes = max_nodes;
+
+ for (size_t index = 0; index < total_nodes; ++index) {
+ const auto& node_info = nodes[index];
+ auto& node = eapol_logoff.nodes[index];
+
+ node.friend_code_seed = node_info.friend_code_seed;
+ node.network_node_id = node_info.network_node_id;
+
+ std::copy(node_info.username.begin(), node_info.username.end(), node.username.begin());
+ }
+
+ std::vector<u8> eapol_buffer(sizeof(EAPoLLogoffPacket));
+ std::memcpy(eapol_buffer.data(), &eapol_logoff, sizeof(eapol_logoff));
+
+ std::vector<u8> buffer = GenerateLLCHeader(EtherType::EAPoL);
+ buffer.insert(buffer.end(), eapol_buffer.begin(), eapol_buffer.end());
+ return buffer;
+}
+
+EAPoLLogoffPacket ParseEAPoLLogoffFrame(const std::vector<u8>& frame) {
+ EAPoLLogoffPacket eapol_logoff;
+
+ // Skip the LLC header
+ std::memcpy(&eapol_logoff, frame.data() + sizeof(LLCHeader), sizeof(eapol_logoff));
+ return eapol_logoff;
+}
+
} // namespace NWM
} // namespace Service
diff --git a/src/core/hle/service/nwm/uds_data.h b/src/core/hle/service/nwm/uds_data.h
index a23520a41..76bccb1bf 100644
--- a/src/core/hle/service/nwm/uds_data.h
+++ b/src/core/hle/service/nwm/uds_data.h
@@ -8,6 +8,7 @@
#include <vector>
#include "common/common_types.h"
#include "common/swap.h"
+#include "core/hle/service/nwm/uds_beacon.h"
#include "core/hle/service/service.h"
namespace Service {
@@ -67,6 +68,49 @@ struct DataFrameCryptoCTR {
static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wrong size");
+struct EAPoLNodeInfo {
+ u64_be friend_code_seed;
+ std::array<u16_be, 10> username;
+ INSERT_PADDING_BYTES(4);
+ u16_be network_node_id;
+ INSERT_PADDING_BYTES(6);
+};
+
+static_assert(sizeof(EAPoLNodeInfo) == 0x28, "EAPoLNodeInfo has the wrong size");
+
+constexpr u16 EAPoLStartMagic = 0x201;
+
+/*
+ * Nintendo EAPoLStartPacket, is used to initaliaze a connection between client and host
+ */
+struct EAPoLStartPacket {
+ u16_be magic = EAPoLStartMagic;
+ u16_be association_id;
+ // This value is hardcoded to 1 in the NWM module.
+ u16_be unknown = 1;
+ INSERT_PADDING_BYTES(2);
+ EAPoLNodeInfo node;
+};
+
+static_assert(sizeof(EAPoLStartPacket) == 0x30, "EAPoLStartPacket has the wrong size");
+
+constexpr u16 EAPoLLogoffMagic = 0x202;
+
+struct EAPoLLogoffPacket {
+ u16_be magic = EAPoLLogoffMagic;
+ INSERT_PADDING_BYTES(2);
+ u16_be assigned_node_id;
+ MacAddress client_mac_address;
+ INSERT_PADDING_BYTES(6);
+ u8 connected_nodes;
+ u8 max_nodes;
+ INSERT_PADDING_BYTES(4);
+
+ std::array<EAPoLNodeInfo, UDSMaxNodes> nodes;
+};
+
+static_assert(sizeof(EAPoLLogoffPacket) == 0x298, "EAPoLLogoffPacket has the wrong size");
+
/**
* Generates an unencrypted 802.11 data payload.
* @returns The generated frame payload.
@@ -74,5 +118,47 @@ static_assert(sizeof(DataFrameCryptoCTR) == 16, "DataFrameCryptoCTR has the wron
std::vector<u8> GenerateDataPayload(const std::vector<u8>& data, u8 channel, u16 dest_node,
u16 src_node, u16 sequence_number);
+/*
+ * Generates an unencrypted 802.11 data frame body with the EAPoL-Start format for UDS
+ * communication.
+ * @returns The generated frame body.
+ */
+std::vector<u8> GenerateEAPoLStartFrame(u16 association_id, const NodeInfo& node_info);
+
+/*
+ * Returns the EtherType of the specified 802.11 frame.
+ */
+EtherType GetFrameEtherType(const std::vector<u8>& frame);
+
+/*
+ * Returns the EAPoL type (Start / Logoff) of the specified 802.11 frame.
+ * Note: The frame *must* be an EAPoL frame.
+ */
+u16 GetEAPoLFrameType(const std::vector<u8>& frame);
+
+/*
+ * Returns a deserialized NodeInfo structure from the information inside an EAPoL-Start packet
+ * encapsulated in an 802.11 data frame.
+ */
+NodeInfo DeserializeNodeInfoFromFrame(const std::vector<u8>& frame);
+
+/*
+ * Returns a NodeInfo constructed from the data in the specified EAPoLNodeInfo.
+ */
+NodeInfo DeserializeNodeInfo(const EAPoLNodeInfo& node);
+
+/*
+ * Generates an unencrypted 802.11 data frame body with the EAPoL-Logoff format for UDS
+ * communication.
+ * @returns The generated frame body.
+ */
+std::vector<u8> GenerateEAPoLLogoffFrame(const MacAddress& mac_address, u16 network_node_id,
+ const NodeList& nodes, u8 max_nodes, u8 total_nodes);
+
+/*
+ * Returns a EAPoLLogoffPacket representing the specified 802.11-encapsulated data frame.
+ */
+EAPoLLogoffPacket ParseEAPoLLogoffFrame(const std::vector<u8>& frame);
+
} // namespace NWM
} // namespace Service
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index aad950e50..f267aad74 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -38,7 +38,7 @@
#include "core/hle/service/news/news.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nim/nim.h"
-#include "core/hle/service/ns_s.h"
+#include "core/hle/service/ns/ns.h"
#include "core/hle/service/nwm/nwm.h"
#include "core/hle/service/pm_app.h"
#include "core/hle/service/ptm/ptm.h"
@@ -215,6 +215,8 @@ void Init() {
SM::g_service_manager = std::make_shared<SM::ServiceManager>();
SM::ServiceManager::InstallInterfaces(SM::g_service_manager);
+ NS::InstallInterfaces(*SM::g_service_manager);
+
AddNamedPort(new ERR::ERR_F);
FS::ArchiveInit();
@@ -246,7 +248,6 @@ void Init() {
AddService(new HTTP::HTTP_C);
AddService(new LDR::LDR_RO);
AddService(new MIC::MIC_U);
- AddService(new NS::NS_S);
AddService(new PM::PM_APP);
AddService(new SOC::SOC_U);
AddService(new SSL::SSL_C);
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index 5e7fc68f9..854ab9a05 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -36,6 +36,10 @@ ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> ServiceManager::RegisterService
std::string name, unsigned int max_sessions) {
CASCADE_CODE(ValidateServiceName(name));
+
+ if (registered_services.find(name) != registered_services.end())
+ return ERR_ALREADY_REGISTERED;
+
Kernel::SharedPtr<Kernel::ServerPort> server_port;
Kernel::SharedPtr<Kernel::ClientPort> client_port;
std::tie(server_port, client_port) = Kernel::ServerPort::CreatePortPair(max_sessions, name);
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index 8f0dbf2db..9f60a7965 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -32,6 +32,9 @@ constexpr ResultCode ERR_ACCESS_DENIED(6, ErrorModule::SRV, ErrorSummary::Invali
ErrorLevel::Permanent); // 0xD8E06406
constexpr ResultCode ERR_NAME_CONTAINS_NUL(7, ErrorModule::SRV, ErrorSummary::WrongArgument,
ErrorLevel::Permanent); // 0xD9006407
+constexpr ResultCode ERR_ALREADY_REGISTERED(ErrorDescription::AlreadyExists, ErrorModule::OS,
+ ErrorSummary::WrongArgument,
+ ErrorLevel::Permanent); // 0xD9001BFC
class ServiceManager {
public:
diff --git a/src/core/hle/service/sm/srv.cpp b/src/core/hle/service/sm/srv.cpp
index 352941e69..fb873981c 100644
--- a/src/core/hle/service/sm/srv.cpp
+++ b/src/core/hle/service/sm/srv.cpp
@@ -13,6 +13,7 @@
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/semaphore.h"
+#include "core/hle/kernel/server_port.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/service/sm/sm.h"
#include "core/hle/service/sm/srv.h"
@@ -61,7 +62,7 @@ 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();
+ Kernel::Semaphore::Create(0, MAX_PENDING_NOTIFICATIONS, 0, "SRV:Notification").Unwrap();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
rb.Push(RESULT_SUCCESS);
@@ -184,12 +185,35 @@ void SRV::PublishToSubscriber(Kernel::HLERequestContext& ctx) {
flags);
}
+void SRV::RegisterService(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp(ctx, 0x3, 4, 0);
+
+ auto name_buf = rp.PopRaw<std::array<char, 8>>();
+ size_t name_len = rp.Pop<u32>();
+ u32 max_sessions = rp.Pop<u32>();
+
+ std::string name(name_buf.data(), std::min(name_len, name_buf.size()));
+
+ auto port = service_manager->RegisterService(name, max_sessions);
+
+ if (port.Failed()) {
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
+ rb.Push(port.Code());
+ LOG_ERROR(Service_SRV, "called service=%s -> error 0x%08X", name.c_str(), port.Code().raw);
+ return;
+ }
+
+ IPC::RequestBuilder rb = rp.MakeBuilder(1, 2);
+ rb.Push(RESULT_SUCCESS);
+ rb.PushObjects(port.Unwrap());
+}
+
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"},
+ {0x00030100, &SRV::RegisterService, "RegisterService"},
{0x000400C0, nullptr, "UnregisterService"},
{0x00050100, &SRV::GetServiceHandle, "GetServiceHandle"},
{0x000600C2, nullptr, "RegisterPort"},
diff --git a/src/core/hle/service/sm/srv.h b/src/core/hle/service/sm/srv.h
index 75cca5184..aad839563 100644
--- a/src/core/hle/service/sm/srv.h
+++ b/src/core/hle/service/sm/srv.h
@@ -28,6 +28,7 @@ private:
void Subscribe(Kernel::HLERequestContext& ctx);
void Unsubscribe(Kernel::HLERequestContext& ctx);
void PublishToSubscriber(Kernel::HLERequestContext& ctx);
+ void RegisterService(Kernel::HLERequestContext& ctx);
std::shared_ptr<ServiceManager> service_manager;
Kernel::SharedPtr<Kernel::Semaphore> notification_semaphore;