diff options
Diffstat (limited to 'src/core/hle')
-rw-r--r-- | src/core/hle/kernel/svc.cpp | 36 | ||||
-rw-r--r-- | src/core/hle/service/am/am.cpp | 331 | ||||
-rw-r--r-- | src/core/hle/service/am/am.h | 29 | ||||
-rw-r--r-- | src/core/hle/service/am/applets/applets.cpp | 115 | ||||
-rw-r--r-- | src/core/hle/service/am/applets/applets.h | 94 | ||||
-rw-r--r-- | src/core/hle/service/am/applets/software_keyboard.cpp | 161 | ||||
-rw-r--r-- | src/core/hle/service/am/applets/software_keyboard.h | 69 |
7 files changed, 729 insertions, 106 deletions
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 9904605cd..51c367de7 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -1183,9 +1183,39 @@ static ResultCode ResetSignal(Handle handle) { /// Creates a TransferMemory object static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 permissions) { - LOG_WARNING(Kernel_SVC, "(STUBBED) called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size, - permissions); - *handle = 0; + LOG_DEBUG(Kernel_SVC, "called addr=0x{:X}, size=0x{:X}, perms=0x{:08X}", addr, size, + permissions); + + if (!Common::Is4KBAligned(addr)) { + LOG_ERROR(Kernel_SVC, "Address ({:016X}) is not page aligned!", addr); + return ERR_INVALID_ADDRESS; + } + + if (!Common::Is4KBAligned(size) || size == 0) { + LOG_ERROR(Kernel_SVC, "Size ({:016X}) is not page aligned or equal to zero!", size); + return ERR_INVALID_ADDRESS; + } + + if (!IsValidAddressRange(addr, size)) { + LOG_ERROR(Kernel_SVC, "Address and size cause overflow! (address={:016X}, size={:016X})", + addr, size); + return ERR_INVALID_ADDRESS_STATE; + } + + const auto perms = static_cast<MemoryPermission>(permissions); + if (perms != MemoryPermission::None && perms != MemoryPermission::Read && + perms != MemoryPermission::ReadWrite) { + LOG_ERROR(Kernel_SVC, "Invalid memory permissions for transfer memory! (perms={:08X})", + permissions); + return ERR_INVALID_MEMORY_PERMISSIONS; + } + + auto& kernel = Core::System::GetInstance().Kernel(); + auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + const auto shared_mem_handle = SharedMemory::Create( + kernel, handle_table.Get<Process>(CurrentProcess), size, perms, perms, addr); + + CASCADE_RESULT(*handle, handle_table.Create(shared_mem_handle)); return RESULT_SUCCESS; } diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 3758ecae1..fd14af1e7 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -6,10 +6,14 @@ #include <cinttypes> #include <cstring> #include <stack> +#include "applets/applets.h" +#include "applets/software_keyboard.h" +#include "audio_core/audio_renderer.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/process.h" +#include "core/hle/kernel/shared_memory.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" @@ -28,6 +32,13 @@ namespace Service::AM { +constexpr ResultCode ERR_NO_DATA_IN_CHANNEL{ErrorModule::AM, 0x2}; +constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7}; + +enum class AppletId : u32 { + SoftwareKeyboard = 0x11, +}; + constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA; struct LaunchParameters { @@ -481,6 +492,24 @@ void ICommonStateGetter::GetDefaultDisplayResolution(Kernel::HLERequestContext& LOG_DEBUG(Service_AM, "called"); } +IStorage::IStorage(std::vector<u8> buffer) + : ServiceFramework("IStorage"), buffer(std::move(buffer)) { + // clang-format off + static const FunctionInfo functions[] = { + {0, &IStorage::Open, "Open"}, + {1, nullptr, "OpenTransferStorage"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +IStorage::~IStorage() = default; + +const std::vector<u8>& IStorage::GetData() const { + return buffer; +} + void ICommonStateGetter::GetOperationMode(Kernel::HLERequestContext& ctx) { const bool use_docked_mode{Settings::values.use_docked_mode}; IPC::ResponseBuilder rb{ctx, 3}; @@ -500,15 +529,31 @@ void ICommonStateGetter::GetPerformanceMode(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); } -class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { +class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { public: - explicit IStorageAccessor(std::vector<u8> buffer) - : ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) { + explicit ILibraryAppletAccessor(std::shared_ptr<Applets::Applet> applet) + : ServiceFramework("ILibraryAppletAccessor"), applet(std::move(applet)), + broker(std::make_shared<Applets::AppletDataBroker>()) { // clang-format off static const FunctionInfo functions[] = { - {0, &IStorageAccessor::GetSize, "GetSize"}, - {10, &IStorageAccessor::Write, "Write"}, - {11, &IStorageAccessor::Read, "Read"}, + {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, + {1, &ILibraryAppletAccessor::IsCompleted, "IsCompleted"}, + {10, &ILibraryAppletAccessor::Start, "Start"}, + {20, nullptr, "RequestExit"}, + {25, nullptr, "Terminate"}, + {30, &ILibraryAppletAccessor::GetResult, "GetResult"}, + {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"}, + {100, &ILibraryAppletAccessor::PushInData, "PushInData"}, + {101, &ILibraryAppletAccessor::PopOutData, "PopOutData"}, + {102, nullptr, "PushExtraStorage"}, + {103, &ILibraryAppletAccessor::PushInteractiveInData, "PushInteractiveInData"}, + {104, &ILibraryAppletAccessor::PopInteractiveOutData, "PopInteractiveOutData"}, + {105, &ILibraryAppletAccessor::GetPopOutDataEvent, "GetPopOutDataEvent"}, + {106, &ILibraryAppletAccessor::GetPopInteractiveOutDataEvent, "GetPopInteractiveOutDataEvent"}, + {110, nullptr, "NeedsToExitProcess"}, + {120, nullptr, "GetLibraryAppletInfo"}, + {150, nullptr, "RequestForAppletToGetForeground"}, + {160, nullptr, "GetIndirectLayerConsumerHandle"}, }; // clang-format on @@ -516,158 +561,188 @@ public: } private: - std::vector<u8> buffer; + void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) { + const auto event = broker->GetStateChangedEvent(); + event->Signal(); - void GetSize(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 4}; + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(event); + + LOG_DEBUG(Service_AM, "called"); + } + void IsCompleted(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push(static_cast<u64>(buffer.size())); + rb.Push<u32>(applet->TransactionComplete()); LOG_DEBUG(Service_AM, "called"); } - void Write(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; + void GetResult(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(applet->GetStatus()); - const u64 offset{rp.Pop<u64>()}; - const std::vector<u8> data{ctx.ReadBuffer()}; + LOG_DEBUG(Service_AM, "called"); + } - ASSERT(offset + data.size() <= buffer.size()); + void Start(Kernel::HLERequestContext& ctx) { + ASSERT(applet != nullptr); - std::memcpy(&buffer[offset], data.data(), data.size()); + applet->Initialize(broker); + applet->Execute(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_AM, "called, offset={}", offset); + LOG_DEBUG(Service_AM, "called"); } - void Read(Kernel::HLERequestContext& ctx) { + void PushInData(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; + broker->PushNormalDataFromGame(*rp.PopIpcInterface<IStorage>()); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_AM, "called"); + } + + void PopOutData(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - const u64 offset{rp.Pop<u64>()}; - const std::size_t size{ctx.GetWriteBufferSize()}; + const auto storage = broker->PopNormalDataToGame(); + if (storage == nullptr) { + rb.Push(ERR_NO_DATA_IN_CHANNEL); + return; + } - ASSERT(offset + size <= buffer.size()); + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IStorage>(std::move(*storage)); + + LOG_DEBUG(Service_AM, "called"); + } + + void PushInteractiveInData(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + broker->PushInteractiveDataFromGame(*rp.PopIpcInterface<IStorage>()); - ctx.WriteBuffer(buffer.data() + offset, size); + ASSERT(applet->IsInitialized()); + applet->ExecuteInteractive(); + applet->Execute(); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_AM, "called, offset={}", offset); + LOG_DEBUG(Service_AM, "called"); } -}; -class IStorage final : public ServiceFramework<IStorage> { -public: - explicit IStorage(std::vector<u8> buffer) - : ServiceFramework("IStorage"), buffer(std::move(buffer)) { - // clang-format off - static const FunctionInfo functions[] = { - {0, &IStorage::Open, "Open"}, - {1, nullptr, "OpenTransferStorage"}, - }; - // clang-format on + void PopInteractiveOutData(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - RegisterHandlers(functions); + const auto storage = broker->PopInteractiveDataToGame(); + if (storage == nullptr) { + rb.Push(ERR_NO_DATA_IN_CHANNEL); + return; + } + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IStorage>(std::move(*storage)); + + LOG_DEBUG(Service_AM, "called"); } -private: - std::vector<u8> buffer; + void GetPopOutDataEvent(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushCopyObjects(broker->GetNormalDataEvent()); - void Open(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + LOG_DEBUG(Service_AM, "called"); + } + void GetPopInteractiveOutDataEvent(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::IStorageAccessor>(buffer); + rb.PushCopyObjects(broker->GetInteractiveDataEvent()); LOG_DEBUG(Service_AM, "called"); } + + std::shared_ptr<Applets::Applet> applet; + std::shared_ptr<Applets::AppletDataBroker> broker; }; -class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { -public: - explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") { - // clang-format off +void IStorage::Open(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface<IStorageAccessor>(*this); + + LOG_DEBUG(Service_AM, "called"); +} + +IStorageAccessor::IStorageAccessor(IStorage& storage) + : ServiceFramework("IStorageAccessor"), backing(storage) { + // clang-format off static const FunctionInfo functions[] = { - {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, - {1, nullptr, "IsCompleted"}, - {10, &ILibraryAppletAccessor::Start, "Start"}, - {20, nullptr, "RequestExit"}, - {25, nullptr, "Terminate"}, - {30, &ILibraryAppletAccessor::GetResult, "GetResult"}, - {50, nullptr, "SetOutOfFocusApplicationSuspendingEnabled"}, - {100, &ILibraryAppletAccessor::PushInData, "PushInData"}, - {101, &ILibraryAppletAccessor::PopOutData, "PopOutData"}, - {102, nullptr, "PushExtraStorage"}, - {103, nullptr, "PushInteractiveInData"}, - {104, nullptr, "PopInteractiveOutData"}, - {105, nullptr, "GetPopOutDataEvent"}, - {106, nullptr, "GetPopInteractiveOutDataEvent"}, - {110, nullptr, "NeedsToExitProcess"}, - {120, nullptr, "GetLibraryAppletInfo"}, - {150, nullptr, "RequestForAppletToGetForeground"}, - {160, nullptr, "GetIndirectLayerConsumerHandle"}, + {0, &IStorageAccessor::GetSize, "GetSize"}, + {10, &IStorageAccessor::Write, "Write"}, + {11, &IStorageAccessor::Read, "Read"}, }; - // clang-format on + // clang-format on - RegisterHandlers(functions); + RegisterHandlers(functions); +} - auto& kernel = Core::System::GetInstance().Kernel(); - state_changed_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, - "ILibraryAppletAccessor:StateChangedEvent"); - } +IStorageAccessor::~IStorageAccessor() = default; -private: - void GetAppletStateChangedEvent(Kernel::HLERequestContext& ctx) { - state_changed_event->Signal(); +void IStorageAccessor::GetSize(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 4}; - IPC::ResponseBuilder rb{ctx, 2, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(state_changed_event); + rb.Push(RESULT_SUCCESS); + rb.Push(static_cast<u64>(backing.buffer.size())); - LOG_WARNING(Service_AM, "(STUBBED) called"); - } + LOG_DEBUG(Service_AM, "called"); +} - void GetResult(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); +void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - LOG_WARNING(Service_AM, "(STUBBED) called"); - } + const u64 offset{rp.Pop<u64>()}; + const std::vector<u8> data{ctx.ReadBuffer()}; - void Start(Kernel::HLERequestContext& ctx) { + if (data.size() > backing.buffer.size() - offset) { IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); - - LOG_WARNING(Service_AM, "(STUBBED) called"); + rb.Push(ERR_SIZE_OUT_OF_BOUNDS); } - void PushInData(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - storage_stack.push(rp.PopIpcInterface<AM::IStorage>()); + std::memcpy(backing.buffer.data() + offset, data.data(), data.size()); - IPC::ResponseBuilder rb{ctx, 2}; - rb.Push(RESULT_SUCCESS); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); - LOG_DEBUG(Service_AM, "called"); - } + LOG_DEBUG(Service_AM, "called, offset={}", offset); +} - void PopOutData(Kernel::HLERequestContext& ctx) { - IPC::ResponseBuilder rb{ctx, 2, 0, 1}; - rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::IStorage>(std::move(storage_stack.top())); +void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; - storage_stack.pop(); + const u64 offset{rp.Pop<u64>()}; + const std::size_t size{ctx.GetWriteBufferSize()}; - LOG_DEBUG(Service_AM, "called"); + if (size > backing.buffer.size() - offset) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ERR_SIZE_OUT_OF_BOUNDS); } - std::stack<std::shared_ptr<AM::IStorage>> storage_stack; - Kernel::SharedPtr<Kernel::Event> state_changed_event; -}; + ctx.WriteBuffer(backing.buffer.data() + offset, size); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + + LOG_DEBUG(Service_AM, "called, offset={}", offset); +} ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryAppletCreator") { static const FunctionInfo functions[] = { @@ -675,7 +750,7 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple {1, nullptr, "TerminateAllLibraryApplets"}, {2, nullptr, "AreAnyLibraryAppletsLeft"}, {10, &ILibraryAppletCreator::CreateStorage, "CreateStorage"}, - {11, nullptr, "CreateTransferMemoryStorage"}, + {11, &ILibraryAppletCreator::CreateTransferMemoryStorage, "CreateTransferMemoryStorage"}, {12, nullptr, "CreateHandleStorage"}, }; RegisterHandlers(functions); @@ -683,11 +758,36 @@ ILibraryAppletCreator::ILibraryAppletCreator() : ServiceFramework("ILibraryApple ILibraryAppletCreator::~ILibraryAppletCreator() = default; +static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) { + switch (id) { + case AppletId::SoftwareKeyboard: + return std::make_shared<Applets::SoftwareKeyboard>(); + default: + UNREACHABLE_MSG("Unimplemented AppletId [{:08X}]!", static_cast<u32>(id)); + return nullptr; + } +} + void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto applet_id = rp.PopRaw<AppletId>(); + const auto applet_mode = rp.PopRaw<u32>(); + + LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", + static_cast<u32>(applet_id), applet_mode); + + const auto applet = GetAppletFromId(applet_id); + + if (applet == nullptr) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); + return; + } + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<AM::ILibraryAppletAccessor>(); + rb.PushIpcInterface<AM::ILibraryAppletAccessor>(applet); LOG_DEBUG(Service_AM, "called"); } @@ -704,6 +804,31 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called, size={}", size); } +void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + + rp.SetCurrentOffset(3); + const auto handle{rp.Pop<Kernel::Handle>()}; + + const auto shared_mem = + Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::SharedMemory>( + handle); + + if (shared_mem == nullptr) { + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(ResultCode(-1)); + return; + } + + const auto mem_begin = shared_mem->backing_block->begin() + shared_mem->backing_block_offset; + const auto mem_end = mem_begin + shared_mem->size; + std::vector<u8> memory{mem_begin, mem_end}; + + IPC::ResponseBuilder rb{ctx, 2, 0, 1}; + rb.Push(RESULT_SUCCESS); + rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory))); +} + IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { // clang-format off static const FunctionInfo functions[] = { diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 5a3fcba8f..44c1bcde5 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -155,6 +155,34 @@ private: std::shared_ptr<AppletMessageQueue> msg_queue; }; +class IStorage final : public ServiceFramework<IStorage> { +public: + explicit IStorage(std::vector<u8> buffer); + ~IStorage() override; + + const std::vector<u8>& GetData() const; + +private: + void Open(Kernel::HLERequestContext& ctx); + + std::vector<u8> buffer; + + friend class IStorageAccessor; +}; + +class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { +public: + explicit IStorageAccessor(IStorage& backing); + ~IStorageAccessor() override; + +private: + void GetSize(Kernel::HLERequestContext& ctx); + void Write(Kernel::HLERequestContext& ctx); + void Read(Kernel::HLERequestContext& ctx); + + IStorage& backing; +}; + class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { public: ILibraryAppletCreator(); @@ -163,6 +191,7 @@ public: private: void CreateLibraryApplet(Kernel::HLERequestContext& ctx); void CreateStorage(Kernel::HLERequestContext& ctx); + void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); }; class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp new file mode 100644 index 000000000..8adb81823 --- /dev/null +++ b/src/core/hle/service/am/applets/applets.cpp @@ -0,0 +1,115 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/assert.h" +#include "core/core.h" +#include "core/hle/kernel/event.h" +#include "core/hle/kernel/server_port.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Service::AM::Applets { + +AppletDataBroker::AppletDataBroker() { + auto& kernel = Core::System::GetInstance().Kernel(); + state_changed_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, + "ILibraryAppletAccessor:StateChangedEvent"); + pop_out_data_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, + "ILibraryAppletAccessor:PopDataOutEvent"); + pop_interactive_out_data_event = Kernel::Event::Create( + kernel, Kernel::ResetType::OneShot, "ILibraryAppletAccessor:PopInteractiveDataOutEvent"); +} + +AppletDataBroker::~AppletDataBroker() = default; + +std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToGame() { + if (out_channel.empty()) + return nullptr; + + auto out = std::move(out_channel.front()); + out_channel.pop(); + return out; +} + +std::unique_ptr<IStorage> AppletDataBroker::PopNormalDataToApplet() { + if (in_channel.empty()) + return nullptr; + + auto out = std::move(in_channel.front()); + in_channel.pop(); + return out; +} + +std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToGame() { + if (out_interactive_channel.empty()) + return nullptr; + + auto out = std::move(out_interactive_channel.front()); + out_interactive_channel.pop(); + return out; +} + +std::unique_ptr<IStorage> AppletDataBroker::PopInteractiveDataToApplet() { + if (in_interactive_channel.empty()) + return nullptr; + + auto out = std::move(in_interactive_channel.front()); + in_interactive_channel.pop(); + return out; +} + +void AppletDataBroker::PushNormalDataFromGame(IStorage storage) { + in_channel.push(std::make_unique<IStorage>(storage)); +} + +void AppletDataBroker::PushNormalDataFromApplet(IStorage storage) { + out_channel.push(std::make_unique<IStorage>(storage)); + pop_out_data_event->Signal(); +} + +void AppletDataBroker::PushInteractiveDataFromGame(IStorage storage) { + in_interactive_channel.push(std::make_unique<IStorage>(storage)); +} + +void AppletDataBroker::PushInteractiveDataFromApplet(IStorage storage) { + out_interactive_channel.push(std::make_unique<IStorage>(storage)); + pop_interactive_out_data_event->Signal(); +} + +void AppletDataBroker::SignalStateChanged() const { + state_changed_event->Signal(); +} + +Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetNormalDataEvent() const { + return pop_out_data_event; +} + +Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetInteractiveDataEvent() const { + return pop_interactive_out_data_event; +} + +Kernel::SharedPtr<Kernel::Event> AppletDataBroker::GetStateChangedEvent() const { + return state_changed_event; +} + +Applet::Applet() = default; + +Applet::~Applet() = default; + +void Applet::Initialize(std::shared_ptr<AppletDataBroker> broker_) { + broker = std::move(broker_); + + const auto common = broker->PopNormalDataToApplet(); + ASSERT(common != nullptr); + + const auto common_data = common->GetData(); + + ASSERT(common_data.size() >= sizeof(CommonArguments)); + std::memcpy(&common_args, common_data.data(), sizeof(CommonArguments)); + + initialized = true; +} + +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h new file mode 100644 index 000000000..136445649 --- /dev/null +++ b/src/core/hle/service/am/applets/applets.h @@ -0,0 +1,94 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <functional> +#include <memory> +#include <queue> +#include "common/swap.h" +#include "core/hle/kernel/event.h" + +union ResultCode; + +namespace Service::AM { + +class IStorage; + +namespace Applets { + +class AppletDataBroker final { +public: + AppletDataBroker(); + ~AppletDataBroker(); + + std::unique_ptr<IStorage> PopNormalDataToGame(); + std::unique_ptr<IStorage> PopNormalDataToApplet(); + + std::unique_ptr<IStorage> PopInteractiveDataToGame(); + std::unique_ptr<IStorage> PopInteractiveDataToApplet(); + + void PushNormalDataFromGame(IStorage storage); + void PushNormalDataFromApplet(IStorage storage); + + void PushInteractiveDataFromGame(IStorage storage); + void PushInteractiveDataFromApplet(IStorage storage); + + void SignalStateChanged() const; + + Kernel::SharedPtr<Kernel::Event> GetNormalDataEvent() const; + Kernel::SharedPtr<Kernel::Event> GetInteractiveDataEvent() const; + Kernel::SharedPtr<Kernel::Event> GetStateChangedEvent() const; + +private: + // Queues are named from applet's perspective + std::queue<std::unique_ptr<IStorage>> + in_channel; // PopNormalDataToApplet and PushNormalDataFromGame + std::queue<std::unique_ptr<IStorage>> + out_channel; // PopNormalDataToGame and PushNormalDataFromApplet + std::queue<std::unique_ptr<IStorage>> + in_interactive_channel; // PopInteractiveDataToApplet and PushInteractiveDataFromGame + std::queue<std::unique_ptr<IStorage>> + out_interactive_channel; // PopInteractiveDataToGame and PushInteractiveDataFromApplet + + Kernel::SharedPtr<Kernel::Event> state_changed_event; + Kernel::SharedPtr<Kernel::Event> pop_out_data_event; // Signaled on PushNormalDataFromApplet + Kernel::SharedPtr<Kernel::Event> + pop_interactive_out_data_event; // Signaled on PushInteractiveDataFromApplet +}; + +class Applet { +public: + Applet(); + virtual ~Applet(); + + virtual void Initialize(std::shared_ptr<AppletDataBroker> broker); + + virtual bool TransactionComplete() const = 0; + virtual ResultCode GetStatus() const = 0; + virtual void ExecuteInteractive() = 0; + virtual void Execute() = 0; + + bool IsInitialized() const { + return initialized; + } + +protected: + struct CommonArguments { + u32_le arguments_version; + u32_le size; + u32_le library_version; + u32_le theme_color; + u8 play_startup_sound; + u64_le system_tick; + }; + static_assert(sizeof(CommonArguments) == 0x20, "CommonArguments has incorrect size."); + + CommonArguments common_args; + std::shared_ptr<AppletDataBroker> broker; + bool initialized = false; +}; + +} // namespace Applets +} // namespace Service::AM diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp new file mode 100644 index 000000000..c4b76a515 --- /dev/null +++ b/src/core/hle/service/am/applets/software_keyboard.cpp @@ -0,0 +1,161 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <cstring> +#include "common/assert.h" +#include "common/string_util.h" +#include "core/core.h" +#include "core/frontend/applets/software_keyboard.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/software_keyboard.h" + +namespace Service::AM::Applets { + +constexpr std::size_t SWKBD_OUTPUT_BUFFER_SIZE = 0x7D8; +constexpr std::size_t SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE = 0x7D4; +constexpr std::size_t DEFAULT_MAX_LENGTH = 500; +constexpr bool INTERACTIVE_STATUS_OK = false; + +static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters( + KeyboardConfig config, std::u16string initial_text) { + Core::Frontend::SoftwareKeyboardParameters params{}; + + params.submit_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + config.submit_text.data(), config.submit_text.size()); + params.header_text = Common::UTF16StringFromFixedZeroTerminatedBuffer( + config.header_text.data(), config.header_text.size()); + params.sub_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.sub_text.data(), + config.sub_text.size()); + params.guide_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(config.guide_text.data(), + config.guide_text.size()); + params.initial_text = initial_text; + params.max_length = config.length_limit == 0 ? DEFAULT_MAX_LENGTH : config.length_limit; + params.password = static_cast<bool>(config.is_password); + params.cursor_at_beginning = static_cast<bool>(config.initial_cursor_position); + params.value = static_cast<u8>(config.keyset_disable_bitmask); + + return params; +} + +SoftwareKeyboard::SoftwareKeyboard() = default; + +SoftwareKeyboard::~SoftwareKeyboard() = default; + +void SoftwareKeyboard::Initialize(std::shared_ptr<AppletDataBroker> broker_) { + complete = false; + initial_text.clear(); + final_data.clear(); + + Applet::Initialize(std::move(broker_)); + + const auto keyboard_config_storage = broker->PopNormalDataToApplet(); + ASSERT(keyboard_config_storage != nullptr); + const auto& keyboard_config = keyboard_config_storage->GetData(); + + ASSERT(keyboard_config.size() >= sizeof(KeyboardConfig)); + std::memcpy(&config, keyboard_config.data(), sizeof(KeyboardConfig)); + + const auto work_buffer_storage = broker->PopNormalDataToApplet(); + ASSERT(work_buffer_storage != nullptr); + const auto& work_buffer = work_buffer_storage->GetData(); + + if (config.initial_string_size == 0) + return; + + std::vector<char16_t> string(config.initial_string_size); + std::memcpy(string.data(), work_buffer.data() + config.initial_string_offset, + string.size() * 2); + initial_text = Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()); +} + +bool SoftwareKeyboard::TransactionComplete() const { + return complete; +} + +ResultCode SoftwareKeyboard::GetStatus() const { + return RESULT_SUCCESS; +} + +void SoftwareKeyboard::ExecuteInteractive() { + if (complete) + return; + + const auto storage = broker->PopInteractiveDataToApplet(); + ASSERT(storage != nullptr); + const auto data = storage->GetData(); + const auto status = static_cast<bool>(data[0]); + + if (status == INTERACTIVE_STATUS_OK) { + complete = true; + } else { + const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()}; + + std::array<char16_t, SWKBD_OUTPUT_INTERACTIVE_BUFFER_SIZE / 2 - 2> string; + std::memcpy(string.data(), data.data() + 4, string.size() * 2); + frontend.SendTextCheckDialog( + Common::UTF16StringFromFixedZeroTerminatedBuffer(string.data(), string.size()), + [this] { broker->SignalStateChanged(); }); + } +} + +void SoftwareKeyboard::Execute() { + if (complete) { + broker->PushNormalDataFromApplet(IStorage{final_data}); + return; + } + + const auto& frontend{Core::System::GetInstance().GetSoftwareKeyboard()}; + + const auto parameters = ConvertToFrontendParameters(config, initial_text); + + frontend.RequestText([this](std::optional<std::u16string> text) { WriteText(text); }, + parameters); +} + +void SoftwareKeyboard::WriteText(std::optional<std::u16string> text) { + std::vector<u8> output_main(SWKBD_OUTPUT_BUFFER_SIZE); + + if (text.has_value()) { + std::vector<u8> output_sub(SWKBD_OUTPUT_BUFFER_SIZE); + + if (config.utf_8) { + const u64 size = text->size() + 8; + const auto new_text = Common::UTF16ToUTF8(*text); + + std::memcpy(output_sub.data(), &size, sizeof(u64)); + std::memcpy(output_sub.data() + 8, new_text.data(), + std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 8)); + + output_main[0] = INTERACTIVE_STATUS_OK; + std::memcpy(output_main.data() + 4, new_text.data(), + std::min(new_text.size(), SWKBD_OUTPUT_BUFFER_SIZE - 4)); + } else { + const u64 size = text->size() * 2 + 8; + std::memcpy(output_sub.data(), &size, sizeof(u64)); + std::memcpy(output_sub.data() + 8, text->data(), + std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 8)); + + output_main[0] = INTERACTIVE_STATUS_OK; + std::memcpy(output_main.data() + 4, text->data(), + std::min(text->size() * 2, SWKBD_OUTPUT_BUFFER_SIZE - 4)); + } + + complete = !config.text_check; + final_data = output_main; + + if (complete) { + broker->PushNormalDataFromApplet(IStorage{output_main}); + } else { + broker->PushInteractiveDataFromApplet(IStorage{output_sub}); + } + + broker->SignalStateChanged(); + } else { + output_main[0] = 1; + complete = true; + broker->PushNormalDataFromApplet(IStorage{output_main}); + broker->SignalStateChanged(); + } +} +} // namespace Service::AM::Applets diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h new file mode 100644 index 000000000..16e1fff66 --- /dev/null +++ b/src/core/hle/service/am/applets/software_keyboard.h @@ -0,0 +1,69 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "common/common_funcs.h" +#include "core/hle/service/am/am.h" +#include "core/hle/service/am/applets/applets.h" + +namespace Service::AM::Applets { + +enum class KeysetDisable : u32 { + Space = 0x02, + Address = 0x04, + Percent = 0x08, + Slashes = 0x10, + Numbers = 0x40, + DownloadCode = 0x80, +}; + +struct KeyboardConfig { + INSERT_PADDING_BYTES(4); + std::array<char16_t, 9> submit_text; + u16_le left_symbol_key; + u16_le right_symbol_key; + INSERT_PADDING_BYTES(1); + KeysetDisable keyset_disable_bitmask; + u32_le initial_cursor_position; + std::array<char16_t, 65> header_text; + std::array<char16_t, 129> sub_text; + std::array<char16_t, 257> guide_text; + u32_le length_limit; + INSERT_PADDING_BYTES(4); + u32_le is_password; + INSERT_PADDING_BYTES(5); + bool utf_8; + bool draw_background; + u32_le initial_string_offset; + u32_le initial_string_size; + u32_le user_dictionary_offset; + u32_le user_dictionary_size; + bool text_check; + u64_le text_check_callback; +}; +static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect size."); + +class SoftwareKeyboard final : public Applet { +public: + SoftwareKeyboard(); + ~SoftwareKeyboard() override; + + void Initialize(std::shared_ptr<AppletDataBroker> broker) override; + + bool TransactionComplete() const override; + ResultCode GetStatus() const override; + void ExecuteInteractive() override; + void Execute() override; + + void WriteText(std::optional<std::u16string> text); + +private: + KeyboardConfig config; + std::u16string initial_text; + bool complete = false; + std::vector<u8> final_data; +}; + +} // namespace Service::AM::Applets |