diff options
Diffstat (limited to '')
47 files changed, 938 insertions, 546 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index c1b1c086d..918cf5372 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,7 +170,7 @@ endif() # On modern Unixes, this is typically already the case. The lone exception is # glibc, which may default to 32 bits. glibc allows this to be configured # by setting _FILE_OFFSET_BITS. -if(CMAKE_SYSTEM_NAME STREQUAL "Linux") +if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW) add_definitions(-D_FILE_OFFSET_BITS=64) endif() @@ -178,10 +178,6 @@ endif() set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>) - -math(EXPR EMU_ARCH_BITS ${CMAKE_SIZEOF_VOID_P}*8) -add_definitions(-DEMU_ARCH_BITS=${EMU_ARCH_BITS}) - # System imported libraries # ====================== diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index d0e506689..eccd8f64a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -64,8 +64,6 @@ add_library(common STATIC logging/text_formatter.cpp logging/text_formatter.h math_util.h - memory_util.cpp - memory_util.h microprofile.cpp microprofile.h microprofileui.h diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp index 548463787..b52492da6 100644 --- a/src/common/file_util.cpp +++ b/src/common/file_util.cpp @@ -15,21 +15,24 @@ #ifdef _WIN32 #include <windows.h> // windows.h needs to be included before other windows headers -#include <commdlg.h> // for GetSaveFileName -#include <direct.h> // getcwd +#include <direct.h> // getcwd #include <io.h> #include <shellapi.h> #include <shlobj.h> // for SHGetFolderPath #include <tchar.h> #include "common/string_util.h" -// 64 bit offsets for windows +#ifdef _MSC_VER +// 64 bit offsets for MSVC #define fseeko _fseeki64 #define ftello _ftelli64 -#define atoll _atoi64 +#define fileno _fileno +#endif + +// 64 bit offsets for MSVC and MinGW. MinGW also needs this for using _wstat64 #define stat _stat64 #define fstat _fstat64 -#define fileno _fileno + #else #ifdef __APPLE__ #include <sys/param.h> diff --git a/src/common/logging/log.h b/src/common/logging/log.h index abbd056ee..c9161155a 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -91,6 +91,7 @@ enum class Class : ClassType { Service_PM, ///< The PM service Service_PREPO, ///< The PREPO (Play report) service Service_PSC, ///< The PSC service + Service_PSM, ///< The PSM service Service_SET, ///< The SET (Settings) service Service_SM, ///< The SM (Service manager) service Service_SPL, ///< The SPL service diff --git a/src/common/memory_util.cpp b/src/common/memory_util.cpp deleted file mode 100644 index 9736fb12a..000000000 --- a/src/common/memory_util.cpp +++ /dev/null @@ -1,177 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "common/logging/log.h" -#include "common/memory_util.h" - -#ifdef _WIN32 -#include <windows.h> -// Windows.h needs to be included before psapi.h -#include <psapi.h> -#include "common/common_funcs.h" -#include "common/string_util.h" -#else -#include <cstdlib> -#include <sys/mman.h> -#endif - -#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT) -#include <unistd.h> -#define PAGE_MASK (getpagesize() - 1) -#define round_page(x) ((((unsigned long)(x)) + PAGE_MASK) & ~(PAGE_MASK)) -#endif - -// This is purposely not a full wrapper for virtualalloc/mmap, but it -// provides exactly the primitive operations that Dolphin needs. - -void* AllocateExecutableMemory(std::size_t size, bool low) { -#if defined(_WIN32) - void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); -#else - static char* map_hint = nullptr; -#if defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT) - // This OS has no flag to enforce allocation below the 4 GB boundary, - // but if we hint that we want a low address it is very likely we will - // get one. - // An older version of this code used MAP_FIXED, but that has the side - // effect of discarding already mapped pages that happen to be in the - // requested virtual memory range (such as the emulated RAM, sometimes). - if (low && (!map_hint)) - map_hint = (char*)round_page(512 * 1024 * 1024); /* 0.5 GB rounded up to the next page */ -#endif - void* ptr = mmap(map_hint, size, PROT_READ | PROT_WRITE | PROT_EXEC, - MAP_ANON | MAP_PRIVATE -#if defined(ARCHITECTURE_x86_64) && defined(MAP_32BIT) - | (low ? MAP_32BIT : 0) -#endif - , - -1, 0); -#endif /* defined(_WIN32) */ - -#ifdef _WIN32 - if (ptr == nullptr) { -#else - if (ptr == MAP_FAILED) { - ptr = nullptr; -#endif - LOG_ERROR(Common_Memory, "Failed to allocate executable memory"); - } -#if !defined(_WIN32) && defined(ARCHITECTURE_x86_64) && !defined(MAP_32BIT) - else { - if (low) { - map_hint += size; - map_hint = (char*)round_page(map_hint); /* round up to the next page */ - } - } -#endif - -#if EMU_ARCH_BITS == 64 - if ((u64)ptr >= 0x80000000 && low == true) - LOG_ERROR(Common_Memory, "Executable memory ended up above 2GB!"); -#endif - - return ptr; -} - -void* AllocateMemoryPages(std::size_t size) { -#ifdef _WIN32 - void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_READWRITE); -#else - void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0); - - if (ptr == MAP_FAILED) - ptr = nullptr; -#endif - - if (ptr == nullptr) - LOG_ERROR(Common_Memory, "Failed to allocate raw memory"); - - return ptr; -} - -void* AllocateAlignedMemory(std::size_t size, std::size_t alignment) { -#ifdef _WIN32 - void* ptr = _aligned_malloc(size, alignment); -#else - void* ptr = nullptr; -#ifdef ANDROID - ptr = memalign(alignment, size); -#else - if (posix_memalign(&ptr, alignment, size) != 0) - LOG_ERROR(Common_Memory, "Failed to allocate aligned memory"); -#endif -#endif - - if (ptr == nullptr) - LOG_ERROR(Common_Memory, "Failed to allocate aligned memory"); - - return ptr; -} - -void FreeMemoryPages(void* ptr, std::size_t size) { - if (ptr) { -#ifdef _WIN32 - if (!VirtualFree(ptr, 0, MEM_RELEASE)) - LOG_ERROR(Common_Memory, "FreeMemoryPages failed!\n{}", GetLastErrorMsg()); -#else - munmap(ptr, size); -#endif - } -} - -void FreeAlignedMemory(void* ptr) { - if (ptr) { -#ifdef _WIN32 - _aligned_free(ptr); -#else - free(ptr); -#endif - } -} - -void WriteProtectMemory(void* ptr, std::size_t size, bool allowExecute) { -#ifdef _WIN32 - DWORD oldValue; - if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READ : PAGE_READONLY, &oldValue)) - LOG_ERROR(Common_Memory, "WriteProtectMemory failed!\n{}", GetLastErrorMsg()); -#else - mprotect(ptr, size, allowExecute ? (PROT_READ | PROT_EXEC) : PROT_READ); -#endif -} - -void UnWriteProtectMemory(void* ptr, std::size_t size, bool allowExecute) { -#ifdef _WIN32 - DWORD oldValue; - if (!VirtualProtect(ptr, size, allowExecute ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE, - &oldValue)) - LOG_ERROR(Common_Memory, "UnWriteProtectMemory failed!\n{}", GetLastErrorMsg()); -#else - mprotect(ptr, size, - allowExecute ? (PROT_READ | PROT_WRITE | PROT_EXEC) : PROT_WRITE | PROT_READ); -#endif -} - -std::string MemUsage() { -#ifdef _WIN32 -#pragma comment(lib, "psapi") - DWORD processID = GetCurrentProcessId(); - HANDLE hProcess; - PROCESS_MEMORY_COUNTERS pmc; - std::string Ret; - - // Print information about the memory usage of the process. - - hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID); - if (nullptr == hProcess) - return "MemUsage Error"; - - if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) - Ret = fmt::format("{} K", Common::ThousandSeparate(pmc.WorkingSetSize / 1024, 7)); - - CloseHandle(hProcess); - return Ret; -#else - return ""; -#endif -} diff --git a/src/common/memory_util.h b/src/common/memory_util.h deleted file mode 100644 index aad071979..000000000 --- a/src/common/memory_util.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2013 Dolphin Emulator Project / 2014 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <cstddef> -#include <string> - -void* AllocateExecutableMemory(std::size_t size, bool low = true); -void* AllocateMemoryPages(std::size_t size); -void FreeMemoryPages(void* ptr, std::size_t size); -void* AllocateAlignedMemory(std::size_t size, std::size_t alignment); -void FreeAlignedMemory(void* ptr); -void WriteProtectMemory(void* ptr, std::size_t size, bool executable = false); -void UnWriteProtectMemory(void* ptr, std::size_t size, bool allowExecute = false); -std::string MemUsage(); - -inline int GetPageSize() { - return 4096; -} diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 4755ec822..64fdf38cd 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -156,6 +156,8 @@ add_library(core STATIC hle/service/am/omm.h hle/service/am/spsm.cpp hle/service/am/spsm.h + hle/service/am/tcap.cpp + hle/service/am/tcap.h hle/service/aoc/aoc_u.cpp hle/service/aoc/aoc_u.h hle/service/apm/apm.cpp @@ -280,6 +282,8 @@ add_library(core STATIC hle/service/nifm/nifm.h hle/service/nim/nim.cpp hle/service/nim/nim.h + hle/service/npns/npns.cpp + hle/service/npns/npns.h hle/service/ns/ns.cpp hle/service/ns/ns.h hle/service/ns/pl_u.cpp @@ -327,6 +331,8 @@ add_library(core STATIC hle/service/prepo/prepo.h hle/service/psc/psc.cpp hle/service/psc/psc.h + hle/service/ptm/psm.cpp + hle/service/ptm/psm.h hle/service/service.cpp hle/service/service.h hle/service/set/set.cpp diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index edad5f1b1..68d5376cb 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp @@ -77,7 +77,8 @@ HLERequestContext::HLERequestContext(SharedPtr<Kernel::ServerSession> server_ses HLERequestContext::~HLERequestContext() = default; -void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { +void HLERequestContext::ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf, + bool incoming) { IPC::RequestParser rp(src_cmdbuf); command_header = std::make_shared<IPC::CommandHeader>(rp.PopRaw<IPC::CommandHeader>()); @@ -94,8 +95,6 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { rp.Skip(2, false); } if (incoming) { - auto& handle_table = Core::System::GetInstance().Kernel().HandleTable(); - // Populate the object lists with the data in the IPC request. for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) { copy_objects.push_back(handle_table.GetGeneric(rp.Pop<Handle>())); @@ -189,10 +188,9 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) { rp.Skip(1, false); // The command is actually an u64, but we don't use the high part. } -ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, - Process& src_process, - HandleTable& src_table) { - ParseCommandBuffer(src_cmdbuf, true); +ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(const HandleTable& handle_table, + u32_le* src_cmdbuf) { + ParseCommandBuffer(handle_table, src_cmdbuf, true); if (command_header->type == IPC::CommandType::Close) { // Close does not populate the rest of the IPC header return RESULT_SUCCESS; @@ -207,14 +205,17 @@ ResultCode HLERequestContext::PopulateFromIncomingCommandBuffer(u32_le* src_cmdb return RESULT_SUCCESS; } -ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) { +ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(Thread& thread) { + auto& owner_process = *thread.GetOwnerProcess(); + auto& handle_table = owner_process.GetHandleTable(); + std::array<u32, IPC::COMMAND_BUFFER_LENGTH> dst_cmdbuf; - Memory::ReadBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(), + Memory::ReadBlock(owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), dst_cmdbuf.size() * sizeof(u32)); // The header was already built in the internal command buffer. Attempt to parse it to verify // the integrity and then copy it over to the target command buffer. - ParseCommandBuffer(cmd_buf.data(), false); + ParseCommandBuffer(handle_table, cmd_buf.data(), false); // The data_size already includes the payload header, the padding and the domain header. std::size_t size = data_payload_offset + command_header->data_size - @@ -236,8 +237,6 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) ASSERT(copy_objects.size() == handle_descriptor_header->num_handles_to_copy); ASSERT(move_objects.size() == handle_descriptor_header->num_handles_to_move); - auto& handle_table = Core::System::GetInstance().Kernel().HandleTable(); - // We don't make a distinction between copy and move handles when translating since HLE // services don't deal with handles directly. However, the guest applications might check // for specific values in each of these descriptors. @@ -268,7 +267,7 @@ ResultCode HLERequestContext::WriteToOutgoingCommandBuffer(const Thread& thread) } // Copy the translated command buffer back into the thread's command buffer area. - Memory::WriteBlock(*thread.GetOwnerProcess(), thread.GetTLSAddress(), dst_cmdbuf.data(), + Memory::WriteBlock(owner_process, thread.GetTLSAddress(), dst_cmdbuf.data(), dst_cmdbuf.size() * sizeof(u32)); return RESULT_SUCCESS; diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index 894479ee0..f01491daa 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h @@ -24,10 +24,10 @@ class ServiceFrameworkBase; namespace Kernel { class Domain; +class Event; class HandleTable; class HLERequestContext; class Process; -class Event; /** * Interface implemented by HLE Session handlers. @@ -126,13 +126,12 @@ public: u64 timeout, WakeupCallback&& callback, Kernel::SharedPtr<Kernel::Event> event = nullptr); - void ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming); - /// Populates this context with data from the requesting process/thread. - ResultCode PopulateFromIncomingCommandBuffer(u32_le* src_cmdbuf, Process& src_process, - HandleTable& src_table); + ResultCode PopulateFromIncomingCommandBuffer(const HandleTable& handle_table, + u32_le* src_cmdbuf); + /// Writes data from this context back to the requesting process/thread. - ResultCode WriteToOutgoingCommandBuffer(const Thread& thread); + ResultCode WriteToOutgoingCommandBuffer(Thread& thread); u32_le GetCommand() const { return command; @@ -255,6 +254,8 @@ public: std::string Description() const; private: + void ParseCommandBuffer(const HandleTable& handle_table, u32_le* src_cmdbuf, bool incoming); + std::array<u32, IPC::COMMAND_BUFFER_LENGTH> cmd_buf; SharedPtr<Kernel::ServerSession> server_session; // TODO(yuriks): Check common usage of this and optimize size accordingly diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index bd680adfe..4b6b32dd5 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -118,7 +118,6 @@ struct KernelCore::Impl { process_list.clear(); current_process = nullptr; - handle_table.Clear(); resource_limits.fill(nullptr); thread_wakeup_callback_handle_table.Clear(); @@ -209,7 +208,6 @@ struct KernelCore::Impl { std::vector<SharedPtr<Process>> process_list; Process* current_process = nullptr; - Kernel::HandleTable handle_table; std::array<SharedPtr<ResourceLimit>, 4> resource_limits; /// The event type of the generic timer callback event @@ -241,14 +239,6 @@ void KernelCore::Shutdown() { impl->Shutdown(); } -Kernel::HandleTable& KernelCore::HandleTable() { - return impl->handle_table; -} - -const Kernel::HandleTable& KernelCore::HandleTable() const { - return impl->handle_table; -} - SharedPtr<ResourceLimit> KernelCore::ResourceLimitForCategory( ResourceLimitCategory category) const { return impl->resource_limits.at(static_cast<std::size_t>(category)); diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 41554821f..7f822d524 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -47,12 +47,6 @@ public: /// Clears all resources in use by the kernel instance. void Shutdown(); - /// Provides a reference to the handle table. - Kernel::HandleTable& HandleTable(); - - /// Provides a const reference to the handle table. - const Kernel::HandleTable& HandleTable() const; - /// Retrieves a shared pointer to a ResourceLimit identified by the given category. SharedPtr<ResourceLimit> ResourceLimitForCategory(ResourceLimitCategory category) const; diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h index f2816943a..148478488 100644 --- a/src/core/hle/kernel/process.h +++ b/src/core/hle/kernel/process.h @@ -13,6 +13,7 @@ #include <boost/container/static_vector.hpp> #include "common/bit_field.h" #include "common/common_types.h" +#include "core/hle/kernel/handle_table.h" #include "core/hle/kernel/object.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/vm_manager.h" @@ -142,6 +143,16 @@ public: return vm_manager; } + /// Gets a reference to the process' handle table. + HandleTable& GetHandleTable() { + return handle_table; + } + + /// Gets a const reference to the process' handle table. + const HandleTable& GetHandleTable() const { + return handle_table; + } + /// Gets the current status of the process ProcessStatus GetStatus() const { return status; @@ -294,6 +305,9 @@ private: /// specified by metadata provided to the process during loading. bool is_64bit_process = true; + /// Per-process handle table for storing created object handles in. + HandleTable handle_table; + std::string name; }; diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp index 1ece691c7..5fc320403 100644 --- a/src/core/hle/kernel/server_session.cpp +++ b/src/core/hle/kernel/server_session.cpp @@ -107,8 +107,7 @@ ResultCode ServerSession::HandleSyncRequest(SharedPtr<Thread> thread) { // similar. Kernel::HLERequestContext context(this); u32* cmd_buf = (u32*)Memory::GetPointer(thread->GetTLSAddress()); - context.PopulateFromIncomingCommandBuffer(cmd_buf, *Core::CurrentProcess(), - kernel.HandleTable()); + context.PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf); ResultCode result = RESULT_SUCCESS; // If the session has been converted to a domain, handle the domain request diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 3b8a2e230..67ea67666 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp @@ -189,14 +189,15 @@ static ResultCode ConnectToNamedPort(Handle* out_handle, VAddr port_name_address CASCADE_RESULT(client_session, client_port->Connect()); // Return the client session - CASCADE_RESULT(*out_handle, kernel.HandleTable().Create(client_session)); + auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + CASCADE_RESULT(*out_handle, handle_table.Create(client_session)); return RESULT_SUCCESS; } /// Makes a blocking IPC call to an OS service. static ResultCode SendSyncRequest(Handle handle) { - auto& kernel = Core::System::GetInstance().Kernel(); - SharedPtr<ClientSession> session = kernel.HandleTable().Get<ClientSession>(handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + SharedPtr<ClientSession> session = handle_table.Get<ClientSession>(handle); if (!session) { LOG_ERROR(Kernel_SVC, "called with invalid handle=0x{:08X}", handle); return ERR_INVALID_HANDLE; @@ -215,8 +216,8 @@ static ResultCode SendSyncRequest(Handle handle) { static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) { LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle); - auto& kernel = Core::System::GetInstance().Kernel(); - const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); if (!thread) { return ERR_INVALID_HANDLE; } @@ -229,8 +230,8 @@ static ResultCode GetThreadId(u32* thread_id, Handle thread_handle) { static ResultCode GetProcessId(u32* process_id, Handle process_handle) { LOG_TRACE(Kernel_SVC, "called process=0x{:08X}", process_handle); - auto& kernel = Core::System::GetInstance().Kernel(); - const SharedPtr<Process> process = kernel.HandleTable().Get<Process>(process_handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + const SharedPtr<Process> process = handle_table.Get<Process>(process_handle); if (!process) { return ERR_INVALID_HANDLE; } @@ -273,11 +274,11 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64 using ObjectPtr = Thread::ThreadWaitObjects::value_type; Thread::ThreadWaitObjects objects(handle_count); - auto& kernel = Core::System::GetInstance().Kernel(); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); for (u64 i = 0; i < handle_count; ++i) { const Handle handle = Memory::Read32(handles_address + i * sizeof(Handle)); - const auto object = kernel.HandleTable().Get<WaitObject>(handle); + const auto object = handle_table.Get<WaitObject>(handle); if (object == nullptr) { return ERR_INVALID_HANDLE; @@ -325,8 +326,8 @@ static ResultCode WaitSynchronization(Handle* index, VAddr handles_address, u64 static ResultCode CancelSynchronization(Handle thread_handle) { LOG_TRACE(Kernel_SVC, "called thread=0x{:X}", thread_handle); - auto& kernel = Core::System::GetInstance().Kernel(); - const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); if (!thread) { return ERR_INVALID_HANDLE; } @@ -354,7 +355,7 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr, return ERR_INVALID_ADDRESS; } - auto& handle_table = Core::System::GetInstance().Kernel().HandleTable(); + auto& handle_table = Core::CurrentProcess()->GetHandleTable(); return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle, requesting_thread_handle); } @@ -499,13 +500,12 @@ static ResultCode SetThreadActivity(Handle handle, u32 unknown) { static ResultCode GetThreadContext(VAddr thread_context, Handle handle) { LOG_DEBUG(Kernel_SVC, "called, context=0x{:08X}, thread=0x{:X}", thread_context, handle); - auto& kernel = Core::System::GetInstance().Kernel(); - const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle); + const auto* current_process = Core::CurrentProcess(); + const SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle); if (!thread) { return ERR_INVALID_HANDLE; } - const auto* current_process = Core::CurrentProcess(); if (thread->GetOwnerProcess() != current_process) { return ERR_INVALID_HANDLE; } @@ -531,10 +531,11 @@ static ResultCode GetThreadContext(VAddr thread_context, Handle handle) { /// Gets the priority for the specified thread static ResultCode GetThreadPriority(u32* priority, Handle handle) { - auto& kernel = Core::System::GetInstance().Kernel(); - const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle); - if (!thread) + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + const SharedPtr<Thread> thread = handle_table.Get<Thread>(handle); + if (!thread) { return ERR_INVALID_HANDLE; + } *priority = thread->GetPriority(); return RESULT_SUCCESS; @@ -546,14 +547,15 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) { return ERR_INVALID_THREAD_PRIORITY; } - auto& kernel = Core::System::GetInstance().Kernel(); - SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(handle); - if (!thread) + const auto* const current_process = Core::CurrentProcess(); + SharedPtr<Thread> thread = current_process->GetHandleTable().Get<Thread>(handle); + if (!thread) { return ERR_INVALID_HANDLE; + } // Note: The kernel uses the current process's resource limit instead of // the one from the thread owner's resource limit. - const ResourceLimit& resource_limit = Core::CurrentProcess()->GetResourceLimit(); + const ResourceLimit& resource_limit = current_process->GetResourceLimit(); if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) { return ERR_NOT_AUTHORIZED; } @@ -595,15 +597,13 @@ static ResultCode MapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 s return ERR_INVALID_MEMORY_PERMISSIONS; } - auto& kernel = Core::System::GetInstance().Kernel(); - auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle); + auto* const current_process = Core::CurrentProcess(); + auto shared_memory = current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle); if (!shared_memory) { return ERR_INVALID_HANDLE; } - auto* const current_process = Core::CurrentProcess(); const auto& vm_manager = current_process->VMManager(); - if (!vm_manager.IsWithinASLRRegion(addr, size)) { return ERR_INVALID_MEMORY_RANGE; } @@ -627,15 +627,13 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 return ERR_INVALID_ADDRESS_STATE; } - auto& kernel = Core::System::GetInstance().Kernel(); - auto shared_memory = kernel.HandleTable().Get<SharedMemory>(shared_memory_handle); + auto* const current_process = Core::CurrentProcess(); + auto shared_memory = current_process->GetHandleTable().Get<SharedMemory>(shared_memory_handle); if (!shared_memory) { return ERR_INVALID_HANDLE; } - auto* const current_process = Core::CurrentProcess(); const auto& vm_manager = current_process->VMManager(); - if (!vm_manager.IsWithinASLRRegion(addr, size)) { return ERR_INVALID_MEMORY_RANGE; } @@ -646,15 +644,14 @@ static ResultCode UnmapSharedMemory(Handle shared_memory_handle, VAddr addr, u64 /// Query process memory static ResultCode QueryProcessMemory(MemoryInfo* memory_info, PageInfo* /*page_info*/, Handle process_handle, u64 addr) { - - auto& kernel = Core::System::GetInstance().Kernel(); - SharedPtr<Process> process = kernel.HandleTable().Get<Process>(process_handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + SharedPtr<Process> process = handle_table.Get<Process>(process_handle); if (!process) { return ERR_INVALID_HANDLE; } auto vma = process->VMManager().FindVMA(addr); memory_info->attributes = 0; - if (vma == Core::CurrentProcess()->VMManager().vma_map.end()) { + if (vma == process->VMManager().vma_map.end()) { memory_info->base_address = 0; memory_info->permission = static_cast<u32>(VMAPermission::None); memory_info->size = 0; @@ -695,20 +692,19 @@ static void ExitProcess() { /// Creates a new thread static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, VAddr stack_top, u32 priority, s32 processor_id) { - std::string name = fmt::format("thread-{:X}", entry_point); - if (priority > THREADPRIO_LOWEST) { return ERR_INVALID_THREAD_PRIORITY; } - const ResourceLimit& resource_limit = Core::CurrentProcess()->GetResourceLimit(); + auto* const current_process = Core::CurrentProcess(); + const ResourceLimit& resource_limit = current_process->GetResourceLimit(); if (resource_limit.GetMaxResourceValue(ResourceType::Priority) > priority) { return ERR_NOT_AUTHORIZED; } if (processor_id == THREADPROCESSORID_DEFAULT) { // Set the target CPU to the one specified in the process' exheader. - processor_id = Core::CurrentProcess()->GetDefaultProcessorID(); + processor_id = current_process->GetDefaultProcessorID(); ASSERT(processor_id != THREADPROCESSORID_DEFAULT); } @@ -723,11 +719,13 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V return ERR_INVALID_PROCESSOR_ID; } + const std::string name = fmt::format("thread-{:X}", entry_point); auto& kernel = Core::System::GetInstance().Kernel(); CASCADE_RESULT(SharedPtr<Thread> thread, Thread::Create(kernel, name, entry_point, priority, arg, processor_id, stack_top, - *Core::CurrentProcess())); - const auto new_guest_handle = kernel.HandleTable().Create(thread); + *current_process)); + + const auto new_guest_handle = current_process->GetHandleTable().Create(thread); if (new_guest_handle.Failed()) { return new_guest_handle.Code(); } @@ -748,8 +746,8 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V static ResultCode StartThread(Handle thread_handle) { LOG_TRACE(Kernel_SVC, "called thread=0x{:08X}", thread_handle); - auto& kernel = Core::System::GetInstance().Kernel(); - const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); if (!thread) { return ERR_INVALID_HANDLE; } @@ -796,8 +794,8 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var "called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}", mutex_addr, condition_variable_addr, thread_handle, nano_seconds); - auto& kernel = Core::System::GetInstance().Kernel(); - SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); ASSERT(thread); CASCADE_CODE(Mutex::Release(mutex_addr)); @@ -908,9 +906,9 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target mutex_val | Mutex::MutexHasWaitersFlag)); // The mutex is already owned by some other thread, make this thread wait on it. - auto& kernel = Core::System::GetInstance().Kernel(); - Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask); - auto owner = kernel.HandleTable().Get<Thread>(owner_handle); + const Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + auto owner = handle_table.Get<Thread>(owner_handle); ASSERT(owner); ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex); thread->InvalidateWakeupCallback(); @@ -989,16 +987,16 @@ static u64 GetSystemTick() { static ResultCode CloseHandle(Handle handle) { LOG_TRACE(Kernel_SVC, "Closing handle 0x{:08X}", handle); - auto& kernel = Core::System::GetInstance().Kernel(); - return kernel.HandleTable().Close(handle); + auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + return handle_table.Close(handle); } /// Reset an event static ResultCode ResetSignal(Handle handle) { LOG_WARNING(Kernel_SVC, "(STUBBED) called handle 0x{:08X}", handle); - auto& kernel = Core::System::GetInstance().Kernel(); - auto event = kernel.HandleTable().Get<Event>(handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + auto event = handle_table.Get<Event>(handle); ASSERT(event != nullptr); @@ -1017,8 +1015,8 @@ static ResultCode CreateTransferMemory(Handle* handle, VAddr addr, u64 size, u32 static ResultCode GetThreadCoreMask(Handle thread_handle, u32* core, u64* mask) { LOG_TRACE(Kernel_SVC, "called, handle=0x{:08X}", thread_handle); - auto& kernel = Core::System::GetInstance().Kernel(); - const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); if (!thread) { return ERR_INVALID_HANDLE; } @@ -1033,8 +1031,8 @@ static ResultCode SetThreadCoreMask(Handle thread_handle, u32 core, u64 mask) { LOG_DEBUG(Kernel_SVC, "called, handle=0x{:08X}, mask=0x{:16X}, core=0x{:X}", thread_handle, mask, core); - auto& kernel = Core::System::GetInstance().Kernel(); - const SharedPtr<Thread> thread = kernel.HandleTable().Get<Thread>(thread_handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + const SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle); if (!thread) { return ERR_INVALID_HANDLE; } @@ -1095,7 +1093,7 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss } auto& kernel = Core::System::GetInstance().Kernel(); - auto& handle_table = kernel.HandleTable(); + auto& handle_table = Core::CurrentProcess()->GetHandleTable(); auto shared_mem_handle = SharedMemory::Create(kernel, handle_table.Get<Process>(KernelHandle::CurrentProcess), size, local_perms, remote_perms); @@ -1107,10 +1105,12 @@ static ResultCode CreateSharedMemory(Handle* handle, u64 size, u32 local_permiss static ResultCode ClearEvent(Handle handle) { LOG_TRACE(Kernel_SVC, "called, event=0x{:08X}", handle); - auto& kernel = Core::System::GetInstance().Kernel(); - SharedPtr<Event> evt = kernel.HandleTable().Get<Event>(handle); - if (evt == nullptr) + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + SharedPtr<Event> evt = handle_table.Get<Event>(handle); + if (evt == nullptr) { return ERR_INVALID_HANDLE; + } + evt->Clear(); return RESULT_SUCCESS; } @@ -1123,8 +1123,8 @@ static ResultCode GetProcessInfo(u64* out, Handle process_handle, u32 type) { Status, }; - const auto& kernel = Core::System::GetInstance().Kernel(); - const auto process = kernel.HandleTable().Get<Process>(process_handle); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); + const auto process = handle_table.Get<Process>(process_handle); if (!process) { return ERR_INVALID_HANDLE; } diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index 35ec98c1a..59bc9e0af 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -266,7 +266,7 @@ SharedPtr<Thread> SetupMainThread(KernelCore& kernel, VAddr entry_point, u32 pri SharedPtr<Thread> thread = std::move(thread_res).Unwrap(); // Register 1 must be a handle to the main thread - const Handle guest_handle = kernel.HandleTable().Create(thread).Unwrap(); + const Handle guest_handle = owner_process.GetHandleTable().Create(thread).Unwrap(); thread->SetGuestHandle(guest_handle); thread->GetContext().cpu_registers[1] = guest_handle; diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index 4d1f83170..ecf72ae24 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -15,6 +15,7 @@ #include "core/hle/service/am/idle.h" #include "core/hle/service/am/omm.h" #include "core/hle/service/am/spsm.h" +#include "core/hle/service/am/tcap.h" #include "core/hle/service/apm/apm.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/nvflinger/nvflinger.h" @@ -26,13 +27,18 @@ namespace Service::AM { IWindowController::IWindowController() : ServiceFramework("IWindowController") { + // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateWindow"}, {1, &IWindowController::GetAppletResourceUserId, "GetAppletResourceUserId"}, {10, &IWindowController::AcquireForegroundRights, "AcquireForegroundRights"}, {11, nullptr, "ReleaseForegroundRights"}, {12, nullptr, "RejectToChangeIntoBackground"}, + {20, nullptr, "SetAppletWindowVisibility"}, + {21, nullptr, "SetAppletGpuTimeSlice"}, }; + // clang-format on + RegisterHandlers(functions); } @@ -87,6 +93,7 @@ void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestCo } IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") { + // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetLastForegroundCaptureImage"}, {1, nullptr, "UpdateLastForegroundCaptureImage"}, @@ -117,7 +124,11 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController" {25, nullptr, "ReleaseLastForegroundCaptureSharedBuffer"}, {26, nullptr, "AcquireCallerAppletCaptureSharedBuffer"}, {27, nullptr, "ReleaseCallerAppletCaptureSharedBuffer"}, + // 6.0.0+ + {28, nullptr, "TakeScreenShotOfOwnLayerEx"}, }; + // clang-format on + RegisterHandlers(functions); } @@ -128,6 +139,7 @@ IDebugFunctions::~IDebugFunctions() = default; ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) { + // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Exit"}, {1, &ISelfController::LockExit, "LockExit"}, @@ -136,10 +148,8 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger {4, nullptr, "LeaveFatalSection"}, {9, &ISelfController::GetLibraryAppletLaunchableEvent, "GetLibraryAppletLaunchableEvent"}, {10, &ISelfController::SetScreenShotPermission, "SetScreenShotPermission"}, - {11, &ISelfController::SetOperationModeChangedNotification, - "SetOperationModeChangedNotification"}, - {12, &ISelfController::SetPerformanceModeChangedNotification, - "SetPerformanceModeChangedNotification"}, + {11, &ISelfController::SetOperationModeChangedNotification, "SetOperationModeChangedNotification"}, + {12, &ISelfController::SetPerformanceModeChangedNotification, "SetPerformanceModeChangedNotification"}, {13, &ISelfController::SetFocusHandlingMode, "SetFocusHandlingMode"}, {14, &ISelfController::SetRestartMessageEnabled, "SetRestartMessageEnabled"}, {15, nullptr, "SetScreenShotAppletIdentityInfo"}, @@ -165,7 +175,12 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger {69, nullptr, "IsAutoSleepDisabled"}, {70, nullptr, "ReportMultimediaError"}, {80, nullptr, "SetWirelessPriorityMode"}, + {90, nullptr, "GetAccumulatedSuspendedTickValue"}, + {91, nullptr, "GetAccumulatedSuspendedTickChangedEvent"}, + {1000, nullptr, "GetDebugStorageChannel"}, }; + // clang-format on + RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); @@ -312,6 +327,7 @@ void ISelfController::GetIdleTimeDetectionExtension(Kernel::HLERequestContext& c } ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter") { + // clang-format off static const FunctionInfo functions[] = { {0, &ICommonStateGetter::GetEventHandle, "GetEventHandle"}, {1, &ICommonStateGetter::ReceiveMessage, "ReceiveMessage"}, @@ -336,11 +352,12 @@ ICommonStateGetter::ICommonStateGetter() : ServiceFramework("ICommonStateGetter" {52, nullptr, "SwitchLcdBacklight"}, {55, nullptr, "IsInControllerFirmwareUpdateSection"}, {60, &ICommonStateGetter::GetDefaultDisplayResolution, "GetDefaultDisplayResolution"}, - {61, &ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent, - "GetDefaultDisplayResolutionChangeEvent"}, + {61, &ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent, "GetDefaultDisplayResolutionChangeEvent"}, {62, nullptr, "GetHdcpAuthenticationState"}, {63, nullptr, "GetHdcpAuthenticationStateChangeEvent"}, }; + // clang-format on + RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); @@ -432,11 +449,14 @@ class IStorageAccessor final : public ServiceFramework<IStorageAccessor> { public: explicit IStorageAccessor(std::vector<u8> buffer) : ServiceFramework("IStorageAccessor"), buffer(std::move(buffer)) { + // clang-format off static const FunctionInfo functions[] = { {0, &IStorageAccessor::GetSize, "GetSize"}, {10, &IStorageAccessor::Write, "Write"}, {11, &IStorageAccessor::Read, "Read"}, }; + // clang-format on + RegisterHandlers(functions); } @@ -489,10 +509,13 @@ 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 + RegisterHandlers(functions); } @@ -512,6 +535,7 @@ private: class ILibraryAppletAccessor final : public ServiceFramework<ILibraryAppletAccessor> { public: explicit ILibraryAppletAccessor() : ServiceFramework("ILibraryAppletAccessor") { + // clang-format off static const FunctionInfo functions[] = { {0, &ILibraryAppletAccessor::GetAppletStateChangedEvent, "GetAppletStateChangedEvent"}, {1, nullptr, "IsCompleted"}, @@ -532,6 +556,8 @@ public: {150, nullptr, "RequestForAppletToGetForeground"}, {160, nullptr, "GetIndirectLayerConsumerHandle"}, }; + // clang-format on + RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); @@ -624,13 +650,13 @@ void ILibraryAppletCreator::CreateStorage(Kernel::HLERequestContext& ctx) { } IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { + // clang-format off static const FunctionInfo functions[] = { {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, {10, nullptr, "CreateApplicationAndPushAndRequestToStart"}, {11, nullptr, "CreateApplicationAndPushAndRequestToStartForQuest"}, {12, nullptr, "CreateApplicationAndRequestToStart"}, - {13, &IApplicationFunctions::CreateApplicationAndRequestToStartForQuest, - "CreateApplicationAndRequestToStartForQuest"}, + {13, &IApplicationFunctions::CreateApplicationAndRequestToStartForQuest, "CreateApplicationAndRequestToStartForQuest"}, {20, &IApplicationFunctions::EnsureSaveData, "EnsureSaveData"}, {21, &IApplicationFunctions::GetDesiredLanguage, "GetDesiredLanguage"}, {22, &IApplicationFunctions::SetTerminateResult, "SetTerminateResult"}, @@ -638,10 +664,8 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF {24, nullptr, "GetLaunchStorageInfoForDebug"}, {25, nullptr, "ExtendSaveData"}, {26, nullptr, "GetSaveDataSize"}, - {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, - "BeginBlockingHomeButtonShortAndLongPressed"}, - {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, - "EndBlockingHomeButtonShortAndLongPressed"}, + {30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"}, + {31, &IApplicationFunctions::EndBlockingHomeButtonShortAndLongPressed, "EndBlockingHomeButtonShortAndLongPressed"}, {32, &IApplicationFunctions::BeginBlockingHomeButton, "BeginBlockingHomeButton"}, {33, &IApplicationFunctions::EndBlockingHomeButton, "EndBlockingHomeButton"}, {40, &IApplicationFunctions::NotifyRunning, "NotifyRunning"}, @@ -666,6 +690,8 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF {1000, nullptr, "CreateMovieMaker"}, {1001, nullptr, "PrepareForJit"}, }; + // clang-format on + RegisterHandlers(functions); } @@ -804,9 +830,11 @@ void InstallInterfaces(SM::ServiceManager& service_manager, std::make_shared<IdleSys>()->InstallAsService(service_manager); std::make_shared<OMM>()->InstallAsService(service_manager); std::make_shared<SPSM>()->InstallAsService(service_manager); + std::make_shared<TCAP>()->InstallAsService(service_manager); } IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions") { + // clang-format off static const FunctionInfo functions[] = { {10, &IHomeMenuFunctions::RequestToGetForeground, "RequestToGetForeground"}, {11, nullptr, "LockForeground"}, @@ -815,7 +843,10 @@ IHomeMenuFunctions::IHomeMenuFunctions() : ServiceFramework("IHomeMenuFunctions" {21, nullptr, "GetPopFromGeneralChannelEvent"}, {30, nullptr, "GetHomeButtonWriterLockAccessor"}, {31, nullptr, "GetWriterLockAccessorEx"}, + {100, nullptr, "PopRequestLaunchApplicationForDebug"}, }; + // clang-format on + RegisterHandlers(functions); } @@ -828,6 +859,7 @@ void IHomeMenuFunctions::RequestToGetForeground(Kernel::HLERequestContext& ctx) } IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStateController") { + // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "RequestToEnterSleep"}, {1, nullptr, "EnterSleep"}, @@ -841,18 +873,23 @@ IGlobalStateController::IGlobalStateController() : ServiceFramework("IGlobalStat {14, nullptr, "ShouldSleepOnBoot"}, {15, nullptr, "GetHdcpAuthenticationFailedEvent"}, }; + // clang-format on + RegisterHandlers(functions); } IGlobalStateController::~IGlobalStateController() = default; IApplicationCreator::IApplicationCreator() : ServiceFramework("IApplicationCreator") { + // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "CreateApplication"}, {1, nullptr, "PopLaunchRequestedApplication"}, {10, nullptr, "CreateSystemApplication"}, {100, nullptr, "PopFloatingApplicationForDevelopment"}, }; + // clang-format on + RegisterHandlers(functions); } @@ -860,6 +897,7 @@ IApplicationCreator::~IApplicationCreator() = default; IProcessWindingController::IProcessWindingController() : ServiceFramework("IProcessWindingController") { + // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "GetLaunchReason"}, {11, nullptr, "OpenCallingLibraryApplet"}, @@ -870,6 +908,8 @@ IProcessWindingController::IProcessWindingController() {40, nullptr, "ReserveToStartAndWaitAndUnwindThis"}, {41, nullptr, "ReserveToStartAndWait"}, }; + // clang-format on + RegisterHandlers(functions); } diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp index 4296c255e..68ea778e8 100644 --- a/src/core/hle/service/am/applet_ae.cpp +++ b/src/core/hle/service/am/applet_ae.cpp @@ -211,6 +211,7 @@ void AppletAE::OpenLibraryAppletProxyOld(Kernel::HLERequestContext& ctx) { AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) : ServiceFramework("appletAE"), nvflinger(std::move(nvflinger)) { + // clang-format off static const FunctionInfo functions[] = { {100, &AppletAE::OpenSystemAppletProxy, "OpenSystemAppletProxy"}, {200, &AppletAE::OpenLibraryAppletProxyOld, "OpenLibraryAppletProxyOld"}, @@ -218,7 +219,10 @@ AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) {300, nullptr, "OpenOverlayAppletProxy"}, {350, nullptr, "OpenSystemApplicationProxy"}, {400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"}, + {401, nullptr, "GetSystemAppletControllerForDebug"}, }; + // clang-format on + RegisterHandlers(functions); } diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp index e45cf6e20..60717afd9 100644 --- a/src/core/hle/service/am/applet_oe.cpp +++ b/src/core/hle/service/am/applet_oe.cpp @@ -14,6 +14,7 @@ class IApplicationProxy final : public ServiceFramework<IApplicationProxy> { public: explicit IApplicationProxy(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) : ServiceFramework("IApplicationProxy"), nvflinger(std::move(nvflinger)) { + // clang-format off static const FunctionInfo functions[] = { {0, &IApplicationProxy::GetCommonStateGetter, "GetCommonStateGetter"}, {1, &IApplicationProxy::GetSelfController, "GetSelfController"}, @@ -25,6 +26,8 @@ public: {20, &IApplicationProxy::GetApplicationFunctions, "GetApplicationFunctions"}, {1000, &IApplicationProxy::GetDebugFunctions, "GetDebugFunctions"}, }; + // clang-format on + RegisterHandlers(functions); } diff --git a/src/core/hle/service/am/idle.cpp b/src/core/hle/service/am/idle.cpp index 0e3088bc8..f814fe2c0 100644 --- a/src/core/hle/service/am/idle.cpp +++ b/src/core/hle/service/am/idle.cpp @@ -12,9 +12,9 @@ IdleSys::IdleSys() : ServiceFramework{"idle:sys"} { {0, nullptr, "GetAutoPowerDownEvent"}, {1, nullptr, "Unknown1"}, {2, nullptr, "Unknown2"}, - {3, nullptr, "Unknown3"}, - {4, nullptr, "Unknown4"}, - {5, nullptr, "Unknown5"}, + {3, nullptr, "SetHandlingContext"}, + {4, nullptr, "LoadAndApplySettings"}, + {5, nullptr, "ReportUserIsActive"}, }; // clang-format on diff --git a/src/core/hle/service/am/tcap.cpp b/src/core/hle/service/am/tcap.cpp new file mode 100644 index 000000000..a75cbdda8 --- /dev/null +++ b/src/core/hle/service/am/tcap.cpp @@ -0,0 +1,23 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/hle/service/am/tcap.h" + +namespace Service::AM { + +TCAP::TCAP() : ServiceFramework{"tcap"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, nullptr, "GetContinuousHighSkinTemperatureEvent"}, + {1, nullptr, "SetOperationMode"}, + {2, nullptr, "LoadAndApplySettings"}, + }; + // clang-format on + + RegisterHandlers(functions); +} + +TCAP::~TCAP() = default; + +} // namespace Service::AM diff --git a/src/core/hle/service/am/tcap.h b/src/core/hle/service/am/tcap.h new file mode 100644 index 000000000..2021b55d1 --- /dev/null +++ b/src/core/hle/service/am/tcap.h @@ -0,0 +1,17 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include "core/hle/service/service.h" + +namespace Service::AM { + +class TCAP final : public ServiceFramework<TCAP> { +public: + explicit TCAP(); + ~TCAP() override; +}; + +} // namespace Service::AM diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index b26593b4f..b06e65a77 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -26,7 +26,11 @@ constexpr s32 HID_JOYSTICK_MAX = 0x7fff; constexpr s32 HID_JOYSTICK_MIN = -0x7fff; constexpr std::size_t NPAD_OFFSET = 0x9A00; constexpr u32 BATTERY_FULL = 2; - +constexpr u32 NPAD_HANDHELD = 32; +constexpr u32 NPAD_UNKNOWN = 16; // TODO(ogniK): What is this? +constexpr u32 MAX_NPAD_ID = 7; +constexpr Controller_NPad::NPadControllerType PREFERRED_CONTROLLER = + Controller_NPad::NPadControllerType::JoyDual; constexpr std::array<u32, 10> npad_id_list{ 0, 1, 2, 3, 4, 5, 6, 7, 32, 16, }; @@ -121,7 +125,7 @@ void Controller_NPad::OnInit() { supported_npad_id_types.resize(npad_id_list.size()); std::memcpy(supported_npad_id_types.data(), npad_id_list.data(), npad_id_list.size() * sizeof(u32)); - AddNewController(NPadControllerType::JoyDual); + AddNewController(PREFERRED_CONTROLLER); } } @@ -218,6 +222,51 @@ void Controller_NPad::OnUpdate(u8* data, std::size_t data_len) { rstick_entry.x = static_cast<s32>(stick_r_x_f * HID_JOYSTICK_MAX); rstick_entry.y = static_cast<s32>(stick_r_y_f * HID_JOYSTICK_MAX); + if (controller_type == NPadControllerType::JoyLeft || + controller_type == NPadControllerType::JoyRight) { + if (npad.properties.is_horizontal) { + ControllerPadState state{}; + AnalogPosition temp_lstick_entry{}; + AnalogPosition temp_rstick_entry{}; + if (controller_type == NPadControllerType::JoyLeft) { + state.d_down.Assign(pad_state.d_left.Value()); + state.d_left.Assign(pad_state.d_up.Value()); + state.d_right.Assign(pad_state.d_down.Value()); + state.d_up.Assign(pad_state.d_right.Value()); + state.l.Assign(pad_state.l.Value() | pad_state.sl.Value()); + state.r.Assign(pad_state.r.Value() | pad_state.sr.Value()); + + state.zl.Assign(pad_state.zl.Value()); + state.plus.Assign(pad_state.minus.Value()); + + temp_lstick_entry = lstick_entry; + temp_rstick_entry = rstick_entry; + std::swap(temp_lstick_entry.x, temp_lstick_entry.y); + std::swap(temp_rstick_entry.x, temp_rstick_entry.y); + temp_lstick_entry.y *= -1; + } else if (controller_type == NPadControllerType::JoyRight) { + state.x.Assign(pad_state.a.Value()); + state.a.Assign(pad_state.b.Value()); + state.b.Assign(pad_state.y.Value()); + state.y.Assign(pad_state.b.Value()); + + state.l.Assign(pad_state.l.Value() | pad_state.sl.Value()); + state.r.Assign(pad_state.r.Value() | pad_state.sr.Value()); + state.zr.Assign(pad_state.zr.Value()); + state.plus.Assign(pad_state.plus.Value()); + + temp_lstick_entry = lstick_entry; + temp_rstick_entry = rstick_entry; + std::swap(temp_lstick_entry.x, temp_lstick_entry.y); + std::swap(temp_rstick_entry.x, temp_rstick_entry.y); + temp_rstick_entry.x *= -1; + } + pad_state.raw = state.raw; + lstick_entry = temp_lstick_entry; + rstick_entry = temp_rstick_entry; + } + } + auto& main_controller = npad.main_controller_states.npad[npad.main_controller_states.common.last_entry_index]; auto& handheld_entry = @@ -320,6 +369,16 @@ void Controller_NPad::SetSupportedNPadIdTypes(u8* data, std::size_t length) { supported_npad_id_types.clear(); supported_npad_id_types.resize(length / sizeof(u32)); std::memcpy(supported_npad_id_types.data(), data, length); + for (std::size_t i = 0; i < connected_controllers.size(); i++) { + auto& controller = connected_controllers[i]; + if (!controller.is_connected) { + continue; + } + if (!IsControllerSupported(PREFERRED_CONTROLLER)) { + controller.type = DecideBestController(PREFERRED_CONTROLLER); + InitNewlyAddedControler(i); + } + } } void Controller_NPad::GetSupportedNpadIdTypes(u32* data, std::size_t max_length) { @@ -351,11 +410,11 @@ void Controller_NPad::VibrateController(const std::vector<u32>& controller_ids, for (std::size_t i = 0; i < controller_ids.size(); i++) { std::size_t controller_pos = i; // Handheld controller conversion - if (controller_pos == 32) { + if (controller_pos == NPAD_HANDHELD) { controller_pos = 8; } // Unknown controller conversion - if (controller_pos == 16) { + if (controller_pos == NPAD_UNKNOWN) { controller_pos = 9; } if (connected_controllers[controller_pos].is_connected) { @@ -433,4 +492,128 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) { void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { can_controllers_vibrate = can_vibrate; } + +bool Controller_NPad::IsControllerSupported(NPadControllerType controller) const { + const bool support_handheld = + std::find(supported_npad_id_types.begin(), supported_npad_id_types.end(), NPAD_HANDHELD) != + supported_npad_id_types.end(); + if (controller == NPadControllerType::Handheld) { + // Handheld is not even a supported type, lets stop here + if (!support_handheld) { + return false; + } + // Handheld should not be supported in docked mode + if (Settings::values.use_docked_mode) { + return false; + } + + return true; + } + if (std::any_of(supported_npad_id_types.begin(), supported_npad_id_types.end(), + [](u32 npad_id) { return npad_id <= MAX_NPAD_ID; })) { + switch (controller) { + case NPadControllerType::ProController: + return style.pro_controller; + case NPadControllerType::JoyDual: + return style.joycon_dual; + case NPadControllerType::JoyLeft: + return style.joycon_left; + case NPadControllerType::JoyRight: + return style.joycon_right; + case NPadControllerType::Pokeball: + return style.pokeball; + default: + return false; + } + } + return false; +} + +Controller_NPad::NPadControllerType Controller_NPad::DecideBestController( + NPadControllerType priority) const { + if (IsControllerSupported(priority)) { + return priority; + } + const auto is_docked = Settings::values.use_docked_mode; + if (is_docked && priority == NPadControllerType::Handheld) { + priority = NPadControllerType::JoyDual; + if (IsControllerSupported(priority)) { + return priority; + } + } + std::vector<NPadControllerType> priority_list; + switch (priority) { + case NPadControllerType::ProController: + priority_list.push_back(NPadControllerType::JoyDual); + if (!is_docked) { + priority_list.push_back(NPadControllerType::Handheld); + } + priority_list.push_back(NPadControllerType::JoyLeft); + priority_list.push_back(NPadControllerType::JoyRight); + priority_list.push_back(NPadControllerType::Pokeball); + break; + case NPadControllerType::Handheld: + priority_list.push_back(NPadControllerType::JoyDual); + priority_list.push_back(NPadControllerType::ProController); + priority_list.push_back(NPadControllerType::JoyLeft); + priority_list.push_back(NPadControllerType::JoyRight); + priority_list.push_back(NPadControllerType::Pokeball); + break; + case NPadControllerType::JoyDual: + if (!is_docked) { + priority_list.push_back(NPadControllerType::Handheld); + } + priority_list.push_back(NPadControllerType::ProController); + priority_list.push_back(NPadControllerType::JoyLeft); + priority_list.push_back(NPadControllerType::JoyRight); + priority_list.push_back(NPadControllerType::Pokeball); + break; + case NPadControllerType::JoyLeft: + priority_list.push_back(NPadControllerType::JoyRight); + priority_list.push_back(NPadControllerType::JoyDual); + if (!is_docked) { + priority_list.push_back(NPadControllerType::Handheld); + } + priority_list.push_back(NPadControllerType::ProController); + priority_list.push_back(NPadControllerType::Pokeball); + break; + case NPadControllerType::JoyRight: + priority_list.push_back(NPadControllerType::JoyLeft); + priority_list.push_back(NPadControllerType::JoyDual); + if (!is_docked) { + priority_list.push_back(NPadControllerType::Handheld); + } + priority_list.push_back(NPadControllerType::ProController); + priority_list.push_back(NPadControllerType::Pokeball); + break; + case NPadControllerType::Pokeball: + priority_list.push_back(NPadControllerType::JoyLeft); + priority_list.push_back(NPadControllerType::JoyRight); + priority_list.push_back(NPadControllerType::JoyDual); + if (!is_docked) { + priority_list.push_back(NPadControllerType::Handheld); + } + priority_list.push_back(NPadControllerType::ProController); + break; + default: + priority_list.push_back(NPadControllerType::JoyDual); + if (!is_docked) { + priority_list.push_back(NPadControllerType::Handheld); + } + priority_list.push_back(NPadControllerType::ProController); + priority_list.push_back(NPadControllerType::JoyLeft); + priority_list.push_back(NPadControllerType::JoyRight); + priority_list.push_back(NPadControllerType::JoyDual); + } + + const auto iter = std::find_if(priority_list.begin(), priority_list.end(), + [this](auto type) { return IsControllerSupported(type); }); + if (iter == priority_list.end()) { + UNIMPLEMENTED_MSG("Could not find supported controller!"); + return priority; + } + + return *iter; +} + } // namespace Service::HID diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h index 7c0f93acf..ac86985ff 100644 --- a/src/core/hle/service/hid/controllers/npad.h +++ b/src/core/hle/service/hid/controllers/npad.h @@ -283,5 +283,7 @@ private: bool can_controllers_vibrate{true}; void InitNewlyAddedControler(std::size_t controller_idx); + bool IsControllerSupported(NPadControllerType controller) const; + NPadControllerType DecideBestController(NPadControllerType priority) const; }; } // namespace Service::HID diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index beb89218a..a9aa9ec78 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp @@ -827,6 +827,7 @@ public: {11, nullptr, "EnableJoyPollingReceiveMode"}, {12, nullptr, "DisableJoyPollingReceiveMode"}, {13, nullptr, "GetPollingData"}, + {14, nullptr, "SetStatusManagerType"}, }; // clang-format on diff --git a/src/core/hle/service/lbl/lbl.cpp b/src/core/hle/service/lbl/lbl.cpp index 7321584e1..164c57e18 100644 --- a/src/core/hle/service/lbl/lbl.cpp +++ b/src/core/hle/service/lbl/lbl.cpp @@ -18,35 +18,35 @@ public: explicit LBL() : ServiceFramework{"lbl"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "Unknown1"}, - {1, nullptr, "Unknown2"}, - {2, nullptr, "Unknown3"}, - {3, nullptr, "GetCurrentBacklightLevel"}, - {4, nullptr, "Unknown4"}, - {5, nullptr, "GetAlsComputedBacklightLevel"}, - {6, nullptr, "TurnOffBacklight"}, - {7, nullptr, "TurnOnBacklight"}, - {8, nullptr, "GetBacklightStatus"}, - {9, nullptr, "Unknown5"}, - {10, nullptr, "Unknown6"}, - {11, nullptr, "Unknown7"}, - {12, nullptr, "Unknown8"}, - {13, nullptr, "Unknown9"}, - {14, nullptr, "Unknown10"}, - {15, nullptr, "GetAutoBrightnessSetting"}, - {16, nullptr, "ReadRawLightSensor"}, - {17, nullptr, "Unknown11"}, - {18, nullptr, "Unknown12"}, - {19, nullptr, "Unknown13"}, - {20, nullptr, "Unknown14"}, - {21, nullptr, "Unknown15"}, - {22, nullptr, "Unknown16"}, - {23, nullptr, "Unknown17"}, - {24, nullptr, "Unknown18"}, - {25, nullptr, "Unknown19"}, + {0, nullptr, "SaveCurrentSetting"}, + {1, nullptr, "LoadCurrentSetting"}, + {2, nullptr, "SetCurrentBrightnessSetting"}, + {3, nullptr, "GetCurrentBrightnessSetting"}, + {4, nullptr, "ApplyCurrentBrightnessSettingToBacklight"}, + {5, nullptr, "GetBrightnessSettingAppliedToBacklight"}, + {6, nullptr, "SwitchBacklightOn"}, + {7, nullptr, "SwitchBacklightOff"}, + {8, nullptr, "GetBacklightSwitchStatus"}, + {9, nullptr, "EnableDimming"}, + {10, nullptr, "DisableDimming"}, + {11, nullptr, "IsDimmingEnabled"}, + {12, nullptr, "EnableAutoBrightnessControl"}, + {13, nullptr, "DisableAutoBrightnessControl"}, + {14, nullptr, "IsAutoBrightnessControlEnabled"}, + {15, nullptr, "SetAmbientLightSensorValue"}, + {16, nullptr, "GetAmbientLightSensorValue"}, + {17, nullptr, "SetBrightnessReflectionDelayLevel"}, + {18, nullptr, "GetBrightnessReflectionDelayLevel"}, + {19, nullptr, "SetCurrentBrightnessMapping"}, + {20, nullptr, "GetCurrentBrightnessMapping"}, + {21, nullptr, "SetCurrentAmbientLightSensorMapping"}, + {22, nullptr, "GetCurrentAmbientLightSensorMapping"}, + {23, nullptr, "IsAmbientLightSensorAvailable"}, + {24, nullptr, "SetCurrentBrightnessSettingForVrMode"}, + {25, nullptr, "GetCurrentBrightnessSettingForVrMode"}, {26, &LBL::EnableVrMode, "EnableVrMode"}, {27, &LBL::DisableVrMode, "DisableVrMode"}, - {28, &LBL::GetVrMode, "GetVrMode"}, + {28, &LBL::IsVrModeEnabled, "IsVrModeEnabled"}, }; // clang-format on @@ -72,7 +72,7 @@ private: LOG_DEBUG(Service_LBL, "called"); } - void GetVrMode(Kernel::HLERequestContext& ctx) { + void IsVrModeEnabled(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push(vr_mode_enabled); diff --git a/src/core/hle/service/npns/npns.cpp b/src/core/hle/service/npns/npns.cpp new file mode 100644 index 000000000..ccb6f9da9 --- /dev/null +++ b/src/core/hle/service/npns/npns.cpp @@ -0,0 +1,88 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> + +#include "core/hle/service/npns/npns.h" +#include "core/hle/service/service.h" +#include "core/hle/service/sm/sm.h" + +namespace Service::NPNS { + +class NPNS_S final : public ServiceFramework<NPNS_S> { +public: + explicit NPNS_S() : ServiceFramework{"npns:s"} { + // clang-format off + static const FunctionInfo functions[] = { + {1, nullptr, "ListenAll"}, + {2, nullptr, "ListenTo"}, + {3, nullptr, "Receive"}, + {4, nullptr, "ReceiveRaw"}, + {5, nullptr, "GetReceiveEvent"}, + {6, nullptr, "ListenUndelivered"}, + {7, nullptr, "GetStateChangeEVent"}, + {11, nullptr, "SubscribeTopic"}, + {12, nullptr, "UnsubscribeTopic"}, + {13, nullptr, "QueryIsTopicExist"}, + {21, nullptr, "CreateToken"}, + {22, nullptr, "CreateTokenWithApplicationId"}, + {23, nullptr, "DestroyToken"}, + {24, nullptr, "DestroyTokenWithApplicationId"}, + {25, nullptr, "QueryIsTokenValid"}, + {31, nullptr, "UploadTokenToBaaS"}, + {32, nullptr, "DestroyTokenForBaaS"}, + {33, nullptr, "CreateTokenForBaaS"}, + {34, nullptr, "SetBaaSDeviceAccountIdList"}, + {101, nullptr, "Suspend"}, + {102, nullptr, "Resume"}, + {103, nullptr, "GetState"}, + {104, nullptr, "GetStatistics"}, + {105, nullptr, "GetPlayReportRequestEvent"}, + {111, nullptr, "GetJid"}, + {112, nullptr, "CreateJid"}, + {113, nullptr, "DestroyJid"}, + {114, nullptr, "AttachJid"}, + {115, nullptr, "DetachJid"}, + {201, nullptr, "RequestChangeStateForceTimed"}, + {102, nullptr, "RequestChangeStateForceAsync"}, + }; + // clang-format on + + RegisterHandlers(functions); + } +}; + +class NPNS_U final : public ServiceFramework<NPNS_U> { +public: + explicit NPNS_U() : ServiceFramework{"npns:u"} { + // clang-format off + static const FunctionInfo functions[] = { + {1, nullptr, "ListenAll"}, + {2, nullptr, "ListenTo"}, + {3, nullptr, "Receive"}, + {4, nullptr, "ReceiveRaw"}, + {5, nullptr, "GetReceiveEvent"}, + {7, nullptr, "GetStateChangeEVent"}, + {21, nullptr, "CreateToken"}, + {23, nullptr, "DestroyToken"}, + {25, nullptr, "QueryIsTokenValid"}, + {26, nullptr, "ListenToMyApplicationId"}, + {101, nullptr, "Suspend"}, + {102, nullptr, "Resume"}, + {103, nullptr, "GetState"}, + {104, nullptr, "GetStatistics"}, + {111, nullptr, "GetJid"}, + }; + // clang-format on + + RegisterHandlers(functions); + } +}; + +void InstallInterfaces(SM::ServiceManager& sm) { + std::make_shared<NPNS_S>()->InstallAsService(sm); + std::make_shared<NPNS_U>()->InstallAsService(sm); +} + +} // namespace Service::NPNS diff --git a/src/core/hle/service/npns/npns.h b/src/core/hle/service/npns/npns.h new file mode 100644 index 000000000..861cd3e48 --- /dev/null +++ b/src/core/hle/service/npns/npns.h @@ -0,0 +1,15 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Service::SM { +class ServiceManager; +} + +namespace Service::NPNS { + +void InstallInterfaces(SM::ServiceManager& sm); + +} // namespace Service::NPNS diff --git a/src/core/hle/service/prepo/prepo.cpp b/src/core/hle/service/prepo/prepo.cpp index 6a9eccfb5..e4fcee9f8 100644 --- a/src/core/hle/service/prepo/prepo.cpp +++ b/src/core/hle/service/prepo/prepo.cpp @@ -14,20 +14,24 @@ public: explicit PlayReport(const char* name) : ServiceFramework{name} { // clang-format off static const FunctionInfo functions[] = { - {10100, nullptr, "SaveReport"}, - {10101, &PlayReport::SaveReportWithUser, "SaveReportWithUser"}, + {10100, nullptr, "SaveReportOld"}, + {10101, &PlayReport::SaveReportWithUserOld, "SaveReportWithUserOld"}, + {10102, nullptr, "SaveReport"}, + {10103, nullptr, "SaveReportWithUser"}, {10200, nullptr, "RequestImmediateTransmission"}, {10300, nullptr, "GetTransmissionStatus"}, {20100, nullptr, "SaveSystemReport"}, - {20200, nullptr, "SetOperationMode"}, {20101, nullptr, "SaveSystemReportWithUser"}, + {20200, nullptr, "SetOperationMode"}, {30100, nullptr, "ClearStorage"}, + {30200, nullptr, "ClearStatistics"}, + {30300, nullptr, "GetStorageUsage"}, + {30400, nullptr, "GetStatistics"}, + {30401, nullptr, "GetThroughputHistory"}, + {30500, nullptr, "GetLastUploadError"}, {40100, nullptr, "IsUserAgreementCheckEnabled"}, {40101, nullptr, "SetUserAgreementCheckEnabled"}, - {90100, nullptr, "GetStorageUsage"}, - {90200, nullptr, "GetStatistics"}, - {90201, nullptr, "GetThroughputHistory"}, - {90300, nullptr, "GetLastUploadError"}, + {90100, nullptr, "ReadAllReportFiles"}, }; // clang-format on @@ -35,7 +39,7 @@ public: } private: - void SaveReportWithUser(Kernel::HLERequestContext& ctx) { + void SaveReportWithUserOld(Kernel::HLERequestContext& ctx) { // TODO(ogniK): Do we want to add play report? LOG_WARNING(Service_PREPO, "(STUBBED) called"); @@ -46,6 +50,7 @@ private: void InstallInterfaces(SM::ServiceManager& service_manager) { std::make_shared<PlayReport>("prepo:a")->InstallAsService(service_manager); + std::make_shared<PlayReport>("prepo:a2")->InstallAsService(service_manager); std::make_shared<PlayReport>("prepo:m")->InstallAsService(service_manager); std::make_shared<PlayReport>("prepo:s")->InstallAsService(service_manager); std::make_shared<PlayReport>("prepo:u")->InstallAsService(service_manager); diff --git a/src/core/hle/service/ptm/psm.cpp b/src/core/hle/service/ptm/psm.cpp new file mode 100644 index 000000000..c2d5fda94 --- /dev/null +++ b/src/core/hle/service/ptm/psm.cpp @@ -0,0 +1,71 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <memory> + +#include "common/logging/log.h" +#include "core/hle/ipc_helpers.h" +#include "core/hle/service/ptm/psm.h" +#include "core/hle/service/service.h" +#include "core/hle/service/sm/sm.h" + +namespace Service::PSM { + +constexpr u32 BATTERY_FULLY_CHARGED = 100; // 100% Full +constexpr u32 BATTERY_CURRENTLY_CHARGING = 1; // Plugged into an official dock + +class PSM final : public ServiceFramework<PSM> { +public: + explicit PSM() : ServiceFramework{"psm"} { + // clang-format off + static const FunctionInfo functions[] = { + {0, &PSM::GetBatteryChargePercentage, "GetBatteryChargePercentage"}, + {1, &PSM::GetChargerType, "GetChargerType"}, + {2, nullptr, "EnableBatteryCharging"}, + {3, nullptr, "DisableBatteryCharging"}, + {4, nullptr, "IsBatteryChargingEnabled"}, + {5, nullptr, "AcquireControllerPowerSupply"}, + {6, nullptr, "ReleaseControllerPowerSupply"}, + {7, nullptr, "OpenSession"}, + {8, nullptr, "EnableEnoughPowerChargeEmulation"}, + {9, nullptr, "DisableEnoughPowerChargeEmulation"}, + {10, nullptr, "EnableFastBatteryCharging"}, + {11, nullptr, "DisableFastBatteryCharging"}, + {12, nullptr, "GetBatteryVoltageState"}, + {13, nullptr, "GetRawBatteryChargePercentage"}, + {14, nullptr, "IsEnoughPowerSupplied"}, + {15, nullptr, "GetBatteryAgePercentage"}, + {16, nullptr, "GetBatteryChargeInfoEvent"}, + {17, nullptr, "GetBatteryChargeInfoFields"}, + }; + // clang-format on + + RegisterHandlers(functions); + } + + ~PSM() override = default; + +private: + void GetBatteryChargePercentage(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PSM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(BATTERY_FULLY_CHARGED); + } + + void GetChargerType(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_PSM, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(BATTERY_CURRENTLY_CHARGING); + } +}; + +void InstallInterfaces(SM::ServiceManager& sm) { + std::make_shared<PSM>()->InstallAsService(sm); +} + +} // namespace Service::PSM diff --git a/src/core/hle/service/ptm/psm.h b/src/core/hle/service/ptm/psm.h new file mode 100644 index 000000000..a286793ae --- /dev/null +++ b/src/core/hle/service/ptm/psm.h @@ -0,0 +1,15 @@ +// Copyright 2018 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +namespace Service::SM { +class ServiceManager; +} + +namespace Service::PSM { + +void InstallInterfaces(SM::ServiceManager& sm); + +} // namespace Service::PSM diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index a225cb4cb..a4cf45267 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp @@ -22,7 +22,7 @@ #include "core/hle/service/apm/apm.h" #include "core/hle/service/arp/arp.h" #include "core/hle/service/audio/audio.h" -#include "core/hle/service/bcat/bcat.h" +#include "core/hle/service/bcat/module.h" #include "core/hle/service/bpc/bpc.h" #include "core/hle/service/btdrv/btdrv.h" #include "core/hle/service/btm/btm.h" @@ -48,15 +48,17 @@ #include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nifm/nifm.h" #include "core/hle/service/nim/nim.h" +#include "core/hle/service/npns/npns.h" #include "core/hle/service/ns/ns.h" #include "core/hle/service/nvdrv/nvdrv.h" #include "core/hle/service/nvflinger/nvflinger.h" #include "core/hle/service/pcie/pcie.h" -#include "core/hle/service/pctl/pctl.h" +#include "core/hle/service/pctl/module.h" #include "core/hle/service/pcv/pcv.h" #include "core/hle/service/pm/pm.h" #include "core/hle/service/prepo/prepo.h" #include "core/hle/service/psc/psc.h" +#include "core/hle/service/ptm/psm.h" #include "core/hle/service/service.h" #include "core/hle/service/set/settings.h" #include "core/hle/service/sm/sm.h" @@ -236,6 +238,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs) NFP::InstallInterfaces(*sm); NIFM::InstallInterfaces(*sm); NIM::InstallInterfaces(*sm); + NPNS::InstallInterfaces(*sm); NS::InstallInterfaces(*sm); Nvidia::InstallInterfaces(*sm, *nv_flinger); PCIe::InstallInterfaces(*sm); @@ -244,6 +247,7 @@ void Init(std::shared_ptr<SM::ServiceManager>& sm, FileSys::VfsFilesystem& vfs) PlayReport::InstallInterfaces(*sm); PM::InstallInterfaces(*sm); PSC::InstallInterfaces(*sm); + PSM::InstallInterfaces(*sm); Set::InstallInterfaces(*sm); Sockets::InstallInterfaces(*sm); SPL::InstallInterfaces(*sm); diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 8afd26fe9..bca014a4a 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -13,8 +13,7 @@ #include "video_core/renderer_base.h" #include "video_core/textures/texture.h" -namespace Tegra { -namespace Engines { +namespace Tegra::Engines { /// First register id that is actually a Macro call. constexpr u32 MacroRegistersStart = 0xE00; @@ -408,5 +407,4 @@ void Maxwell3D::ProcessClearBuffers() { rasterizer.Clear(); } -} // namespace Engines -} // namespace Tegra +} // namespace Tegra::Engines diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index c8af1c6b6..0e09a7ee5 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -643,8 +643,10 @@ public: u32 d3d_cull_mode; ComparisonOp depth_test_func; + float alpha_test_ref; + ComparisonOp alpha_test_func; - INSERT_PADDING_WORDS(0xB); + INSERT_PADDING_WORDS(0x9); struct { u32 separate_alpha; diff --git a/src/video_core/engines/maxwell_compute.cpp b/src/video_core/engines/maxwell_compute.cpp index 59e28b22d..8b5f08351 100644 --- a/src/video_core/engines/maxwell_compute.cpp +++ b/src/video_core/engines/maxwell_compute.cpp @@ -6,8 +6,7 @@ #include "core/core.h" #include "video_core/engines/maxwell_compute.h" -namespace Tegra { -namespace Engines { +namespace Tegra::Engines { void MaxwellCompute::WriteReg(u32 method, u32 value) { ASSERT_MSG(method < Regs::NUM_REGS, @@ -26,5 +25,4 @@ void MaxwellCompute::WriteReg(u32 method, u32 value) { } } -} // namespace Engines -} // namespace Tegra +} // namespace Tegra::Engines diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp index 103cd110e..b8a78cf82 100644 --- a/src/video_core/engines/maxwell_dma.cpp +++ b/src/video_core/engines/maxwell_dma.cpp @@ -7,8 +7,7 @@ #include "video_core/rasterizer_interface.h" #include "video_core/textures/decoders.h" -namespace Tegra { -namespace Engines { +namespace Tegra::Engines { MaxwellDMA::MaxwellDMA(VideoCore::RasterizerInterface& rasterizer, MemoryManager& memory_manager) : memory_manager(memory_manager), rasterizer{rasterizer} {} @@ -78,9 +77,9 @@ void MaxwellDMA::HandleCopy() { ASSERT(regs.exec.enable_2d == 1); - std::size_t copy_size = regs.x_count * regs.y_count; + const std::size_t copy_size = regs.x_count * regs.y_count; - const auto FlushAndInvalidate = [&](u32 src_size, u32 dst_size) { + const auto FlushAndInvalidate = [&](u32 src_size, u64 dst_size) { // TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated // copying. rasterizer.FlushRegion(source_cpu, src_size); @@ -91,14 +90,11 @@ void MaxwellDMA::HandleCopy() { rasterizer.InvalidateRegion(dest_cpu, dst_size); }; - u8* src_buffer = Memory::GetPointer(source_cpu); - u8* dst_buffer = Memory::GetPointer(dest_cpu); - if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) { ASSERT(regs.src_params.size_z == 1); // If the input is tiled and the output is linear, deswizzle the input and copy it over. - u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x; + const u32 src_bytes_per_pixel = regs.src_pitch / regs.src_params.size_x; FlushAndInvalidate(regs.src_pitch * regs.src_params.size_y, copy_size * src_bytes_per_pixel); @@ -111,7 +107,7 @@ void MaxwellDMA::HandleCopy() { ASSERT(regs.dst_params.size_z == 1); ASSERT(regs.src_pitch == regs.x_count); - u32 src_bpp = regs.src_pitch / regs.x_count; + const u32 src_bpp = regs.src_pitch / regs.x_count; FlushAndInvalidate(regs.src_pitch * regs.y_count, regs.dst_params.size_x * regs.dst_params.size_y * src_bpp); @@ -122,5 +118,4 @@ void MaxwellDMA::HandleCopy() { } } -} // namespace Engines -} // namespace Tegra +} // namespace Tegra::Engines diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h index e3d67ff87..6cd08d28b 100644 --- a/src/video_core/engines/shader_bytecode.h +++ b/src/video_core/engines/shader_bytecode.h @@ -214,7 +214,7 @@ enum class IMinMaxExchange : u64 { XHi = 3, }; -enum class VmadType : u64 { +enum class VideoType : u64 { Size16_Low = 0, Size16_High = 1, Size32 = 2, @@ -564,6 +564,10 @@ union Instruction { } fmul; union { + BitField<55, 1, u64> saturate; + } fmul32; + + union { BitField<48, 1, u64> is_signed; } shift; @@ -753,7 +757,6 @@ union Instruction { BitField<45, 2, PredOperation> op; BitField<47, 1, u64> ftz; BitField<48, 4, PredCondition> cond; - BitField<56, 1, u64> neg_b; } fsetp; union { @@ -780,6 +783,14 @@ union Instruction { } psetp; union { + BitField<43, 4, PredCondition> cond; + BitField<45, 2, PredOperation> op; + BitField<3, 3, u64> pred3; + BitField<0, 3, u64> pred0; + BitField<39, 3, u64> pred39; + } vsetp; + + union { BitField<12, 3, u64> pred12; BitField<15, 1, u64> neg_pred12; BitField<24, 2, PredOperation> cond; @@ -828,7 +839,6 @@ union Instruction { BitField<53, 1, u64> neg_b; BitField<54, 1, u64> abs_a; BitField<55, 1, u64> ftz; - BitField<56, 1, u64> neg_imm; } fset; union { @@ -1152,15 +1162,17 @@ union Instruction { union { BitField<48, 1, u64> signed_a; BitField<38, 1, u64> is_byte_chunk_a; - BitField<36, 2, VmadType> type_a; + BitField<36, 2, VideoType> type_a; BitField<36, 2, u64> byte_height_a; BitField<49, 1, u64> signed_b; BitField<50, 1, u64> use_register_b; BitField<30, 1, u64> is_byte_chunk_b; - BitField<28, 2, VmadType> type_b; + BitField<28, 2, VideoType> type_b; BitField<28, 2, u64> byte_height_b; + } video; + union { BitField<51, 2, VmadShr> shr; BitField<55, 1, u64> saturate; // Saturates the result (a * b + c) BitField<47, 1, u64> cc; @@ -1211,11 +1223,13 @@ public: KIL, SSY, SYNC, + BRK, DEPBAR, BFE_C, BFE_R, BFE_IMM, BRA, + PBK, LD_A, LD_C, ST_A, @@ -1234,6 +1248,7 @@ public: OUT_R, // Emit vertex/primitive ISBERD, VMAD, + VSETP, FFMA_IMM, // Fused Multiply and Add FFMA_CR, FFMA_RC, @@ -1372,7 +1387,7 @@ public: /// conditionally executed). static bool IsPredicatedInstruction(Id opcode) { // TODO(Subv): Add the rest of unpredicated instructions. - return opcode != Id::SSY; + return opcode != Id::SSY && opcode != Id::PBK; } class Matcher { @@ -1468,9 +1483,11 @@ private: #define INST(bitstring, op, type, name) Detail::GetMatcher(bitstring, op, type, name) INST("111000110011----", Id::KIL, Type::Flow, "KIL"), INST("111000101001----", Id::SSY, Type::Flow, "SSY"), + INST("111000101010----", Id::PBK, Type::Flow, "PBK"), INST("111000100100----", Id::BRA, Type::Flow, "BRA"), + INST("1111000011111---", Id::SYNC, Type::Flow, "SYNC"), + INST("111000110100---", Id::BRK, Type::Flow, "BRK"), INST("1111000011110---", Id::DEPBAR, Type::Synch, "DEPBAR"), - INST("1111000011111---", Id::SYNC, Type::Synch, "SYNC"), INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"), INST("1110111110010---", Id::LD_C, Type::Memory, "LD_C"), INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"), @@ -1489,6 +1506,7 @@ private: INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"), INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"), INST("01011111--------", Id::VMAD, Type::Trivial, "VMAD"), + INST("0101000011110---", Id::VSETP, Type::Trivial, "VSETP"), INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"), INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"), INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"), @@ -1608,4 +1626,4 @@ private: } }; -} // namespace Tegra::Shader +} // namespace Tegra::Shader
\ No newline at end of file diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index 3daccf82f..be51c5215 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp @@ -570,10 +570,11 @@ void RasterizerOpenGL::DrawArrays() { SyncBlendState(); SyncLogicOpState(); SyncCullMode(); - SyncAlphaTest(); SyncScissorTest(); + // Alpha Testing is synced on shaders. SyncTransformFeedback(); SyncPointState(); + CheckAlphaTests(); // TODO(bunnei): Sync framebuffer_scale uniform here // TODO(bunnei): Sync scissorbox uniform(s) here @@ -1007,17 +1008,6 @@ void RasterizerOpenGL::SyncLogicOpState() { state.logic_op.operation = MaxwellToGL::LogicOp(regs.logic_op.operation); } -void RasterizerOpenGL::SyncAlphaTest() { - const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; - - // TODO(Rodrigo): Alpha testing is a legacy OpenGL feature, but it can be - // implemented with a test+discard in fragment shaders. - if (regs.alpha_test_enabled != 0) { - LOG_CRITICAL(Render_OpenGL, "Alpha testing is not implemented"); - UNREACHABLE(); - } -} - void RasterizerOpenGL::SyncScissorTest() { const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; @@ -1052,4 +1042,15 @@ void RasterizerOpenGL::SyncPointState() { state.point.size = regs.point_size == 0 ? 1 : regs.point_size; } +void RasterizerOpenGL::CheckAlphaTests() { + const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs; + + if (regs.alpha_test_enabled != 0 && regs.rt_control.count > 1) { + LOG_CRITICAL( + Render_OpenGL, + "Alpha Testing is enabled with Multiple Render Targets, this behavior is undefined."); + UNREACHABLE(); + } +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index b1f7ccc7e..0e90a31f5 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h @@ -162,9 +162,6 @@ private: /// Syncs the LogicOp state to match the guest state void SyncLogicOpState(); - /// Syncs the alpha test state to match the guest state - void SyncAlphaTest(); - /// Syncs the scissor test state to match the guest state void SyncScissorTest(); @@ -174,6 +171,9 @@ private: /// Syncs the point state to match the guest state void SyncPointState(); + /// Check asserts for alpha testing. + void CheckAlphaTests(); + bool has_ARB_direct_state_access = false; bool has_ARB_multi_bind = false; bool has_ARB_separate_shader_objects = false; diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp index a427353e9..fe4d1bd83 100644 --- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp +++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp @@ -163,10 +163,11 @@ private: const ExitMethod jmp = Scan(target, end, labels); return exit_method = ParallelExit(no_jmp, jmp); } - case OpCode::Id::SSY: { - // The SSY instruction uses a similar encoding as the BRA instruction. + case OpCode::Id::SSY: + case OpCode::Id::PBK: { + // The SSY and PBK use a similar encoding as the BRA instruction. ASSERT_MSG(instr.bra.constant_buffer == 0, - "Constant buffer SSY is not supported"); + "Constant buffer branching is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); labels.insert(target); // Continue scanning for an exit method. @@ -378,8 +379,8 @@ public: * @param reg The destination register to use. * @param elem The element to use for the operation. * @param value The code representing the value to assign. Type has to be half float. - * @param type Half float kind of assignment. - * @param dest_num_components Number of components in the destionation. + * @param merge Half float kind of assignment. + * @param dest_num_components Number of components in the destination. * @param value_num_components Number of components in the value. * @param is_saturated Optional, when True, saturates the provided value. * @param dest_elem Optional, the destination element to use for the operation. @@ -422,6 +423,7 @@ public: * @param reg The destination register to use. * @param elem The element to use for the operation. * @param attribute The input attribute to use as the source value. + * @param input_mode The input mode. * @param vertex The register that decides which vertex to read from (used in GS). */ void SetRegisterToInputAttibute(const Register& reg, u64 elem, Attribute::Index attribute, @@ -951,7 +953,7 @@ private: // Can't assign to the constant predicate. ASSERT(pred != static_cast<u64>(Pred::UnusedIndex)); - const std::string variable = 'p' + std::to_string(pred) + '_' + suffix; + std::string variable = 'p' + std::to_string(pred) + '_' + suffix; shader.AddLine(variable + " = " + value + ';'); declr_predicates.insert(std::move(variable)); } @@ -1058,7 +1060,7 @@ private: /* * Transforms the input string GLSL operand into an unpacked half float pair. * @note This function returns a float type pair instead of a half float pair. This is because - * real half floats are not standarized in GLSL but unpackHalf2x16 (which returns a vec2) is. + * real half floats are not standardized in GLSL but unpackHalf2x16 (which returns a vec2) is. * @param operand Input operand. It has to be an unsigned integer. * @param type How to unpack the unsigned integer to a half float pair. * @param abs Get the absolute value of unpacked half floats. @@ -1232,27 +1234,27 @@ private: } /* - * Emits code to push the input target address to the SSY address stack, incrementing the stack + * Emits code to push the input target address to the flow address stack, incrementing the stack * top. */ - void EmitPushToSSYStack(u32 target) { + void EmitPushToFlowStack(u32 target) { shader.AddLine('{'); ++shader.scope; - shader.AddLine("ssy_stack[ssy_stack_top] = " + std::to_string(target) + "u;"); - shader.AddLine("ssy_stack_top++;"); + shader.AddLine("flow_stack[flow_stack_top] = " + std::to_string(target) + "u;"); + shader.AddLine("flow_stack_top++;"); --shader.scope; shader.AddLine('}'); } /* - * Emits code to pop an address from the SSY address stack, setting the jump address to the + * Emits code to pop an address from the flow address stack, setting the jump address to the * popped address and decrementing the stack top. */ - void EmitPopFromSSYStack() { + void EmitPopFromFlowStack() { shader.AddLine('{'); ++shader.scope; - shader.AddLine("ssy_stack_top--;"); - shader.AddLine("jmp_to = ssy_stack[ssy_stack_top];"); + shader.AddLine("flow_stack_top--;"); + shader.AddLine("jmp_to = flow_stack[flow_stack_top];"); shader.AddLine("break;"); --shader.scope; shader.AddLine('}'); @@ -1264,9 +1266,29 @@ private: ASSERT_MSG(header.ps.omap.sample_mask == 0, "Samplemask write is unimplemented"); + shader.AddLine("if (alpha_test[0] != 0) {"); + ++shader.scope; + // We start on the register containing the alpha value in the first RT. + u32 current_reg = 3; + for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets; + ++render_target) { + // TODO(Blinkhawk): verify the behavior of alpha testing on hardware when + // multiple render targets are used. + if (header.ps.IsColorComponentOutputEnabled(render_target, 0) || + header.ps.IsColorComponentOutputEnabled(render_target, 1) || + header.ps.IsColorComponentOutputEnabled(render_target, 2) || + header.ps.IsColorComponentOutputEnabled(render_target, 3)) { + shader.AddLine(fmt::format("if (!AlphaFunc({})) discard;", + regs.GetRegisterAsFloat(current_reg))); + current_reg += 4; + } + } + --shader.scope; + shader.AddLine('}'); + // Write the color outputs using the data in the shader registers, disabled // rendertargets/components are skipped in the register assignment. - u32 current_reg = 0; + current_reg = 0; for (u32 render_target = 0; render_target < Maxwell3D::Regs::NumRenderTargets; ++render_target) { // TODO(Subv): Figure out how dual-source blending is configured in the Switch. @@ -1290,6 +1312,63 @@ private: } } + /// Unpacks a video instruction operand (e.g. VMAD). + std::string GetVideoOperand(const std::string& op, bool is_chunk, bool is_signed, + Tegra::Shader::VideoType type, u64 byte_height) { + const std::string value = [&]() { + if (!is_chunk) { + const auto offset = static_cast<u32>(byte_height * 8); + return "((" + op + " >> " + std::to_string(offset) + ") & 0xff)"; + } + const std::string zero = "0"; + + switch (type) { + case Tegra::Shader::VideoType::Size16_Low: + return '(' + op + " & 0xffff)"; + case Tegra::Shader::VideoType::Size16_High: + return '(' + op + " >> 16)"; + case Tegra::Shader::VideoType::Size32: + // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when + // this type is used (1 * 1 + 0 == 0x5b800000). Until a better + // explanation is found: assert. + UNIMPLEMENTED(); + return zero; + case Tegra::Shader::VideoType::Invalid: + UNREACHABLE_MSG("Invalid instruction encoding"); + return zero; + default: + UNREACHABLE(); + return zero; + } + }(); + + if (is_signed) { + return "int(" + value + ')'; + } + return value; + }; + + /// Gets the A operand for a video instruction. + std::string GetVideoOperandA(Instruction instr) { + return GetVideoOperand(regs.GetRegisterAsInteger(instr.gpr8, 0, false), + instr.video.is_byte_chunk_a != 0, instr.video.signed_a, + instr.video.type_a, instr.video.byte_height_a); + } + + /// Gets the B operand for a video instruction. + std::string GetVideoOperandB(Instruction instr) { + if (instr.video.use_register_b) { + return GetVideoOperand(regs.GetRegisterAsInteger(instr.gpr20, 0, false), + instr.video.is_byte_chunk_b != 0, instr.video.signed_b, + instr.video.type_b, instr.video.byte_height_b); + } else { + return '(' + + std::to_string(instr.video.signed_b ? static_cast<s16>(instr.alu.GetImm20_16()) + : instr.alu.GetImm20_16()) + + ')'; + } + } + /** * Compiles a single instruction from Tegra to GLSL. * @param offset the offset of the Tegra shader instruction. @@ -1459,9 +1538,10 @@ private: break; } case OpCode::Id::FMUL32_IMM: { - regs.SetRegisterToFloat( - instr.gpr0, 0, - regs.GetRegisterAsFloat(instr.gpr8) + " * " + GetImmediate32(instr), 1, 1); + regs.SetRegisterToFloat(instr.gpr0, 0, + regs.GetRegisterAsFloat(instr.gpr8) + " * " + + GetImmediate32(instr), + 1, 1, instr.fmul32.saturate); break; } case OpCode::Id::FADD32I: { @@ -2736,20 +2816,13 @@ private: break; } case OpCode::Type::FloatSetPredicate: { - std::string op_a = instr.fsetp.neg_a ? "-" : ""; - op_a += regs.GetRegisterAsFloat(instr.gpr8); - - if (instr.fsetp.abs_a) { - op_a = "abs(" + op_a + ')'; - } + const std::string op_a = + GetOperandAbsNeg(regs.GetRegisterAsFloat(instr.gpr8), instr.fsetp.abs_a != 0, + instr.fsetp.neg_a != 0); - std::string op_b{}; + std::string op_b; if (instr.is_b_imm) { - if (instr.fsetp.neg_b) { - // Only the immediate version of fsetp has a neg_b bit. - op_b += '-'; - } op_b += '(' + GetImmediate19(instr) + ')'; } else { if (instr.is_b_gpr) { @@ -2945,33 +3018,24 @@ private: break; } case OpCode::Type::FloatSet: { - std::string op_a = instr.fset.neg_a ? "-" : ""; - op_a += regs.GetRegisterAsFloat(instr.gpr8); - - if (instr.fset.abs_a) { - op_a = "abs(" + op_a + ')'; - } + const std::string op_a = GetOperandAbsNeg(regs.GetRegisterAsFloat(instr.gpr8), + instr.fset.abs_a != 0, instr.fset.neg_a != 0); - std::string op_b = instr.fset.neg_b ? "-" : ""; + std::string op_b; if (instr.is_b_imm) { const std::string imm = GetImmediate19(instr); - if (instr.fset.neg_imm) - op_b += "(-" + imm + ')'; - else - op_b += imm; + op_b = imm; } else { if (instr.is_b_gpr) { - op_b += regs.GetRegisterAsFloat(instr.gpr20); + op_b = regs.GetRegisterAsFloat(instr.gpr20); } else { - op_b += regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, - GLSLRegister::Type::Float); + op_b = regs.GetUniform(instr.cbuf34.index, instr.cbuf34.offset, + GLSLRegister::Type::Float); } } - if (instr.fset.abs_b) { - op_b = "abs(" + op_b + ')'; - } + op_b = GetOperandAbsNeg(op_b, instr.fset.abs_b != 0, instr.fset.neg_b != 0); // The fset instruction sets a register to 1.0 or -1 (depending on the bf bit) if the // condition is true, and to 0 otherwise. @@ -3279,16 +3343,32 @@ private: // The SSY opcode tells the GPU where to re-converge divergent execution paths, it // sets the target of the jump that the SYNC instruction will make. The SSY opcode // has a similar structure to the BRA opcode. - ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer SSY is not supported"); + ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer flow is not supported"); + + const u32 target = offset + instr.bra.GetBranchTarget(); + EmitPushToFlowStack(target); + break; + } + case OpCode::Id::PBK: { + // PBK pushes to a stack the address where BRK will jump to. This shares stack with + // SSY but using SYNC on a PBK address will kill the shader execution. We don't + // emulate this because it's very unlikely a driver will emit such invalid shader. + ASSERT_MSG(instr.bra.constant_buffer == 0, "Constant buffer PBK is not supported"); const u32 target = offset + instr.bra.GetBranchTarget(); - EmitPushToSSYStack(target); + EmitPushToFlowStack(target); break; } case OpCode::Id::SYNC: { // The SYNC opcode jumps to the address previously set by the SSY opcode ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always); - EmitPopFromSSYStack(); + EmitPopFromFlowStack(); + break; + } + case OpCode::Id::BRK: { + // The BRK opcode jumps to the address previously set by the PBK opcode + ASSERT(instr.flow.cond == Tegra::Shader::FlowCondition::Always); + EmitPopFromFlowStack(); break; } case OpCode::Id::DEPBAR: { @@ -3298,87 +3378,51 @@ private: break; } case OpCode::Id::VMAD: { - const bool signed_a = instr.vmad.signed_a == 1; - const bool signed_b = instr.vmad.signed_b == 1; - const bool result_signed = signed_a || signed_b; - boost::optional<std::string> forced_result; - - auto Unpack = [&](const std::string& op, bool is_chunk, bool is_signed, - Tegra::Shader::VmadType type, u64 byte_height) { - const std::string value = [&]() { - if (!is_chunk) { - const auto offset = static_cast<u32>(byte_height * 8); - return "((" + op + " >> " + std::to_string(offset) + ") & 0xff)"; - } - const std::string zero = "0"; - - switch (type) { - case Tegra::Shader::VmadType::Size16_Low: - return '(' + op + " & 0xffff)"; - case Tegra::Shader::VmadType::Size16_High: - return '(' + op + " >> 16)"; - case Tegra::Shader::VmadType::Size32: - // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when - // this type is used (1 * 1 + 0 == 0x5b800000). Until a better - // explanation is found: assert. - UNREACHABLE_MSG("Unimplemented"); - return zero; - case Tegra::Shader::VmadType::Invalid: - // Note(Rodrigo): This flag is invalid according to nvdisasm. From my - // testing (even though it's invalid) this makes the whole instruction - // assign zero to target register. - forced_result = boost::make_optional(zero); - return zero; - default: - UNREACHABLE(); - return zero; - } - }(); - - if (is_signed) { - return "int(" + value + ')'; - } - return value; - }; - - const std::string op_a = Unpack(regs.GetRegisterAsInteger(instr.gpr8, 0, false), - instr.vmad.is_byte_chunk_a != 0, signed_a, - instr.vmad.type_a, instr.vmad.byte_height_a); - - std::string op_b; - if (instr.vmad.use_register_b) { - op_b = Unpack(regs.GetRegisterAsInteger(instr.gpr20, 0, false), - instr.vmad.is_byte_chunk_b != 0, signed_b, instr.vmad.type_b, - instr.vmad.byte_height_b); - } else { - op_b = '(' + - std::to_string(signed_b ? static_cast<s16>(instr.alu.GetImm20_16()) - : instr.alu.GetImm20_16()) + - ')'; - } - + const bool result_signed = instr.video.signed_a == 1 || instr.video.signed_b == 1; + const std::string op_a = GetVideoOperandA(instr); + const std::string op_b = GetVideoOperandB(instr); const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39, 0, result_signed); - std::string result; - if (forced_result) { - result = *forced_result; - } else { - result = '(' + op_a + " * " + op_b + " + " + op_c + ')'; + std::string result = '(' + op_a + " * " + op_b + " + " + op_c + ')'; - switch (instr.vmad.shr) { - case Tegra::Shader::VmadShr::Shr7: - result = '(' + result + " >> 7)"; - break; - case Tegra::Shader::VmadShr::Shr15: - result = '(' + result + " >> 15)"; - break; - } + switch (instr.vmad.shr) { + case Tegra::Shader::VmadShr::Shr7: + result = '(' + result + " >> 7)"; + break; + case Tegra::Shader::VmadShr::Shr15: + result = '(' + result + " >> 15)"; + break; } + regs.SetRegisterToInteger(instr.gpr0, result_signed, 1, result, 1, 1, instr.vmad.saturate == 1, 0, Register::Size::Word, instr.vmad.cc); break; } + case OpCode::Id::VSETP: { + const std::string op_a = GetVideoOperandA(instr); + const std::string op_b = GetVideoOperandB(instr); + + // We can't use the constant predicate as destination. + ASSERT(instr.vsetp.pred3 != static_cast<u64>(Pred::UnusedIndex)); + + const std::string second_pred = GetPredicateCondition(instr.vsetp.pred39, false); + + const std::string combiner = GetPredicateCombiner(instr.vsetp.op); + + const std::string predicate = GetPredicateComparison(instr.vsetp.cond, op_a, op_b); + // Set the primary predicate to the result of Predicate OP SecondPredicate + SetPredicate(instr.vsetp.pred3, + '(' + predicate + ") " + combiner + " (" + second_pred + ')'); + + if (instr.vsetp.pred0 != static_cast<u64>(Pred::UnusedIndex)) { + // Set the secondary predicate to the result of !Predicate OP SecondPredicate, + // if enabled + SetPredicate(instr.vsetp.pred0, + "!(" + predicate + ") " + combiner + " (" + second_pred + ')'); + } + break; + } default: { LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName()); UNREACHABLE(); @@ -3442,11 +3486,11 @@ private: labels.insert(subroutine.begin); shader.AddLine("uint jmp_to = " + std::to_string(subroutine.begin) + "u;"); - // TODO(Subv): Figure out the actual depth of the SSY stack, for now it seems - // unlikely that shaders will use 20 nested SSYs. - constexpr u32 SSY_STACK_SIZE = 20; - shader.AddLine("uint ssy_stack[" + std::to_string(SSY_STACK_SIZE) + "];"); - shader.AddLine("uint ssy_stack_top = 0u;"); + // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems + // unlikely that shaders will use 20 nested SSYs and PBKs. + constexpr u32 FLOW_STACK_SIZE = 20; + shader.AddLine("uint flow_stack[" + std::to_string(FLOW_STACK_SIZE) + "];"); + shader.AddLine("uint flow_stack_top = 0u;"); shader.AddLine("while (true) {"); ++shader.scope; @@ -3513,7 +3557,7 @@ private: // Declarations std::set<std::string> declr_predicates; -}; // namespace Decompiler +}; // namespace OpenGL::GLShader::Decompiler std::string GetCommonDeclarations() { return fmt::format("#define MAX_CONSTBUFFER_ELEMENTS {}\n", diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index ecbc9d8ed..e883ffb1d 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp @@ -29,6 +29,7 @@ layout(std140) uniform vs_config { vec4 viewport_flip; uvec4 instance_id; uvec4 flip_stage; + uvec4 alpha_test; }; )"; @@ -105,6 +106,7 @@ layout (std140) uniform gs_config { vec4 viewport_flip; uvec4 instance_id; uvec4 flip_stage; + uvec4 alpha_test; }; void main() { @@ -142,8 +144,33 @@ layout (std140) uniform fs_config { vec4 viewport_flip; uvec4 instance_id; uvec4 flip_stage; + uvec4 alpha_test; }; +bool AlphaFunc(in float value) { + float ref = uintBitsToFloat(alpha_test[2]); + switch (alpha_test[1]) { + case 1: + return false; + case 2: + return value < ref; + case 3: + return value == ref; + case 4: + return value <= ref; + case 5: + return value > ref; + case 6: + return value != ref; + case 7: + return value >= ref; + case 8: + return true; + default: + return false; + } +} + void main() { exec_fragment(); } @@ -152,4 +179,4 @@ void main() { out += program.first; return {out, program.second}; } -} // namespace OpenGL::GLShader
\ No newline at end of file +} // namespace OpenGL::GLShader diff --git a/src/video_core/renderer_opengl/gl_shader_manager.cpp b/src/video_core/renderer_opengl/gl_shader_manager.cpp index 010857ec6..8b8869ecb 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.cpp +++ b/src/video_core/renderer_opengl/gl_shader_manager.cpp @@ -16,6 +16,17 @@ void MaxwellUniformData::SetFromRegs(const Maxwell3D::State::ShaderStageInfo& sh viewport_flip[0] = regs.viewport_transform[0].scale_x < 0.0 ? -1.0f : 1.0f; viewport_flip[1] = regs.viewport_transform[0].scale_y < 0.0 ? -1.0f : 1.0f; + u32 func = static_cast<u32>(regs.alpha_test_func); + // Normalize the gl variants of opCompare to be the same as the normal variants + u32 op_gl_variant_base = static_cast<u32>(Tegra::Engines::Maxwell3D::Regs::ComparisonOp::Never); + if (func >= op_gl_variant_base) { + func = func - op_gl_variant_base + 1U; + } + + alpha_test.enabled = regs.alpha_test_enabled; + alpha_test.func = func; + alpha_test.ref = regs.alpha_test_ref; + // We only assign the instance to the first component of the vector, the rest is just padding. instance_id[0] = state.current_instance; diff --git a/src/video_core/renderer_opengl/gl_shader_manager.h b/src/video_core/renderer_opengl/gl_shader_manager.h index b3a191cf2..36fe1f04c 100644 --- a/src/video_core/renderer_opengl/gl_shader_manager.h +++ b/src/video_core/renderer_opengl/gl_shader_manager.h @@ -22,8 +22,14 @@ struct MaxwellUniformData { alignas(16) GLvec4 viewport_flip; alignas(16) GLuvec4 instance_id; alignas(16) GLuvec4 flip_stage; + struct alignas(16) { + GLuint enabled; + GLuint func; + GLfloat ref; + GLuint padding; + } alpha_test; }; -static_assert(sizeof(MaxwellUniformData) == 48, "MaxwellUniformData structure size is incorrect"); +static_assert(sizeof(MaxwellUniformData) == 64, "MaxwellUniformData structure size is incorrect"); static_assert(sizeof(MaxwellUniformData) < 16384, "MaxwellUniformData structure must be less than 16kb as per the OpenGL spec"); diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h index 3c3bcaae4..0f6dcab2b 100644 --- a/src/video_core/renderer_opengl/maxwell_to_gl.h +++ b/src/video_core/renderer_opengl/maxwell_to_gl.h @@ -82,8 +82,20 @@ inline GLenum VertexType(Maxwell::VertexAttribute attrib) { return {}; } - case Maxwell::VertexAttribute::Type::Float: - return GL_FLOAT; + case Maxwell::VertexAttribute::Type::Float: { + switch (attrib.size) { + case Maxwell::VertexAttribute::Size::Size_16: + case Maxwell::VertexAttribute::Size::Size_16_16: + case Maxwell::VertexAttribute::Size::Size_16_16_16: + case Maxwell::VertexAttribute::Size::Size_16_16_16_16: + return GL_HALF_FLOAT; + case Maxwell::VertexAttribute::Size::Size_32: + case Maxwell::VertexAttribute::Size::Size_32_32: + case Maxwell::VertexAttribute::Size::Size_32_32_32: + case Maxwell::VertexAttribute::Size::Size_32_32_32_32: + return GL_FLOAT; + } + } } LOG_CRITICAL(Render_OpenGL, "Unimplemented vertex type={}", attrib.TypeString()); diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt index 93878f8ab..01f2d129d 100644 --- a/src/web_service/CMakeLists.txt +++ b/src/web_service/CMakeLists.txt @@ -12,5 +12,5 @@ create_target_directory_groups(web_service) get_directory_property(OPENSSL_LIBS DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl DEFINITION OPENSSL_LIBS) -target_compile_definitions(web_service PUBLIC -DCPPHTTPLIB_OPENSSL_SUPPORT) +target_compile_definitions(web_service PRIVATE -DCPPHTTPLIB_OPENSSL_SUPPORT) target_link_libraries(web_service PRIVATE common json-headers ${OPENSSL_LIBS} httplib lurlparser) diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 0d24e8eb0..9379d9110 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -121,7 +121,7 @@ target_link_libraries(yuzu PRIVATE Boost::boost glad Qt5::OpenGL Qt5::Widgets) target_link_libraries(yuzu PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads) if (YUZU_ENABLE_COMPATIBILITY_REPORTING) - add_definitions(-DYUZU_ENABLE_COMPATIBILITY_REPORTING) + target_compile_definitions(yuzu PRIVATE -DYUZU_ENABLE_COMPATIBILITY_REPORTING) endif() if (USE_DISCORD_PRESENCE) diff --git a/src/yuzu/debugger/wait_tree.cpp b/src/yuzu/debugger/wait_tree.cpp index 7403e9ccd..0c831c9f4 100644 --- a/src/yuzu/debugger/wait_tree.cpp +++ b/src/yuzu/debugger/wait_tree.cpp @@ -9,8 +9,8 @@ #include "core/core.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/handle_table.h" -#include "core/hle/kernel/kernel.h" #include "core/hle/kernel/mutex.h" +#include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/thread.h" #include "core/hle/kernel/timer.h" @@ -83,7 +83,7 @@ QString WaitTreeText::GetText() const { } WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address) : mutex_address(mutex_address) { - auto& handle_table = Core::System::GetInstance().Kernel().HandleTable(); + const auto& handle_table = Core::CurrentProcess()->GetHandleTable(); mutex_value = Memory::Read32(mutex_address); owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Mutex::MutexOwnerMask); diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 9851f507d..dffd9c788 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -97,18 +97,24 @@ <addaction name="action_Show_Status_Bar"/> <addaction name="menu_View_Debugging"/> </widget> + <widget class ="QMenu" name="menu_Tools"> + <property name="title"> + <string>Tools</string> + </property> + <addaction name="action_Rederive" /> + </widget> <widget class="QMenu" name="menu_Help"> <property name="title"> <string>&Help</string> </property> <addaction name="action_Report_Compatibility"/> <addaction name="separator"/> - <addaction name="action_Rederive"/> <addaction name="action_About"/> </widget> <addaction name="menu_File"/> <addaction name="menu_Emulation"/> <addaction name="menu_View"/> + <addaction name="menu_Tools" /> <addaction name="menu_Help"/> </widget> <action name="action_Install_File_NAND"> |