diff options
Diffstat (limited to 'src/core/hle')
26 files changed, 1037 insertions, 183 deletions
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp index 22645f4ec..ddcf4c916 100644 --- a/src/core/hle/kernel/client_port.cpp +++ b/src/core/hle/kernel/client_port.cpp @@ -19,24 +19,21 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() { // AcceptSession before returning from this call. if (active_sessions >= max_sessions) { - // TODO(Subv): Return an error code in this situation after session disconnection is - // implemented. - /*return ResultCode(ErrorDescription::MaxConnectionsReached, - ErrorModule::OS, ErrorSummary::WouldBlock, - ErrorLevel::Temporary);*/ + return ResultCode(ErrorDescription::MaxConnectionsReached, ErrorModule::OS, + ErrorSummary::WouldBlock, ErrorLevel::Temporary); } active_sessions++; // Create a new session pair, let the created sessions inherit the parent port's HLE handler. auto sessions = - ServerSession::CreateSessionPair(server_port->GetName(), server_port->hle_handler); + ServerSession::CreateSessionPair(server_port->GetName(), server_port->hle_handler, this); auto client_session = std::get<SharedPtr<ClientSession>>(sessions); auto server_session = std::get<SharedPtr<ServerSession>>(sessions); if (server_port->hle_handler) server_port->hle_handler->ClientConnected(server_session); - - server_port->pending_sessions.push_back(std::move(server_session)); + else + server_port->pending_sessions.push_back(std::move(server_session)); // Wake the threads waiting on the ServerPort server_port->WakeupAllWaitingThreads(); diff --git a/src/core/hle/kernel/client_session.cpp b/src/core/hle/kernel/client_session.cpp index 0331386ec..e297b7464 100644 --- a/src/core/hle/kernel/client_session.cpp +++ b/src/core/hle/kernel/client_session.cpp @@ -14,27 +14,24 @@ ClientSession::~ClientSession() { // This destructor will be called automatically when the last ClientSession handle is closed by // the emulated application. - if (server_session->hle_handler) - server_session->hle_handler->ClientDisconnected(server_session); + if (parent->server) { + if (parent->server->hle_handler) + parent->server->hle_handler->ClientDisconnected(parent->server); - // TODO(Subv): If the session is still open, set the connection status to 2 (Closed by client), - // wake up all the ServerSession's waiting threads and set the WaitSynchronization result to - // 0xC920181A. -} - -ResultVal<SharedPtr<ClientSession>> ClientSession::Create(ServerSession* server_session, - std::string name) { - SharedPtr<ClientSession> client_session(new ClientSession); + // TODO(Subv): Force a wake up of all the ServerSession's waiting threads and set + // their WaitSynchronization result to 0xC920181A. + } - client_session->name = std::move(name); - client_session->server_session = server_session; - client_session->session_status = SessionStatus::Open; - return MakeResult<SharedPtr<ClientSession>>(std::move(client_session)); + parent->client = nullptr; } ResultCode ClientSession::SendSyncRequest() { // Signal the server session that new data is available - return server_session->HandleSyncRequest(); + if (parent->server) + return parent->server->HandleSyncRequest(); + + return ResultCode(ErrorDescription::SessionClosedByRemote, ErrorModule::OS, + ErrorSummary::Canceled, ErrorLevel::Status); } } // namespace diff --git a/src/core/hle/kernel/client_session.h b/src/core/hle/kernel/client_session.h index ed468dec6..9f3adb72b 100644 --- a/src/core/hle/kernel/client_session.h +++ b/src/core/hle/kernel/client_session.h @@ -14,12 +14,7 @@ namespace Kernel { class ServerSession; - -enum class SessionStatus { - Open = 1, - ClosedByClient = 2, - ClosedBYServer = 3, -}; +class Session; class ClientSession final : public Object { public: @@ -44,22 +39,14 @@ public: */ ResultCode SendSyncRequest(); - std::string name; ///< Name of client port (optional) - ServerSession* server_session; ///< The server session associated with this client session. - SessionStatus session_status; ///< The session's current status. + std::string name; ///< Name of client port (optional) + + /// The parent session, which links to the server endpoint. + std::shared_ptr<Session> parent; private: ClientSession(); ~ClientSession() override; - - /** - * Creates a client session. - * @param server_session The server session associated with this client session - * @param name Optional name of client session - * @return The created client session - */ - static ResultVal<SharedPtr<ClientSession>> Create(ServerSession* server_session, - std::string name = "Unknown"); }; } // namespace diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp index 33c165197..8250a90b5 100644 --- a/src/core/hle/kernel/memory.cpp +++ b/src/core/hle/kernel/memory.cpp @@ -2,11 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <cinttypes> #include <map> #include <memory> #include <utility> #include <vector> #include "audio_core/audio_core.h" +#include "common/assert.h" #include "common/common_types.h" #include "common/logging/log.h" #include "core/hle/config_mem.h" @@ -92,52 +94,96 @@ MemoryRegionInfo* GetMemoryRegion(MemoryRegion region) { UNREACHABLE(); } } -} - -namespace Memory { -namespace { +std::array<u8, Memory::VRAM_SIZE> vram; +std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram; + +void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping) { + using namespace Memory; + + struct MemoryArea { + VAddr vaddr_base; + PAddr paddr_base; + u32 size; + }; + + // The order of entries in this array is important. The VRAM and IO VAddr ranges overlap, and + // VRAM must be tried first. + static constexpr MemoryArea memory_areas[] = { + {VRAM_VADDR, VRAM_PADDR, VRAM_SIZE}, + {IO_AREA_VADDR, IO_AREA_PADDR, IO_AREA_SIZE}, + {DSP_RAM_VADDR, DSP_RAM_PADDR, DSP_RAM_SIZE}, + {N3DS_EXTRA_RAM_VADDR, N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE - 0x20000}, + }; + + VAddr mapping_limit = mapping.address + mapping.size; + if (mapping_limit < mapping.address) { + LOG_CRITICAL(Loader, "Mapping size overflowed: address=0x%08" PRIX32 " size=0x%" PRIX32, + mapping.address, mapping.size); + return; + } -struct MemoryArea { - u32 base; - u32 size; - const char* name; -}; + auto area = + std::find_if(std::begin(memory_areas), std::end(memory_areas), [&](const auto& area) { + return mapping.address >= area.vaddr_base && + mapping_limit <= area.vaddr_base + area.size; + }); + if (area == std::end(memory_areas)) { + LOG_ERROR(Loader, "Unhandled special mapping: address=0x%08" PRIX32 " size=0x%" PRIX32 + " read_only=%d unk_flag=%d", + mapping.address, mapping.size, mapping.read_only, mapping.unk_flag); + return; + } -// We don't declare the IO regions in here since its handled by other means. -static MemoryArea memory_areas[] = { - {VRAM_VADDR, VRAM_SIZE, "VRAM"}, // Video memory (VRAM) -}; -} + u32 offset_into_region = mapping.address - area->vaddr_base; + if (area->paddr_base == IO_AREA_PADDR) { + LOG_ERROR(Loader, "MMIO mappings are not supported yet. phys_addr=0x%08" PRIX32, + area->paddr_base + offset_into_region); + return; + } -void Init() { - InitMemoryMap(); - LOG_DEBUG(HW_Memory, "initialized OK"); -} + // TODO(yuriks): Use GetPhysicalPointer when that becomes independent of the virtual + // mappings. + u8* target_pointer = nullptr; + switch (area->paddr_base) { + case VRAM_PADDR: + target_pointer = vram.data(); + break; + case DSP_RAM_PADDR: + target_pointer = AudioCore::GetDspMemory().data(); + break; + case N3DS_EXTRA_RAM_PADDR: + target_pointer = n3ds_extra_ram.data(); + break; + default: + UNREACHABLE(); + } -void InitLegacyAddressSpace(Kernel::VMManager& address_space) { - using namespace Kernel; + // TODO(yuriks): This flag seems to have some other effect, but it's unknown what + MemoryState memory_state = mapping.unk_flag ? MemoryState::Static : MemoryState::IO; - for (MemoryArea& area : memory_areas) { - auto block = std::make_shared<std::vector<u8>>(area.size); - address_space - .MapMemoryBlock(area.base, std::move(block), 0, area.size, MemoryState::Private) - .Unwrap(); - } + auto vma = address_space + .MapBackingMemory(mapping.address, target_pointer + offset_into_region, + mapping.size, memory_state) + .MoveFrom(); + address_space.Reprotect(vma, + mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite); +} +void MapSharedPages(VMManager& address_space) { auto cfg_mem_vma = address_space - .MapBackingMemory(CONFIG_MEMORY_VADDR, (u8*)&ConfigMem::config_mem, - CONFIG_MEMORY_SIZE, MemoryState::Shared) + .MapBackingMemory(Memory::CONFIG_MEMORY_VADDR, + reinterpret_cast<u8*>(&ConfigMem::config_mem), + Memory::CONFIG_MEMORY_SIZE, MemoryState::Shared) .MoveFrom(); address_space.Reprotect(cfg_mem_vma, VMAPermission::Read); auto shared_page_vma = address_space - .MapBackingMemory(SHARED_PAGE_VADDR, (u8*)&SharedPage::shared_page, - SHARED_PAGE_SIZE, MemoryState::Shared) + .MapBackingMemory(Memory::SHARED_PAGE_VADDR, + reinterpret_cast<u8*>(&SharedPage::shared_page), + Memory::SHARED_PAGE_SIZE, MemoryState::Shared) .MoveFrom(); address_space.Reprotect(shared_page_vma, VMAPermission::Read); - - AudioCore::AddAddressSpace(address_space); } -} // namespace +} // namespace Kernel diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h index 4e1856a41..08c1a9989 100644 --- a/src/core/hle/kernel/memory.h +++ b/src/core/hle/kernel/memory.h @@ -23,11 +23,7 @@ struct MemoryRegionInfo { void MemoryInit(u32 mem_type); void MemoryShutdown(); MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); -} -namespace Memory { - -void Init(); -void InitLegacyAddressSpace(Kernel::VMManager& address_space); - -} // namespace +void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); +void MapSharedPages(VMManager& address_space); +} // namespace Kernel diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp index ba80fe7f8..32cb25fb7 100644 --- a/src/core/hle/kernel/process.cpp +++ b/src/core/hle/kernel/process.cpp @@ -35,7 +35,6 @@ SharedPtr<Process> Process::Create(SharedPtr<CodeSet> code_set) { process->codeset = std::move(code_set); process->flags.raw = 0; process->flags.memory_region.Assign(MemoryRegion::APPLICATION); - Memory::InitLegacyAddressSpace(process->vm_manager); return process; } @@ -78,8 +77,15 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { AddressMapping mapping; mapping.address = descriptor << 12; - mapping.size = (end_desc << 12) - mapping.address; - mapping.writable = (descriptor & (1 << 20)) != 0; + VAddr end_address = end_desc << 12; + + if (mapping.address < end_address) { + mapping.size = end_address - mapping.address; + } else { + mapping.size = 0; + } + + mapping.read_only = (descriptor & (1 << 20)) != 0; mapping.unk_flag = (end_desc & (1 << 20)) != 0; address_mappings.push_back(mapping); @@ -88,8 +94,10 @@ void Process::ParseKernelCaps(const u32* kernel_caps, size_t len) { AddressMapping mapping; mapping.address = descriptor << 12; mapping.size = Memory::PAGE_SIZE; - mapping.writable = true; // TODO: Not sure if correct + mapping.read_only = false; mapping.unk_flag = false; + + address_mappings.push_back(mapping); } else if ((type & 0xFE0) == 0xFC0) { // 0x01FF // Kernel version kernel_version = descriptor & 0xFFFF; @@ -131,6 +139,12 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { misc_memory_used += stack_size; memory_region->used += stack_size; + // Map special address mappings + MapSharedPages(vm_manager); + for (const auto& mapping : address_mappings) { + HandleSpecialMapping(vm_manager, mapping); + } + vm_manager.LogLayout(Log::Level::Debug); Kernel::SetupMainThread(codeset->entrypoint, main_thread_priority); } @@ -138,6 +152,7 @@ void Process::Run(s32 main_thread_priority, u32 stack_size) { VAddr Process::GetLinearHeapAreaAddress() const { return kernel_version < 0x22C ? Memory::LINEAR_HEAP_VADDR : Memory::NEW_LINEAR_HEAP_VADDR; } + VAddr Process::GetLinearHeapBase() const { return GetLinearHeapAreaAddress() + memory_region->base; } diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index b566950b0..b52211d2a 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -20,7 +20,7 @@ struct AddressMapping { // Address and size must be page-aligned VAddr address; u32 size; - bool writable; + bool read_only; bool unk_flag; }; diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 9447ff236..500b909ab 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -14,8 +14,15 @@ ServerSession::ServerSession() = default; ServerSession::~ServerSession() { // This destructor will be called automatically when the last ServerSession handle is closed by // the emulated application. - // TODO(Subv): Reduce the ClientPort's connection count, - // if the session is still open, set the connection status to 3 (Closed by server), + + // Decrease the port's connection count. + if (parent->port) + parent->port->active_sessions--; + + // TODO(Subv): Wake up all the ClientSession's waiting threads and set + // the SendSyncRequest result to 0xC920181A. + + parent->server = nullptr; } ResultVal<SharedPtr<ServerSession>> ServerSession::Create( @@ -25,6 +32,7 @@ ResultVal<SharedPtr<ServerSession>> ServerSession::Create( server_session->name = std::move(name); server_session->signaled = false; server_session->hle_handler = std::move(hle_handler); + server_session->parent = nullptr; return MakeResult<SharedPtr<ServerSession>>(std::move(server_session)); } @@ -61,13 +69,22 @@ ResultCode ServerSession::HandleSyncRequest() { } ServerSession::SessionPair ServerSession::CreateSessionPair( - const std::string& name, std::shared_ptr<Service::SessionRequestHandler> hle_handler) { + const std::string& name, std::shared_ptr<Service::SessionRequestHandler> hle_handler, + SharedPtr<ClientPort> port) { + auto server_session = ServerSession::Create(name + "_Server", std::move(hle_handler)).MoveFrom(); - // We keep a non-owning pointer to the ServerSession in the ClientSession because we don't want - // to prevent the ServerSession's destructor from being called when the emulated - // application closes the last ServerSession handle. - auto client_session = ClientSession::Create(server_session.get(), name + "_Client").MoveFrom(); + + SharedPtr<ClientSession> client_session(new ClientSession); + client_session->name = name + "_Client"; + + std::shared_ptr<Session> parent(new Session); + parent->client = client_session.get(); + parent->server = server_session.get(); + parent->port = port; + + client_session->parent = parent; + server_session->parent = parent; return std::make_tuple(std::move(server_session), std::move(client_session)); } diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h index 761fc4781..c907d487c 100644 --- a/src/core/hle/kernel/server_session.h +++ b/src/core/hle/kernel/server_session.h @@ -9,6 +9,7 @@ #include "common/assert.h" #include "common/common_types.h" #include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/session.h" #include "core/hle/kernel/thread.h" #include "core/hle/result.h" #include "core/hle/service/service.h" @@ -17,6 +18,8 @@ namespace Kernel { class ClientSession; +class ClientPort; +class ServerSession; /** * Kernel object representing the server endpoint of an IPC session. Sessions are the basic CTR-OS @@ -47,11 +50,13 @@ public: * Creates a pair of ServerSession and an associated ClientSession. * @param name Optional name of the ports. * @param hle_handler Optional HLE handler for this server session. + * @param client_port Optional The ClientPort that spawned this session. * @return The created session tuple */ static SessionPair CreateSessionPair( const std::string& name = "Unknown", - std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr); + std::shared_ptr<Service::SessionRequestHandler> hle_handler = nullptr, + SharedPtr<ClientPort> client_port = nullptr); /** * Handle a sync request from the emulated application. @@ -63,8 +68,9 @@ public: void Acquire(Thread* thread) override; - std::string name; ///< The name of this session (optional) - bool signaled; ///< Whether there's new data available to this ServerSession + std::string name; ///< The name of this session (optional) + bool signaled; ///< Whether there's new data available to this ServerSession + std::shared_ptr<Session> parent; ///< The parent session, which links to the client endpoint. std::shared_ptr<Service::SessionRequestHandler> hle_handler; ///< This session's HLE request handler (optional) diff --git a/src/core/hle/kernel/session.h b/src/core/hle/kernel/session.h new file mode 100644 index 000000000..a45e78022 --- /dev/null +++ b/src/core/hle/kernel/session.h @@ -0,0 +1,27 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/kernel/kernel.h" + +namespace Kernel { + +class ClientSession; +class ClientPort; +class ServerSession; + +/** + * Parent structure to link the client and server endpoints of a session with their associated + * client port. The client port need not exist, as is the case for portless sessions like the + * FS File and Directory sessions. When one of the endpoints of a session is destroyed, its + * corresponding field in this structure will be set to nullptr. + */ +class Session final { +public: + ClientSession* client = nullptr; ///< The client endpoint of the session. + ServerSession* server = nullptr; ///< The server endpoint of the session. + SharedPtr<ClientPort> port; ///< The port that this session is associated with (optional). +}; +} diff --git a/src/core/hle/result.h b/src/core/hle/result.h index cfefbbc64..13b948871 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -16,6 +16,7 @@ /// Detailed description of the error. This listing is likely incomplete. enum class ErrorDescription : u32 { Success = 0, + SessionClosedByRemote = 26, WrongPermission = 46, OS_InvalidBufferDescriptor = 48, MaxConnectionsReached = 52, diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index b19e831fe..64d01cdd7 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -53,30 +53,29 @@ static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton:: buttons; static std::unique_ptr<Input::AnalogDevice> circle_pad; -static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) { +DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { // 30 degree and 60 degree are angular thresholds for directions constexpr float TAN30 = 0.577350269f; constexpr float TAN60 = 1 / TAN30; // a circle pad radius greater than 40 will trigger circle pad direction constexpr int CIRCLE_PAD_THRESHOLD_SQUARE = 40 * 40; - PadState state; - state.hex = 0; + DirectionState state{false, false, false, false}; if (circle_pad_x * circle_pad_x + circle_pad_y * circle_pad_y > CIRCLE_PAD_THRESHOLD_SQUARE) { float t = std::abs(static_cast<float>(circle_pad_y) / circle_pad_x); if (circle_pad_x != 0 && t < TAN60) { if (circle_pad_x > 0) - state.circle_right.Assign(1); + state.right = true; else - state.circle_left.Assign(1); + state.left = true; } if (circle_pad_x == 0 || t > TAN30) { if (circle_pad_y > 0) - state.circle_up.Assign(1); + state.up = true; else - state.circle_down.Assign(1); + state.down = true; } } @@ -125,7 +124,11 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) { constexpr int MAX_CIRCLEPAD_POS = 0x9C; // Max value for a circle pad position s16 circle_pad_x = static_cast<s16>(circle_pad_x_f * MAX_CIRCLEPAD_POS); s16 circle_pad_y = static_cast<s16>(circle_pad_y_f * MAX_CIRCLEPAD_POS); - state.hex |= GetCirclePadDirectionState(circle_pad_x, circle_pad_y).hex; + const DirectionState direction = GetStickDirectionState(circle_pad_x, circle_pad_y); + state.circle_up.Assign(direction.up); + state.circle_down.Assign(direction.down); + state.circle_left.Assign(direction.left); + state.circle_right.Assign(direction.right); mem->pad.current_state.hex = state.hex; mem->pad.index = next_pad_index; diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index b505cdcd5..1ef972e70 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h @@ -176,6 +176,16 @@ ASSERT_REG_POSITION(touch.index_reset_ticks, 0x2A); #undef ASSERT_REG_POSITION #endif // !defined(_MSC_VER) +struct DirectionState { + bool up; + bool down; + bool left; + bool right; +}; + +/// Translates analog stick axes to directions. This is exposed for ir_rst module to use. +DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y); + /** * HID::GetIPCHandles service function * Inputs: diff --git a/src/core/hle/service/ir/ir.cpp b/src/core/hle/service/ir/ir.cpp index 7ac34a990..f06dd552f 100644 --- a/src/core/hle/service/ir/ir.cpp +++ b/src/core/hle/service/ir/ir.cpp @@ -25,6 +25,11 @@ void Shutdown() { ShutdownRST(); } +void ReloadInputDevices() { + ReloadInputDevicesUser(); + ReloadInputDevicesRST(); +} + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir.h b/src/core/hle/service/ir/ir.h index c741498e2..6be3e950c 100644 --- a/src/core/hle/service/ir/ir.h +++ b/src/core/hle/service/ir/ir.h @@ -16,5 +16,8 @@ void Init(); /// Shutdown IR service void Shutdown(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevices(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_rst.cpp b/src/core/hle/service/ir/ir_rst.cpp index 3f1275c53..53807cd91 100644 --- a/src/core/hle/service/ir/ir_rst.cpp +++ b/src/core/hle/service/ir/ir_rst.cpp @@ -2,16 +2,135 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <atomic> +#include "common/bit_field.h" +#include "core/core_timing.h" +#include "core/frontend/input.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/shared_memory.h" +#include "core/hle/service/hid/hid.h" #include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir_rst.h" +#include "core/settings.h" namespace Service { namespace IR { -static Kernel::SharedPtr<Kernel::Event> handle_event; +union PadState { + u32_le hex; + + BitField<14, 1, u32_le> zl; + BitField<15, 1, u32_le> zr; + + BitField<24, 1, u32_le> c_stick_right; + BitField<25, 1, u32_le> c_stick_left; + BitField<26, 1, u32_le> c_stick_up; + BitField<27, 1, u32_le> c_stick_down; +}; + +struct PadDataEntry { + PadState current_state; + PadState delta_additions; + PadState delta_removals; + + s16_le c_stick_x; + s16_le c_stick_y; +}; + +struct SharedMem { + u64_le index_reset_ticks; ///< CPU tick count for when HID module updated entry index 0 + u64_le index_reset_ticks_previous; ///< Previous `index_reset_ticks` + u32_le index; + INSERT_PADDING_WORDS(1); + std::array<PadDataEntry, 8> entries; ///< Last 8 pad entries +}; + +static_assert(sizeof(SharedMem) == 0x98, "SharedMem has wrong size!"); + +static Kernel::SharedPtr<Kernel::Event> update_event; static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; +static u32 next_pad_index; +static int update_callback_id; +static std::unique_ptr<Input::ButtonDevice> zl_button; +static std::unique_ptr<Input::ButtonDevice> zr_button; +static std::unique_ptr<Input::AnalogDevice> c_stick; +static std::atomic<bool> is_device_reload_pending; +static bool raw_c_stick; +static int update_period; + +static void LoadInputDevices() { + zl_button = Input::CreateDevice<Input::ButtonDevice>( + Settings::values.buttons[Settings::NativeButton::ZL]); + zr_button = Input::CreateDevice<Input::ButtonDevice>( + Settings::values.buttons[Settings::NativeButton::ZR]); + c_stick = Input::CreateDevice<Input::AnalogDevice>( + Settings::values.analogs[Settings::NativeAnalog::CStick]); +} + +static void UnloadInputDevices() { + zl_button = nullptr; + zr_button = nullptr; + c_stick = nullptr; +} + +static void UpdateCallback(u64 userdata, int cycles_late) { + SharedMem* mem = reinterpret_cast<SharedMem*>(shared_memory->GetPointer()); + + if (is_device_reload_pending.exchange(false)) + LoadInputDevices(); + + PadState state; + state.zl.Assign(zl_button->GetStatus()); + state.zr.Assign(zr_button->GetStatus()); + + // Get current c-stick position and update c-stick direction + float c_stick_x_f, c_stick_y_f; + std::tie(c_stick_x_f, c_stick_y_f) = c_stick->GetStatus(); + constexpr int MAX_CSTICK_RADIUS = 0x9C; // Max value for a c-stick radius + const s16 c_stick_x = static_cast<s16>(c_stick_x_f * MAX_CSTICK_RADIUS); + const s16 c_stick_y = static_cast<s16>(c_stick_y_f * MAX_CSTICK_RADIUS); + + if (!raw_c_stick) { + const HID::DirectionState direction = HID::GetStickDirectionState(c_stick_x, c_stick_y); + state.c_stick_up.Assign(direction.up); + state.c_stick_down.Assign(direction.down); + state.c_stick_left.Assign(direction.left); + state.c_stick_right.Assign(direction.right); + } + + // TODO (wwylele): implement raw C-stick data for raw_c_stick = true + + const u32 last_entry_index = mem->index; + mem->index = next_pad_index; + next_pad_index = (next_pad_index + 1) % mem->entries.size(); + + // Get the previous Pad state + PadState old_state{mem->entries[last_entry_index].current_state}; + + // Compute bitmask with 1s for bits different from the old state + PadState changed = {state.hex ^ old_state.hex}; + + // Get the current Pad entry + PadDataEntry& pad_entry = mem->entries[mem->index]; + + // Update entry properties + pad_entry.current_state.hex = state.hex; + pad_entry.delta_additions.hex = changed.hex & state.hex; + pad_entry.delta_removals.hex = changed.hex & old_state.hex; + pad_entry.c_stick_x = c_stick_x; + pad_entry.c_stick_y = c_stick_y; + + // If we just updated index 0, provide a new timestamp + if (mem->index == 0) { + mem->index_reset_ticks_previous = mem->index_reset_ticks; + mem->index_reset_ticks = CoreTiming::GetTicks(); + } + + update_event->Signal(); + + // Reschedule recurrent event + CoreTiming::ScheduleEvent(msToCycles(update_period) - cycles_late, update_callback_id); +} /** * IR::GetHandles service function @@ -22,18 +141,52 @@ static Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; * 4 : Event handle */ static void GetHandles(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x01, 0, 0); + IPC::RequestBuilder rb = rp.MakeBuilder(1, 3); + rb.Push(RESULT_SUCCESS); + rb.PushMoveHandles(Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(), + Kernel::g_handle_table.Create(Service::IR::update_event).MoveFrom()); +} + +/** + * IR::Initialize service function + * Inputs: + * 1 : pad state update period in ms + * 2 : bool output raw c-stick data + */ +static void Initialize(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x02, 2, 0); + update_period = static_cast<int>(rp.Pop<u32>()); + raw_c_stick = rp.Pop<bool>(); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0x4000000; - cmd_buff[3] = Kernel::g_handle_table.Create(Service::IR::shared_memory).MoveFrom(); - cmd_buff[4] = Kernel::g_handle_table.Create(Service::IR::handle_event).MoveFrom(); + if (raw_c_stick) + LOG_ERROR(Service_IR, "raw C-stick data is not implemented!"); + + next_pad_index = 0; + is_device_reload_pending.store(true); + CoreTiming::ScheduleEvent(msToCycles(update_period), update_callback_id); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_IR, "called. update_period=%d, raw_c_stick=%d", update_period, raw_c_stick); +} + +static void Shutdown(Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x03, 1, 0); + + CoreTiming::UnscheduleEvent(update_callback_id, 0); + UnloadInputDevices(); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + LOG_DEBUG(Service_IR, "called"); } const Interface::FunctionInfo FunctionTable[] = { {0x00010000, GetHandles, "GetHandles"}, - {0x00020080, nullptr, "Initialize"}, - {0x00030000, nullptr, "Shutdown"}, + {0x00020080, Initialize, "Initialize"}, + {0x00030000, Shutdown, "Shutdown"}, {0x00090000, nullptr, "WriteToTwoFields"}, }; @@ -43,17 +196,24 @@ IR_RST_Interface::IR_RST_Interface() { void InitRST() { using namespace Kernel; - + // Note: these two kernel objects are even available before Initialize service function is + // called. shared_memory = - SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, - MemoryPermission::ReadWrite, 0, MemoryRegion::BASE, "IR:SharedMemory"); + SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, MemoryPermission::Read, + 0, MemoryRegion::BASE, "IRRST:SharedMemory"); + update_event = Event::Create(ResetType::OneShot, "IRRST:UpdateEvent"); - handle_event = Event::Create(ResetType::OneShot, "IR:HandleEvent"); + update_callback_id = CoreTiming::RegisterEvent("IRRST:UpdateCallBack", UpdateCallback); } void ShutdownRST() { shared_memory = nullptr; - handle_event = nullptr; + update_event = nullptr; + UnloadInputDevices(); +} + +void ReloadInputDevicesRST() { + is_device_reload_pending.store(true); } } // namespace IR diff --git a/src/core/hle/service/ir/ir_rst.h b/src/core/hle/service/ir/ir_rst.h index 75b732627..d932bb7e5 100644 --- a/src/core/hle/service/ir/ir_rst.h +++ b/src/core/hle/service/ir/ir_rst.h @@ -21,5 +21,8 @@ public: void InitRST(); void ShutdownRST(); +/// Reload input devices. Used when input configuration changed +void ReloadInputDevicesRST(); + } // namespace IR } // namespace Service diff --git a/src/core/hle/service/ir/ir_user.cpp b/src/core/hle/service/ir/ir_user.cpp index bccf6bce7..226af0083 100644 --- a/src/core/hle/service/ir/ir_user.cpp +++ b/src/core/hle/service/ir/ir_user.cpp @@ -542,7 +542,7 @@ void ShutdownUser() { receive_event = nullptr; } -void ReloadInputDevices() { +void ReloadInputDevicesUser() { if (extra_hid) extra_hid->RequestInputDevicesReload(); } diff --git a/src/core/hle/service/ir/ir_user.h b/src/core/hle/service/ir/ir_user.h index 2401346e8..930650406 100644 --- a/src/core/hle/service/ir/ir_user.h +++ b/src/core/hle/service/ir/ir_user.h @@ -52,7 +52,7 @@ void InitUser(); void ShutdownUser(); /// Reload input devices. Used when input configuration changed -void ReloadInputDevices(); +void ReloadInputDevicesUser(); } // namespace IR } // namespace Service diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index ef6c5ebe3..581816e81 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <array> #include <cstring> #include <unordered_map> #include <vector> @@ -12,6 +13,7 @@ #include "core/hle/kernel/shared_memory.h" #include "core/hle/result.h" #include "core/hle/service/nwm/nwm_uds.h" +#include "core/hle/service/nwm/uds_beacon.h" #include "core/memory.h" namespace Service { @@ -27,10 +29,12 @@ static Kernel::SharedPtr<Kernel::SharedMemory> recv_buffer_memory; // Connection status of this 3DS. static ConnectionStatus connection_status{}; -// Node information about the current 3DS. -// TODO(Subv): Keep an array of all nodes connected to the network, -// that data has to be retransmitted in every beacon frame. -static NodeInfo node_info; +/* 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. + */ +static NodeList node_info(1); // Mapping of bind node ids to their respective events. static std::unordered_map<u32, Kernel::SharedPtr<Kernel::Event>> bind_node_events; @@ -82,29 +86,70 @@ static void Shutdown(Interface* self) { * 1 : Result of function, 0 on success, otherwise error code */ static void RecvBeaconBroadcastData(Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); - u32 out_buffer_size = cmd_buff[1]; - u32 unk1 = cmd_buff[2]; - u32 unk2 = cmd_buff[3]; - u32 mac_address = cmd_buff[4]; + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x0F, 16, 4); - u32 unk3 = cmd_buff[6]; + u32 out_buffer_size = rp.Pop<u32>(); + u32 unk1 = rp.Pop<u32>(); + u32 unk2 = rp.Pop<u32>(); - u32 wlan_comm_id = cmd_buff[15]; - u32 ctr_gen_id = cmd_buff[16]; - u32 value = cmd_buff[17]; - u32 input_handle = cmd_buff[18]; - u32 new_buffer_size = cmd_buff[19]; - u32 out_buffer_ptr = cmd_buff[20]; + MacAddress mac_address; + rp.PopRaw(mac_address); - cmd_buff[1] = RESULT_SUCCESS.raw; + rp.Skip(9, false); - LOG_WARNING(Service_NWM, - "(STUBBED) called out_buffer_size=0x%08X, unk1=0x%08X, unk2=0x%08X," - "mac_address=0x%08X, unk3=0x%08X, wlan_comm_id=0x%08X, ctr_gen_id=0x%08X," - "value=%u, input_handle=0x%08X, new_buffer_size=0x%08X, out_buffer_ptr=0x%08X", - out_buffer_size, unk1, unk2, mac_address, unk3, wlan_comm_id, ctr_gen_id, value, - input_handle, new_buffer_size, out_buffer_ptr); + u32 wlan_comm_id = rp.Pop<u32>(); + u32 id = rp.Pop<u32>(); + Kernel::Handle input_handle = rp.PopHandle(); + + size_t desc_size; + const VAddr out_buffer_ptr = rp.PopMappedBuffer(&desc_size); + ASSERT(desc_size == out_buffer_size); + + VAddr current_buffer_pos = out_buffer_ptr; + 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); + + BeaconDataReplyHeader data_reply_header{}; + data_reply_header.total_entries = beacons.size(); + data_reply_header.max_output_size = out_buffer_size; + + Memory::WriteBlock(current_buffer_pos, &data_reply_header, sizeof(BeaconDataReplyHeader)); + current_buffer_pos += sizeof(BeaconDataReplyHeader); + + // Write each of the received beacons into the buffer + 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.wifi_channel = beacon.channel; + entry.header_size = sizeof(BeaconEntryHeader); + entry.mac_address = beacon.transmitter_address; + + ASSERT(current_buffer_pos < out_buffer_ptr + out_buffer_size); + + Memory::WriteBlock(current_buffer_pos, &entry, sizeof(BeaconEntryHeader)); + current_buffer_pos += sizeof(BeaconEntryHeader); + + Memory::WriteBlock(current_buffer_pos, beacon.data.data(), beacon.data.size()); + current_buffer_pos += beacon.data.size(); + + total_size += sizeof(BeaconEntryHeader) + beacon.data.size(); + } + + // Update the total size in the structure and write it to the buffer again. + data_reply_header.total_size = total_size; + Memory::WriteBlock(out_buffer_ptr, &data_reply_header, sizeof(BeaconDataReplyHeader)); + + IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_NWM, "called out_buffer_size=0x%08X, wlan_comm_id=0x%08X, id=0x%08X," + "input_handle=0x%08X, out_buffer_ptr=0x%08X, unk1=0x%08X, unk2=0x%08X", + out_buffer_size, wlan_comm_id, id, input_handle, out_buffer_ptr, unk1, unk2); } /** @@ -127,10 +172,10 @@ 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); + rp.PopRaw(node_info[0]); + + u16 version = rp.Pop<u16>(); - u16 version; - rp.PopRaw(version); Kernel::Handle sharedmem_handle = rp.PopHandle(); recv_buffer_memory = Kernel::g_handle_table.Get<Kernel::SharedMemory>(sharedmem_handle); @@ -191,10 +236,8 @@ static void Bind(Interface* self) { u32 bind_node_id = rp.Pop<u32>(); u32 recv_buffer_size = rp.Pop<u32>(); - u8 data_channel; - rp.PopRaw(data_channel); - u16 network_node_id; - rp.PopRaw(network_node_id); + u8 data_channel = rp.Pop<u8>(); + u16 network_node_id = rp.Pop<u16>(); // TODO(Subv): Store the data channel and verify it when receiving data frames. @@ -251,13 +294,25 @@ static void BeginHostingNetwork(Interface* self) { 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.network_node_id = 1; + node_info[0].network_node_id = 1; // Set the bit 0 in the nodes bitmask to indicate that node 1 is already taken. connection_status.node_bitmask |= 1; @@ -325,7 +380,7 @@ static void GetChannel(Interface* self) { u8 channel = is_connected ? network_channel : 0; rb.Push(RESULT_SUCCESS); - rb.PushRaw(channel); + rb.Push(channel); LOG_DEBUG(Service_NWM, "called"); } @@ -373,7 +428,8 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) { if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) return; - // TODO(Subv): Actually generate the beacon and send it. + // TODO(Subv): Actually send the beacon. + std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info); // Start broadcasting the network, send a beacon frame every 102.4ms. CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index 65349f9fd..29b146569 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h @@ -6,6 +6,7 @@ #include <array> #include <cstddef> +#include <vector> #include "common/common_types.h" #include "common/swap.h" #include "core/hle/service/service.h" @@ -33,6 +34,8 @@ struct NodeInfo { static_assert(sizeof(NodeInfo) == 40, "NodeInfo has incorrect size."); +using NodeList = std::vector<NodeInfo>; + enum class NetworkStatus { NotConnected = 3, ConnectedAsHost = 6, @@ -75,6 +78,8 @@ struct NetworkInfo { std::array<u8, ApplicationDataSize> application_data; }; +static_assert(offsetof(NetworkInfo, oui_value) == 0xC, "oui_value is at the wrong offset."); +static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset."); static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); class NWM_UDS final : public Interface { diff --git a/src/core/hle/service/nwm/uds_beacon.cpp b/src/core/hle/service/nwm/uds_beacon.cpp new file mode 100644 index 000000000..c6e5bc5f1 --- /dev/null +++ b/src/core/hle/service/nwm/uds_beacon.cpp @@ -0,0 +1,333 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> + +#include "core/hle/service/nwm/nwm_uds.h" +#include "core/hle/service/nwm/uds_beacon.h" + +#include <cryptopp/aes.h> +#include <cryptopp/md5.h> +#include <cryptopp/modes.h> +#include <cryptopp/sha.h> + +namespace Service { +namespace NWM { + +// 802.11 broadcast MAC address +constexpr MacAddress BroadcastMac = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +constexpr u64 DefaultNetworkUptime = 900000000; // 15 minutes in microseconds. + +// Note: These values were taken from a packet capture of an o3DS XL +// broadcasting a Super Smash Bros. 4 lobby. +constexpr u16 DefaultExtraCapabilities = 0x0431; + +// Size of the SSID broadcast by an UDS beacon frame. +constexpr u8 UDSBeaconSSIDSize = 8; + +// The maximum size of the data stored in the EncryptedData0 tag (24). +constexpr u32 EncryptedDataSizeCutoff = 0xFA; + +/** + * NWM Beacon data encryption key, taken from the NWM module code. + * We stub this with an all-zeros key as that is enough for Citra's purpose. + * The real key can be used here to generate beacons that will be accepted by + * a real 3ds. + */ +constexpr std::array<u8, CryptoPP::AES::BLOCKSIZE> nwm_beacon_key = {}; + +/** + * Generates a buffer with the fixed parameters of an 802.11 Beacon frame + * using dummy values. + * @returns A buffer with the fixed parameters of the beacon frame. + */ +std::vector<u8> GenerateFixedParameters() { + std::vector<u8> buffer(sizeof(BeaconFrameHeader)); + + BeaconFrameHeader header{}; + // Use a fixed default time for now. + // TODO(Subv): Perhaps use the difference between now and the time the network was started? + header.timestamp = DefaultNetworkUptime; + header.beacon_interval = DefaultBeaconInterval; + header.capabilities = DefaultExtraCapabilities; + + std::memcpy(buffer.data(), &header, sizeof(header)); + + return buffer; +} + +/** + * Generates an SSID tag of an 802.11 Beacon frame with an 8-byte all-zero SSID value. + * @returns A buffer with the SSID tag. + */ +std::vector<u8> GenerateSSIDTag() { + std::vector<u8> buffer(sizeof(TagHeader) + UDSBeaconSSIDSize); + + TagHeader tag_header{}; + tag_header.tag_id = static_cast<u8>(TagId::SSID); + tag_header.length = UDSBeaconSSIDSize; + + std::memcpy(buffer.data(), &tag_header, sizeof(TagHeader)); + + // The rest of the buffer is already filled with zeros. + + return buffer; +} + +/** + * Generates a buffer with the basic tagged parameters of an 802.11 Beacon frame + * such as SSID, Rate Information, Country Information, etc. + * @returns A buffer with the tagged parameters of the beacon frame. + */ +std::vector<u8> GenerateBasicTaggedParameters() { + // Append the SSID tag + std::vector<u8> buffer = GenerateSSIDTag(); + + // TODO(Subv): Add the SupportedRates tag. + // TODO(Subv): Add the DSParameterSet tag. + // TODO(Subv): Add the TrafficIndicationMap tag. + // TODO(Subv): Add the CountryInformation tag. + // TODO(Subv): Add the ERPInformation tag. + + return buffer; +} + +/** + * Generates a buffer with the Dummy Nintendo tag. + * It is currently unknown what this tag does. + * TODO(Subv): Figure out if this is needed and what it does. + * @returns A buffer with the Nintendo tagged parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoDummyTag() { + // Note: These values were taken from a packet capture of an o3DS XL + // broadcasting a Super Smash Bros. 4 lobby. + constexpr std::array<u8, 3> dummy_data = {0x0A, 0x00, 0x00}; + + DummyTag tag{}; + tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); + tag.header.length = sizeof(DummyTag) - sizeof(TagHeader); + tag.oui_type = static_cast<u8>(NintendoTagId::Dummy); + tag.oui = NintendoOUI; + tag.data = dummy_data; + + std::vector<u8> buffer(sizeof(DummyTag)); + std::memcpy(buffer.data(), &tag, sizeof(DummyTag)); + + return buffer; +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the network information of the network that is being broadcast. + * It also contains the application data provided by the application that opened the network. + * @returns A buffer with the Nintendo network info parameter of the beacon frame. + */ +std::vector<u8> GenerateNintendoNetworkInfoTag(const NetworkInfo& network_info) { + NetworkInfoTag tag{}; + tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); + tag.header.length = + sizeof(NetworkInfoTag) - sizeof(TagHeader) + network_info.application_data_size; + tag.appdata_size = network_info.application_data_size; + // Set the hash to zero initially, it will be updated once we calculate it. + tag.sha_hash = {}; + + // Ensure the network structure has the correct OUI and OUI type. + ASSERT(network_info.oui_type == static_cast<u8>(NintendoTagId::NetworkInfo)); + ASSERT(network_info.oui_value == NintendoOUI); + + // Ensure the application data size is less than the maximum value. + ASSERT_MSG(network_info.application_data_size <= ApplicationDataSize, "Data size is too big."); + + // This tag contains the network info structure starting at the OUI. + std::memcpy(tag.network_info.data(), &network_info.oui_value, tag.network_info.size()); + + // Copy the tag and the data so we can calculate the SHA1 over it. + std::vector<u8> buffer(sizeof(tag) + network_info.application_data_size); + std::memcpy(buffer.data(), &tag, sizeof(tag)); + std::memcpy(buffer.data() + sizeof(tag), network_info.application_data.data(), + network_info.application_data_size); + + // Calculate the SHA1 of the contents of the tag. + std::array<u8, CryptoPP::SHA1::DIGESTSIZE> hash; + CryptoPP::SHA1().CalculateDigest(hash.data(), + buffer.data() + offsetof(NetworkInfoTag, network_info), + buffer.size() - sizeof(TagHeader)); + + // Copy it directly into the buffer, overwriting the zeros that we had previously placed there. + std::memcpy(buffer.data() + offsetof(NetworkInfoTag, sha_hash), hash.data(), hash.size()); + + return buffer; +} + +/* + * Calculates the CTR used for the AES-CTR encryption of the data stored in the + * EncryptedDataTags. + * @returns The CTR used for beacon crypto. + */ +std::array<u8, CryptoPP::AES::BLOCKSIZE> GetBeaconCryptoCTR(const NetworkInfo& network_info) { + BeaconDataCryptoCTR data{}; + + data.host_mac = network_info.host_mac_address; + data.wlan_comm_id = network_info.wlan_comm_id; + data.id = network_info.id; + data.network_id = network_info.network_id; + + std::array<u8, CryptoPP::AES::BLOCKSIZE> hash; + std::memcpy(hash.data(), &data, sizeof(data)); + + return hash; +} + +/* + * Serializes the node information into the format needed for network transfer, + * and then encrypts it with the NWM key. + * @returns The serialized and encrypted node information. + */ +std::vector<u8> GeneratedEncryptedData(const NetworkInfo& network_info, const NodeList& nodes) { + std::vector<u8> buffer(sizeof(BeaconData)); + + BeaconData data{}; + std::memcpy(buffer.data(), &data, sizeof(BeaconData)); + + for (const NodeInfo& node : nodes) { + // Serialize each node and convert the data from + // host byte-order to Big Endian. + BeaconNodeInfo info{}; + info.friend_code_seed = node.friend_code_seed; + info.network_node_id = node.network_node_id; + for (int i = 0; i < info.username.size(); ++i) + info.username[i] = node.username[i]; + + buffer.insert(buffer.end(), reinterpret_cast<u8*>(&info), + reinterpret_cast<u8*>(&info) + sizeof(info)); + } + + // Calculate the MD5 hash of the data in the buffer, not including the hash field. + std::array<u8, CryptoPP::MD5::DIGESTSIZE> hash; + CryptoPP::MD5().CalculateDigest(hash.data(), buffer.data() + offsetof(BeaconData, bitmask), + buffer.size() - sizeof(data.md5_hash)); + + // Copy the hash into the buffer. + std::memcpy(buffer.data(), hash.data(), hash.size()); + + // Encrypt the data using AES-CTR and the NWM beacon key. + using CryptoPP::AES; + std::array<u8, AES::BLOCKSIZE> counter = GetBeaconCryptoCTR(network_info); + CryptoPP::CTR_Mode<AES>::Encryption aes; + aes.SetKeyWithIV(nwm_beacon_key.data(), AES::BLOCKSIZE, counter.data()); + aes.ProcessData(buffer.data(), buffer.data(), buffer.size()); + + return buffer; +} + +void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer) { + // Decrypt the data using AES-CTR and the NWM beacon key. + using CryptoPP::AES; + std::array<u8, AES::BLOCKSIZE> counter = GetBeaconCryptoCTR(network_info); + CryptoPP::CTR_Mode<AES>::Decryption aes; + aes.SetKeyWithIV(nwm_beacon_key.data(), AES::BLOCKSIZE, counter.data()); + aes.ProcessData(buffer.data(), buffer.data(), buffer.size()); +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the first portion of the encrypted payload in the 802.11 beacon frame. + * The encrypted payload contains information about the nodes currently connected to the network. + * @returns A buffer with the first Nintendo encrypted data parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoFirstEncryptedDataTag(const NetworkInfo& network_info, + const NodeList& nodes) { + const size_t payload_size = + std::min<size_t>(EncryptedDataSizeCutoff, nodes.size() * sizeof(NodeInfo)); + + EncryptedDataTag tag{}; + tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); + tag.header.length = sizeof(tag) - sizeof(TagHeader) + payload_size; + tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData0); + tag.oui = NintendoOUI; + + std::vector<u8> buffer(sizeof(tag) + payload_size); + std::memcpy(buffer.data(), &tag, sizeof(tag)); + + std::vector<u8> encrypted_data = GeneratedEncryptedData(network_info, nodes); + std::memcpy(buffer.data() + sizeof(tag), encrypted_data.data(), payload_size); + + return buffer; +} + +/** + * Generates a buffer with the Network Info Nintendo tag. + * This tag contains the second portion of the encrypted payload in the 802.11 beacon frame. + * The encrypted payload contains information about the nodes currently connected to the network. + * This tag is only present if the payload size is greater than EncryptedDataSizeCutoff (0xFA) + * bytes. + * @returns A buffer with the second Nintendo encrypted data parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoSecondEncryptedDataTag(const NetworkInfo& network_info, + const NodeList& nodes) { + // This tag is only present if the payload is larger than EncryptedDataSizeCutoff (0xFA). + if (nodes.size() * sizeof(NodeInfo) <= EncryptedDataSizeCutoff) + return {}; + + const size_t payload_size = nodes.size() * sizeof(NodeInfo) - EncryptedDataSizeCutoff; + + const size_t tag_length = sizeof(EncryptedDataTag) - sizeof(TagHeader) + payload_size; + + // TODO(Subv): What does the 3DS do when a game has too much data to fit into the tag? + ASSERT_MSG(tag_length <= 255, "Data is too big."); + + EncryptedDataTag tag{}; + tag.header.tag_id = static_cast<u8>(TagId::VendorSpecific); + tag.header.length = tag_length; + tag.oui_type = static_cast<u8>(NintendoTagId::EncryptedData1); + tag.oui = NintendoOUI; + + std::vector<u8> buffer(sizeof(tag) + payload_size); + std::memcpy(buffer.data(), &tag, sizeof(tag)); + + std::vector<u8> encrypted_data = GeneratedEncryptedData(network_info, nodes); + std::memcpy(buffer.data() + sizeof(tag), encrypted_data.data() + EncryptedDataSizeCutoff, + payload_size); + + return buffer; +} + +/** + * Generates a buffer with the Nintendo tagged parameters of an 802.11 Beacon frame + * for UDS communication. + * @returns A buffer with the Nintendo tagged parameters of the beacon frame. + */ +std::vector<u8> GenerateNintendoTaggedParameters(const NetworkInfo& network_info, + const NodeList& nodes) { + ASSERT_MSG(network_info.max_nodes == nodes.size(), "Inconsistent network state."); + + std::vector<u8> buffer = GenerateNintendoDummyTag(); + std::vector<u8> network_info_tag = GenerateNintendoNetworkInfoTag(network_info); + std::vector<u8> first_data_tag = GenerateNintendoFirstEncryptedDataTag(network_info, nodes); + std::vector<u8> second_data_tag = GenerateNintendoSecondEncryptedDataTag(network_info, nodes); + + buffer.insert(buffer.end(), network_info_tag.begin(), network_info_tag.end()); + buffer.insert(buffer.end(), first_data_tag.begin(), first_data_tag.end()); + buffer.insert(buffer.end(), second_data_tag.begin(), second_data_tag.end()); + + return buffer; +} + +std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes) { + std::vector<u8> buffer = GenerateFixedParameters(); + std::vector<u8> basic_tags = GenerateBasicTaggedParameters(); + std::vector<u8> nintendo_tags = GenerateNintendoTaggedParameters(network_info, nodes); + + buffer.insert(buffer.end(), basic_tags.begin(), basic_tags.end()); + buffer.insert(buffer.end(), nintendo_tags.begin(), nintendo_tags.end()); + + 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 new file mode 100644 index 000000000..6df4c4f47 --- /dev/null +++ b/src/core/hle/service/nwm/uds_beacon.h @@ -0,0 +1,173 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <deque> +#include <vector> +#include "common/common_types.h" +#include "common/swap.h" +#include "core/hle/service/service.h" + +namespace Service { +namespace NWM { + +using MacAddress = std::array<u8, 6>; + +/// The maximum number of nodes that can exist in an UDS session. +constexpr u32 UDSMaxNodes = 16; +constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32}; + +/// Additional block tag ids in the Beacon frames +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. + */ +enum class NintendoTagId : u8 { + Dummy = 20, + NetworkInfo = 21, + EncryptedData0 = 24, + EncryptedData1 = 25, +}; + +struct BeaconEntryHeader { + u32_le total_size; + INSERT_PADDING_BYTES(1); + u8 wifi_channel; + INSERT_PADDING_BYTES(2); + MacAddress mac_address; + INSERT_PADDING_BYTES(6); + u32_le unk_size; + u32_le header_size; +}; + +static_assert(sizeof(BeaconEntryHeader) == 0x1C, "BeaconEntryHeader has incorrect size."); + +struct BeaconDataReplyHeader { + u32_le max_output_size; + u32_le total_size; + u32_le total_entries; +}; + +static_assert(sizeof(BeaconDataReplyHeader) == 12, "BeaconDataReplyHeader has incorrect size."); + +#pragma pack(push, 1) +struct BeaconFrameHeader { + // Number of microseconds the AP has been active. + u64_le timestamp; + // Interval between beacon transmissions, expressed in TU. + u16_le beacon_interval; + // Indicates the presence of optional capabilities. + u16_le capabilities; +}; +#pragma pack(pop) + +static_assert(sizeof(BeaconFrameHeader) == 12, "BeaconFrameHeader has incorrect size."); + +struct TagHeader { + u8 tag_id; + u8 length; +}; + +static_assert(sizeof(TagHeader) == 2, "TagHeader has incorrect size."); + +struct DummyTag { + TagHeader header; + std::array<u8, 3> oui; + u8 oui_type; + std::array<u8, 3> data; +}; + +static_assert(sizeof(DummyTag) == 9, "DummyTag has incorrect size."); + +struct NetworkInfoTag { + TagHeader header; + std::array<u8, 0x1F> network_info; + std::array<u8, 0x14> sha_hash; + u8 appdata_size; +}; + +static_assert(sizeof(NetworkInfoTag) == 54, "NetworkInfoTag has incorrect size."); + +struct EncryptedDataTag { + TagHeader header; + std::array<u8, 3> oui; + u8 oui_type; +}; + +static_assert(sizeof(EncryptedDataTag) == 6, "EncryptedDataTag has incorrect size."); + +#pragma pack(push, 1) +// The raw bytes of this structure are the CTR used in the encryption (AES-CTR) +// of the beacon data stored in the EncryptedDataTags. +struct BeaconDataCryptoCTR { + MacAddress host_mac; + u32_le wlan_comm_id; + u8 id; + INSERT_PADDING_BYTES(1); + u32_le network_id; +}; + +static_assert(sizeof(BeaconDataCryptoCTR) == 0x10, "BeaconDataCryptoCTR has incorrect size."); + +struct BeaconNodeInfo { + u64_be friend_code_seed; + std::array<u16_be, 10> username; + u16_be network_node_id; +}; + +static_assert(sizeof(BeaconNodeInfo) == 0x1E, "BeaconNodeInfo has incorrect size."); + +struct BeaconData { + std::array<u8, 0x10> md5_hash; + u16_be bitmask; +}; +#pragma pack(pop) + +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`. + */ +void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer); + +/** + * Generates an 802.11 beacon frame starting at the management frame header. + * This frame contains information about the network and its connected clients. + * @returns The generated frame. + */ +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/ptm/ptm.cpp b/src/core/hle/service/ptm/ptm.cpp index e373ed47a..319e8c946 100644 --- a/src/core/hle/service/ptm/ptm.cpp +++ b/src/core/hle/service/ptm/ptm.cpp @@ -27,67 +27,72 @@ static bool shell_open; static bool battery_is_charging; -void GetAdapterState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); +static bool pedometer_is_counting; - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. +void GetAdapterState(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x5, 0, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = battery_is_charging ? 1 : 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(battery_is_charging); LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetShellState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x6, 0, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = shell_open ? 1 : 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(shell_open); } void GetBatteryLevel(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x7, 0, 0); - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = - static_cast<u32>(ChargeLevels::CompletelyFull); // Set to a completely full battery + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u32>(ChargeLevels::CompletelyFull)); // Set to a completely full battery LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetBatteryChargeState(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x8, 0, 0); - // TODO(purpasmart96): This function is only a stub, - // it returns a valid result without implementing full functionality. + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(battery_is_charging); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = battery_is_charging ? 1 : 0; + LOG_WARNING(Service_PTM, "(STUBBED) called"); +} + +void GetPedometerState(Service::Interface* self) { + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x9, 0, 0); + + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(pedometer_is_counting); LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetTotalStepCount(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0xC, 0, 0); - // TODO: This function is only a stub, - // it returns 0 as the total step count - - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(0); LOG_WARNING(Service_PTM, "(STUBBED) called"); } void GetSoftwareClosedFlag(Service::Interface* self) { - u32* cmd_buff = Kernel::GetCommandBuffer(); + IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x80F, 0, 0); - cmd_buff[1] = RESULT_SUCCESS.raw; - cmd_buff[2] = 0; + IPC::RequestBuilder rb = rp.MakeBuilder(2, 0); + rb.Push(RESULT_SUCCESS); + rb.Push(false); LOG_WARNING(Service_PTM, "(STUBBED) called"); } @@ -121,6 +126,7 @@ void Init() { shell_open = true; battery_is_charging = true; + pedometer_is_counting = false; // Open the SharedExtSaveData archive 0xF000000B and create the gamecoin.dat file if it doesn't // exist diff --git a/src/core/hle/service/ptm/ptm.h b/src/core/hle/service/ptm/ptm.h index 683fb445b..e17e59835 100644 --- a/src/core/hle/service/ptm/ptm.h +++ b/src/core/hle/service/ptm/ptm.h @@ -75,6 +75,14 @@ void GetBatteryLevel(Interface* self); void GetBatteryChargeState(Interface* self); /** + * PTM::GetPedometerState service function + * Outputs: + * 1 : Result of function, 0 on success, otherwise error code + * 2 : Output of function, 0 = not counting steps, 1 = counting steps. + */ +void GetPedometerState(Interface* self); + +/** * PTM::GetTotalStepCount service function * Outputs: * 1 : Result of function, 0 on success, otherwise error code diff --git a/src/core/hle/service/ptm/ptm_u.cpp b/src/core/hle/service/ptm/ptm_u.cpp index e0b65ba89..696a58a36 100644 --- a/src/core/hle/service/ptm/ptm_u.cpp +++ b/src/core/hle/service/ptm/ptm_u.cpp @@ -17,7 +17,7 @@ const Interface::FunctionInfo FunctionTable[] = { {0x00060000, GetShellState, "GetShellState"}, {0x00070000, GetBatteryLevel, "GetBatteryLevel"}, {0x00080000, GetBatteryChargeState, "GetBatteryChargeState"}, - {0x00090000, nullptr, "GetPedometerState"}, + {0x00090000, GetPedometerState, "GetPedometerState"}, {0x000A0042, nullptr, "GetStepHistoryEntry"}, {0x000B00C2, nullptr, "GetStepHistory"}, {0x000C0000, GetTotalStepCount, "GetTotalStepCount"}, |