diff options
47 files changed, 1413 insertions, 563 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e98b557e..918cf5372 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,21 +23,21 @@ option(ENABLE_CUBEB "Enables the cubeb audio backend" ON) option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF) -if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit) +if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit) message(STATUS "Copying pre-commit hook") file(COPY hooks/pre-commit - DESTINATION ${CMAKE_SOURCE_DIR}/.git/hooks) + DESTINATION ${PROJECT_SOURCE_DIR}/.git/hooks) endif() # Sanity check : Check that all submodules are present # ======================================================================= function(check_submodules_present) - file(READ "${CMAKE_SOURCE_DIR}/.gitmodules" gitmodules) + file(READ "${PROJECT_SOURCE_DIR}/.gitmodules" gitmodules) string(REGEX MATCHALL "path *= *[^ \t\r\n]*" gitmodules ${gitmodules}) foreach(module ${gitmodules}) string(REGEX REPLACE "path *= *" "" module ${module}) - if (NOT EXISTS "${CMAKE_SOURCE_DIR}/${module}/.git") + if (NOT EXISTS "${PROJECT_SOURCE_DIR}/${module}/.git") message(FATAL_ERROR "Git submodule ${module} not found. " "Please run: git submodule update --init --recursive") endif() @@ -45,17 +45,17 @@ function(check_submodules_present) endfunction() check_submodules_present() -configure_file(${CMAKE_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qrc - ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc +configure_file(${PROJECT_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qrc + ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc COPYONLY) -if (ENABLE_COMPATIBILITY_LIST_DOWNLOAD AND NOT EXISTS ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) +if (ENABLE_COMPATIBILITY_LIST_DOWNLOAD AND NOT EXISTS ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) message(STATUS "Downloading compatibility list for yuzu...") file(DOWNLOAD https://api.yuzu-emu.org/gamedb/ - "${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json" SHOW_PROGRESS) + "${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json" SHOW_PROGRESS) endif() -if (NOT EXISTS ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) - file(WRITE ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json "") +if (NOT EXISTS ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) + file(WRITE ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json "") endif() # Detect current compilation architecture and create standard definitions @@ -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 # ====================== @@ -189,13 +185,13 @@ find_package(Boost 1.63.0 QUIET) if (NOT Boost_FOUND) message(STATUS "Boost 1.63.0 or newer not found, falling back to externals") - set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/boost") + set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost") set(Boost_NO_SYSTEM_PATHS OFF) find_package(Boost QUIET REQUIRED) endif() # Output binaries to bin/ -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) # Prefer the -pthread flag on Linux. set(THREADS_PREFER_PTHREAD_FLAG ON) @@ -264,7 +260,7 @@ if (YUZU_USE_BUNDLED_UNICORN) endif() set(UNICORN_FOUND YES) - set(UNICORN_PREFIX ${CMAKE_SOURCE_DIR}/externals/unicorn) + set(UNICORN_PREFIX ${PROJECT_SOURCE_DIR}/externals/unicorn) set(LIBUNICORN_LIBRARY "${UNICORN_PREFIX}/${UNICORN_LIB_NAME}" CACHE PATH "Path to Unicorn library" FORCE) set(LIBUNICORN_INCLUDE_DIR "${UNICORN_PREFIX}/include" CACHE PATH "Path to Unicorn headers" FORCE) set(UNICORN_DLL_DIR "${UNICORN_PREFIX}/" CACHE PATH "Path to unicorn dynamic library" FORCE) @@ -356,12 +352,12 @@ set(CLANG_FORMAT_POSTFIX "-6.0") find_program(CLANG_FORMAT NAMES clang-format${CLANG_FORMAT_POSTFIX} clang-format - PATHS ${CMAKE_BINARY_DIR}/externals) + PATHS ${PROJECT_BINARY_DIR}/externals) # if find_program doesn't find it, try to download from externals if (NOT CLANG_FORMAT) if (WIN32) message(STATUS "Clang format not found! Downloading...") - set(CLANG_FORMAT "${CMAKE_BINARY_DIR}/externals/clang-format${CLANG_FORMAT_POSTFIX}.exe") + set(CLANG_FORMAT "${PROJECT_BINARY_DIR}/externals/clang-format${CLANG_FORMAT_POSTFIX}.exe") file(DOWNLOAD https://github.com/yuzu-emu/ext-windows-bin/raw/master/clang-format${CLANG_FORMAT_POSTFIX}.exe "${CLANG_FORMAT}" SHOW_PROGRESS @@ -377,7 +373,7 @@ if (NOT CLANG_FORMAT) endif() if (CLANG_FORMAT) - set(SRCS ${CMAKE_SOURCE_DIR}/src) + set(SRCS ${PROJECT_SOURCE_DIR}/src) set(CCOMMENT "Running clang format against all the .h and .cpp files in src/") if (WIN32) add_custom_target(clang-format @@ -450,10 +446,10 @@ endif() # http://standards.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html # http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html if(ENABLE_QT AND UNIX AND NOT APPLE) - install(FILES "${CMAKE_SOURCE_DIR}/dist/yuzu.desktop" + install(FILES "${PROJECT_SOURCE_DIR}/dist/yuzu.desktop" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/applications") - install(FILES "${CMAKE_SOURCE_DIR}/dist/yuzu.svg" + install(FILES "${PROJECT_SOURCE_DIR}/dist/yuzu.svg" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/icons/hicolor/scalable/apps") - install(FILES "${CMAKE_SOURCE_DIR}/dist/yuzu.xml" + install(FILES "${PROJECT_SOURCE_DIR}/dist/yuzu.xml" DESTINATION "${CMAKE_INSTALL_PREFIX}/share/mime/packages") endif() 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/logging/backend.cpp b/src/common/logging/backend.cpp index 9f5918851..6d5218465 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp @@ -196,6 +196,7 @@ void FileBackend::Write(const Entry& entry) { SUB(Service, NFP) \ SUB(Service, NIFM) \ SUB(Service, NIM) \ + SUB(Service, NPNS) \ SUB(Service, NS) \ SUB(Service, NVDRV) \ SUB(Service, PCIE) \ @@ -204,10 +205,12 @@ void FileBackend::Write(const Entry& entry) { SUB(Service, PM) \ SUB(Service, PREPO) \ SUB(Service, PSC) \ + SUB(Service, PSM) \ SUB(Service, SET) \ SUB(Service, SM) \ SUB(Service, SPL) \ SUB(Service, SSL) \ + SUB(Service, TCAP) \ SUB(Service, Time) \ SUB(Service, USB) \ SUB(Service, VI) \ diff --git a/src/common/logging/log.h b/src/common/logging/log.h index c9161155a..d4ec31ec3 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h @@ -83,6 +83,7 @@ enum class Class : ClassType { Service_NFP, ///< The NFP service Service_NIFM, ///< The NIFM (Network interface) service Service_NIM, ///< The NIM service + Service_NPNS, ///< The NPNS service Service_NS, ///< The NS services Service_NVDRV, ///< The NVDRV (Nvidia driver) service Service_PCIE, ///< The PCIe service @@ -96,6 +97,7 @@ enum class Class : ClassType { Service_SM, ///< The SM (Service manager) service Service_SPL, ///< The SPL service Service_SSL, ///< The SSL service + Service_TCAP, ///< The TCAP service. Service_Time, ///< The time service Service_USB, ///< The USB (Universal Serial Bus) service Service_VI, ///< The VI (Video interface) 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/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp index fd0786068..fefc3c747 100644 --- a/src/core/crypto/key_manager.cpp +++ b/src/core/crypto/key_manager.cpp @@ -713,7 +713,6 @@ void KeyManager::DeriveBase() { const auto sbk = GetKey(S128KeyType::SecureBoot); const auto tsec = GetKey(S128KeyType::TSEC); - const auto master_source = GetKey(S128KeyType::Source, static_cast<u64>(SourceKeyType::Master)); for (size_t i = 0; i < revisions.size(); ++i) { if (!revisions[i]) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 0117cb0bf..1f4928562 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -168,7 +168,8 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const { static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id); - if (type != ContentRecordType::Program || load_dir == nullptr || load_dir->GetSize() <= 0) { + if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || + load_dir == nullptr || load_dir->GetSize() <= 0) { return; } @@ -218,7 +219,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content title_id, static_cast<u8>(type)) .c_str(); - if (type == ContentRecordType::Program) + if (type == ContentRecordType::Program || type == ContentRecordType::Data) LOG_INFO(Loader, log_string); else LOG_DEBUG(Loader, log_string); diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 1febb398e..29b100414 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -2,6 +2,7 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <regex> #include <mbedtls/sha256.h> #include "common/assert.h" @@ -30,6 +31,14 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) return (lhs.title_id < rhs.title_id) || (lhs.title_id == rhs.title_id && lhs.type < rhs.type); } +bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) { + return std::tie(lhs.title_id, lhs.type) == std::tie(rhs.title_id, rhs.type); +} + +bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) { + return !operator==(lhs, rhs); +} + static bool FollowsTwoDigitDirFormat(std::string_view name) { static const std::regex two_digit_regex("000000[0-9A-F]{2}", std::regex_constants::ECMAScript | std::regex_constants::icase); @@ -593,6 +602,9 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const { }, [](const CNMT& c, const ContentRecord& r) { return true; }); } + + std::sort(out.begin(), out.end()); + out.erase(std::unique(out.begin(), out.end()), out.end()); return out; } @@ -616,6 +628,9 @@ std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter( return true; }); } + + std::sort(out.begin(), out.end()); + out.erase(std::unique(out.begin(), out.end()), out.end()); return out; } } // namespace FileSys diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 5ddacba47..5beceffb3 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -50,6 +50,10 @@ constexpr u64 GetUpdateTitleID(u64 base_title_id) { // boost flat_map requires operator< for O(log(n)) lookups. bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); +// std unique requires operator== to identify duplicates. +bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); +bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); + /* * A class that catalogues NCAs in the registered directory structure. * Nintendo's registered format follows this structure: @@ -60,8 +64,8 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs) * | 00 * | 01 <- Actual content split along 4GB boundaries. (optional) * - * (This impl also supports substituting the nca dir for an nca file, as that's more convenient when - * 4GB splitting can be ignored.) + * (This impl also supports substituting the nca dir for an nca file, as that's more convenient + * when 4GB splitting can be ignored.) */ class RegisteredCache { friend class RegisteredCacheUnion; diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index bfe50da73..3824c74e0 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp @@ -472,10 +472,14 @@ bool VfsRawCopy(const VirtualFile& src, const VirtualFile& dest, std::size_t blo std::vector<u8> temp(std::min(block_size, src->GetSize())); for (std::size_t i = 0; i < src->GetSize(); i += block_size) { const auto read = std::min(block_size, src->GetSize() - i); - const auto block = src->Read(temp.data(), read, i); - if (dest->Write(temp.data(), read, i) != read) + if (src->Read(temp.data(), read, i) != read) { return false; + } + + if (dest->Write(temp.data(), read, i) != read) { + return false; + } } return true; 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 690b84930..9a783d524 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); } @@ -374,9 +375,19 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) { return Mutex::Release(mutex_addr); } +enum class BreakType : u32 { + Panic = 0, + AssertionFailed = 1, + PreNROLoad = 3, + PostNROLoad = 4, + PreNROUnload = 5, + PostNROUnload = 6, +}; + struct BreakReason { union { u32 raw; + BitField<0, 30, BreakType> break_type; BitField<31, 1, u32> signal_debugger; }; }; @@ -384,12 +395,48 @@ struct BreakReason { /// Break program execution static void Break(u32 reason, u64 info1, u64 info2) { BreakReason break_reason{reason}; - if (break_reason.signal_debugger) { - LOG_ERROR( + + switch (break_reason.break_type) { + case BreakType::Panic: + LOG_CRITICAL(Debug_Emulated, "Signalling debugger, PANIC! info1=0x{:016X}, info2=0x{:016X}", + info1, info2); + break; + case BreakType::AssertionFailed: + LOG_CRITICAL(Debug_Emulated, + "Signalling debugger, Assertion failed! info1=0x{:016X}, info2=0x{:016X}", + info1, info2); + break; + case BreakType::PreNROLoad: + LOG_WARNING( Debug_Emulated, - "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", - reason, info1, info2); - } else { + "Signalling debugger, Attempting to load an NRO at 0x{:016X} with size 0x{:016X}", + info1, info2); + break; + case BreakType::PostNROLoad: + LOG_WARNING(Debug_Emulated, + "Signalling debugger, Loaded an NRO at 0x{:016X} with size 0x{:016X}", info1, + info2); + break; + case BreakType::PreNROUnload: + LOG_WARNING( + Debug_Emulated, + "Signalling debugger, Attempting to unload an NRO at 0x{:016X} with size 0x{:016X}", + info1, info2); + break; + case BreakType::PostNROUnload: + LOG_WARNING(Debug_Emulated, + "Signalling debugger, Unloaded an NRO at 0x{:016X} with size 0x{:016X}", info1, + info2); + break; + default: + LOG_WARNING( + Debug_Emulated, + "Signalling debugger, Unknown break reason {}, info1=0x{:016X}, info2=0x{:016X}", + static_cast<u32>(break_reason.break_type.Value()), info1, info2); + break; + } + + if (!break_reason.signal_debugger) { LOG_CRITICAL( Debug_Emulated, "Emulated program broke execution! reason=0x{:016X}, info1=0x{:016X}, info2=0x{:016X}", @@ -499,13 +546,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 +577,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 +593,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 +643,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 +673,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,9 +690,8 @@ 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; } @@ -695,20 +738,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 +765,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 +792,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 +840,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 +952,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 +1033,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 +1061,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 +1077,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 +1139,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 +1151,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 +1169,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/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp index e61748ca3..cf065c2e0 100644 --- a/src/core/hle/service/acc/acc.cpp +++ b/src/core/hle/service/acc/acc.cpp @@ -2,9 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> #include <array> +#include "common/common_paths.h" #include "common/common_types.h" +#include "common/file_util.h" #include "common/logging/log.h" +#include "common/string_util.h" #include "common/swap.h" #include "core/core_timing.h" #include "core/hle/ipc_helpers.h" @@ -16,6 +20,9 @@ #include "core/hle/service/acc/profile_manager.h" namespace Service::Account { + +constexpr u32 MAX_JPEG_IMAGE_SIZE = 0x20000; + // TODO: RE this structure struct UserData { INSERT_PADDING_WORDS(1); @@ -27,6 +34,11 @@ struct UserData { }; static_assert(sizeof(UserData) == 0x80, "UserData structure has incorrect size"); +static std::string GetImagePath(UUID uuid) { + return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; +} + class IProfile final : public ServiceFramework<IProfile> { public: explicit IProfile(UUID user_id, ProfileManager& profile_manager) @@ -73,11 +85,11 @@ private: } void LoadImage(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); + LOG_DEBUG(Service_ACC, "called"); // smallest jpeg https://github.com/mathiasbynens/small/blob/master/jpeg.jpg - // TODO(mailwl): load actual profile image from disk, width 256px, max size 0x20000 - constexpr u32 jpeg_size = 107; - static constexpr std::array<u8, jpeg_size> jpeg{ + // used as a backup should the one on disk not exist + constexpr u32 backup_jpeg_size = 107; + static constexpr std::array<u8, backup_jpeg_size> backup_jpeg{ 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, @@ -87,18 +99,42 @@ private: 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, }; - ctx.WriteBuffer(jpeg); + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(jpeg_size); + + const FileUtil::IOFile image(GetImagePath(user_id), "rb"); + + if (!image.IsOpen()) { + LOG_WARNING(Service_ACC, + "Failed to load user provided image! Falling back to built-in backup..."); + ctx.WriteBuffer(backup_jpeg); + rb.Push<u32>(backup_jpeg_size); + } else { + const auto size = std::min<u32>(image.GetSize(), MAX_JPEG_IMAGE_SIZE); + std::vector<u8> buffer(size); + image.ReadBytes(buffer.data(), buffer.size()); + + ctx.WriteBuffer(buffer.data(), buffer.size()); + rb.Push<u32>(buffer.size()); + } } void GetImageSize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_ACC, "(STUBBED) called"); - constexpr u32 jpeg_size = 107; + LOG_DEBUG(Service_ACC, "called"); + constexpr u32 backup_jpeg_size = 107; IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(jpeg_size); + + const FileUtil::IOFile image(GetImagePath(user_id), "rb"); + + if (!image.IsOpen()) { + LOG_WARNING(Service_ACC, + "Failed to load user provided image! Falling back to built-in backup..."); + rb.Push<u32>(backup_jpeg_size); + } else { + rb.Push<u32>(std::min<u32>(image.GetSize(), MAX_JPEG_IMAGE_SIZE)); + } } const ProfileManager& profile_manager; diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp index bcb3475db..06f7d1b15 100644 --- a/src/core/hle/service/acc/profile_manager.cpp +++ b/src/core/hle/service/acc/profile_manager.cpp @@ -4,32 +4,57 @@ #include <random> #include <boost/optional.hpp> +#include "common/file_util.h" #include "core/hle/service/acc/profile_manager.h" #include "core/settings.h" namespace Service::Account { + +struct UserRaw { + UUID uuid; + UUID uuid2; + u64 timestamp; + ProfileUsername username; + INSERT_PADDING_BYTES(0x80); +}; +static_assert(sizeof(UserRaw) == 0xC8, "UserRaw has incorrect size."); + +struct ProfileDataRaw { + INSERT_PADDING_BYTES(0x10); + std::array<UserRaw, MAX_USERS> users; +}; +static_assert(sizeof(ProfileDataRaw) == 0x650, "ProfileDataRaw has incorrect size."); + // TODO(ogniK): Get actual error codes constexpr ResultCode ERROR_TOO_MANY_USERS(ErrorModule::Account, -1); constexpr ResultCode ERROR_USER_ALREADY_EXISTS(ErrorModule::Account, -2); constexpr ResultCode ERROR_ARGUMENT_IS_NULL(ErrorModule::Account, 20); -const UUID& UUID::Generate() { +constexpr char ACC_SAVE_AVATORS_BASE_PATH[] = "/system/save/8000000000000010/su/avators/"; + +UUID UUID::Generate() { std::random_device device; std::mt19937 gen(device()); std::uniform_int_distribution<u64> distribution(1, std::numeric_limits<u64>::max()); - uuid[0] = distribution(gen); - uuid[1] = distribution(gen); - return *this; + return UUID{distribution(gen), distribution(gen)}; } ProfileManager::ProfileManager() { - // TODO(ogniK): Create the default user we have for now until loading/saving users is added - auto user_uuid = UUID{1, 0}; - ASSERT(CreateNewUser(user_uuid, Settings::values.username).IsSuccess()); - OpenUser(user_uuid); + ParseUserSaveFile(); + + if (user_count == 0) + CreateNewUser(UUID::Generate(), "yuzu"); + + auto current = std::clamp<int>(Settings::values.current_user, 0, MAX_USERS - 1); + if (UserExistsIndex(current)) + current = 0; + + OpenUser(*GetUser(current)); } -ProfileManager::~ProfileManager() = default; +ProfileManager::~ProfileManager() { + WriteUserSaveFile(); +} /// After a users creation it needs to be "registered" to the system. AddToProfiles handles the /// internal management of the users profiles @@ -101,6 +126,12 @@ ResultCode ProfileManager::CreateNewUser(UUID uuid, const std::string& username) return CreateNewUser(uuid, username_output); } +boost::optional<UUID> ProfileManager::GetUser(std::size_t index) const { + if (index >= MAX_USERS) + return boost::none; + return profiles[index].user_uuid; +} + /// Returns a users profile index based on their user id. boost::optional<std::size_t> ProfileManager::GetUserIndex(const UUID& uuid) const { if (!uuid) { @@ -164,6 +195,12 @@ bool ProfileManager::UserExists(UUID uuid) const { return (GetUserIndex(uuid) != boost::none); } +bool ProfileManager::UserExistsIndex(std::size_t index) const { + if (index >= MAX_USERS) + return false; + return profiles[index].user_uuid.uuid != INVALID_UUID; +} + /// Opens a specific user void ProfileManager::OpenUser(UUID uuid) { auto idx = GetUserIndex(uuid); @@ -239,4 +276,96 @@ bool ProfileManager::CanSystemRegisterUser() const { // emulate qlaunch. Update this to dynamically change. } +bool ProfileManager::RemoveUser(UUID uuid) { + auto index = GetUserIndex(uuid); + if (index == boost::none) { + return false; + } + + profiles[*index] = ProfileInfo{}; + std::stable_partition(profiles.begin(), profiles.end(), + [](const ProfileInfo& profile) { return profile.user_uuid; }); + return true; +} + +bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) { + auto index = GetUserIndex(uuid); + if (profile_new.user_uuid == UUID(INVALID_UUID) || index == boost::none) { + return false; + } + + auto& profile = profiles[*index]; + profile.user_uuid = profile_new.user_uuid; + profile.username = profile_new.username; + profile.creation_time = profile_new.timestamp; + + return true; +} + +void ProfileManager::ParseUserSaveFile() { + FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", + "rb"); + + if (!save.IsOpen()) { + LOG_WARNING(Service_ACC, "Failed to load profile data from save data... Generating new " + "user 'yuzu' with random UUID."); + return; + } + + ProfileDataRaw data; + if (save.ReadBytes(&data, sizeof(ProfileDataRaw)) != sizeof(ProfileDataRaw)) { + LOG_WARNING(Service_ACC, "profiles.dat is smaller than expected... Generating new user " + "'yuzu' with random UUID."); + return; + } + + for (std::size_t i = 0; i < MAX_USERS; ++i) { + const auto& user = data.users[i]; + + if (user.uuid != UUID(INVALID_UUID)) + AddUser({user.uuid, user.username, user.timestamp, {}, false}); + } + + std::stable_partition(profiles.begin(), profiles.end(), + [](const ProfileInfo& profile) { return profile.user_uuid; }); +} + +void ProfileManager::WriteUserSaveFile() { + ProfileDataRaw raw{}; + + for (std::size_t i = 0; i < MAX_USERS; ++i) { + raw.users[i].username = profiles[i].username; + raw.users[i].uuid2 = profiles[i].user_uuid; + raw.users[i].uuid = profiles[i].user_uuid; + raw.users[i].timestamp = profiles[i].creation_time; + } + + const auto raw_path = + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"; + if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path)) + FileUtil::Delete(raw_path); + + const auto path = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat"; + + if (!FileUtil::CreateFullPath(path)) { + LOG_WARNING(Service_ACC, "Failed to create full path of profiles.dat. Create the directory " + "nand/system/save/8000000000000010/su/avators to mitigate this " + "issue."); + return; + } + + FileUtil::IOFile save(path, "wb"); + + if (!save.IsOpen()) { + LOG_WARNING(Service_ACC, "Failed to write save data to file... No changes to user data " + "made in current session will be saved."); + return; + } + + save.Resize(sizeof(ProfileDataRaw)); + save.WriteBytes(&raw, sizeof(ProfileDataRaw)); +} + }; // namespace Service::Account diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h index bffd4cf4d..235208d56 100644 --- a/src/core/hle/service/acc/profile_manager.h +++ b/src/core/hle/service/acc/profile_manager.h @@ -36,7 +36,7 @@ struct UUID { } // TODO(ogniK): Properly generate uuids based on RFC-4122 - const UUID& Generate(); + static UUID Generate(); // Set the UUID to {0,0} to be considered an invalid user void Invalidate() { @@ -45,6 +45,15 @@ struct UUID { std::string Format() const { return fmt::format("0x{:016X}{:016X}", uuid[1], uuid[0]); } + + std::string FormatSwitch() const { + std::array<u8, 16> s{}; + std::memcpy(s.data(), uuid.data(), sizeof(u128)); + return fmt::format("{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{" + ":02x}{:02x}{:02x}{:02x}{:02x}", + s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11], + s[12], s[13], s[14], s[15]); + } }; static_assert(sizeof(UUID) == 16, "UUID is an invalid size!"); @@ -81,12 +90,13 @@ static_assert(sizeof(ProfileBase) == 0x38, "ProfileBase is an invalid size"); /// objects class ProfileManager { public: - ProfileManager(); // TODO(ogniK): Load from system save + ProfileManager(); ~ProfileManager(); ResultCode AddUser(const ProfileInfo& user); ResultCode CreateNewUser(UUID uuid, const ProfileUsername& username); ResultCode CreateNewUser(UUID uuid, const std::string& username); + boost::optional<UUID> GetUser(std::size_t index) const; boost::optional<std::size_t> GetUserIndex(const UUID& uuid) const; boost::optional<std::size_t> GetUserIndex(const ProfileInfo& user) const; bool GetProfileBase(boost::optional<std::size_t> index, ProfileBase& profile) const; @@ -100,6 +110,7 @@ public: std::size_t GetUserCount() const; std::size_t GetOpenUserCount() const; bool UserExists(UUID uuid) const; + bool UserExistsIndex(std::size_t index) const; void OpenUser(UUID uuid); void CloseUser(UUID uuid); UserIDArray GetOpenUsers() const; @@ -108,7 +119,13 @@ public: bool CanSystemRegisterUser() const; + bool RemoveUser(UUID uuid); + bool SetProfileBase(UUID uuid, const ProfileBase& profile); + private: + void ParseUserSaveFile(); + void WriteUserSaveFile(); + std::array<ProfileInfo, MAX_USERS> profiles{}; std::size_t user_count = 0; boost::optional<std::size_t> AddToProfiles(const ProfileInfo& profile); diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index ecf72ae24..4ed66d817 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -4,11 +4,13 @@ #include <array> #include <cinttypes> +#include <cstring> #include <stack> #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" #include "core/hle/kernel/process.h" +#include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/am.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" @@ -26,6 +28,16 @@ namespace Service::AM { +constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA; + +struct LaunchParameters { + u32_le magic; + u32_le is_account_selected; + u128 current_user; + INSERT_PADDING_BYTES(0x70); +}; +static_assert(sizeof(LaunchParameters) == 0x88); + IWindowController::IWindowController() : ServiceFramework("IWindowController") { // clang-format off static const FunctionInfo functions[] = { @@ -724,20 +736,23 @@ void IApplicationFunctions::EndBlockingHomeButton(Kernel::HLERequestContext& ctx } void IApplicationFunctions::PopLaunchParameter(Kernel::HLERequestContext& ctx) { - constexpr std::array<u8, 0x88> data{{ - 0xca, 0x97, 0x94, 0xc7, // Magic - 1, 0, 0, 0, // IsAccountSelected (bool) - 1, 0, 0, 0, // User Id (word 0) - 0, 0, 0, 0, // User Id (word 1) - 0, 0, 0, 0, // User Id (word 2) - 0, 0, 0, 0 // User Id (word 3) - }}; + LaunchParameters params{}; - std::vector<u8> buffer(data.begin(), data.end()); + params.magic = POP_LAUNCH_PARAMETER_MAGIC; + params.is_account_selected = 1; + + Account::ProfileManager profile_manager{}; + const auto uuid = profile_manager.GetUser(Settings::values.current_user); + ASSERT(uuid != boost::none); + params.current_user = uuid->uuid; IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); + + std::vector<u8> buffer(sizeof(LaunchParameters)); + std::memcpy(buffer.data(), ¶ms, buffer.size()); + rb.PushIpcInterface<AM::IStorage>(buffer); LOG_DEBUG(Service_AM, "called"); diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 428069df2..54305cf05 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -24,8 +24,8 @@ namespace Service::AOC { constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; constexpr u64 DLC_BASE_TO_AOC_ID = 0x1000; -static bool CheckAOCTitleIDMatchesBase(u64 base, u64 aoc) { - return (aoc & DLC_BASE_TITLE_ID_MASK) == base; +static bool CheckAOCTitleIDMatchesBase(u64 title_id, u64 base) { + return (title_id & DLC_BASE_TITLE_ID_MASK) == base; } static std::vector<u64> AccumulateAOCTitleIDs() { @@ -74,7 +74,7 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) { const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); rb.Push<u32>(static_cast<u32>( std::count_if(add_on_content.begin(), add_on_content.end(), - [¤t](u64 tid) { return (tid & DLC_BASE_TITLE_ID_MASK) == current; }))); + [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); }))); } void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) { diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index d5dced429..c87721c39 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp @@ -17,6 +17,7 @@ #include "core/file_sys/errors.h" #include "core/file_sys/mode.h" #include "core/file_sys/nca_metadata.h" +#include "core/file_sys/patch_manager.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/vfs.h" #include "core/hle/ipc_helpers.h" @@ -630,6 +631,7 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { static_cast<u8>(storage_id), unknown, title_id); auto data = OpenRomFS(title_id, storage_id, FileSys::ContentRecordType::Data); + if (data.Failed()) { // TODO(DarkLordZach): Find the right error code to use here LOG_ERROR(Service_FS, @@ -640,7 +642,9 @@ void FSP_SRV::OpenDataStorageByDataId(Kernel::HLERequestContext& ctx) { return; } - IStorage storage(std::move(data.Unwrap())); + FileSys::PatchManager pm{title_id}; + + IStorage storage(pm.PatchRomFS(std::move(data.Unwrap()), 0, FileSys::ContentRecordType::Data)); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp index 8fec97db8..30e542542 100644 --- a/src/core/hle/service/nfc/nfc.cpp +++ b/src/core/hle/service/nfc/nfc.cpp @@ -10,12 +10,13 @@ #include "core/hle/service/nfc/nfc.h" #include "core/hle/service/service.h" #include "core/hle/service/sm/sm.h" +#include "core/settings.h" namespace Service::NFC { class IAm final : public ServiceFramework<IAm> { public: - explicit IAm() : ServiceFramework{"IAm"} { + explicit IAm() : ServiceFramework{"NFC::IAm"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -52,7 +53,7 @@ private: class MFIUser final : public ServiceFramework<MFIUser> { public: - explicit MFIUser() : ServiceFramework{"IUser"} { + explicit MFIUser() : ServiceFramework{"NFC::MFIUser"} { // clang-format off static const FunctionInfo functions[] = { {0, nullptr, "Initialize"}, @@ -100,13 +101,13 @@ private: class IUser final : public ServiceFramework<IUser> { public: - explicit IUser() : ServiceFramework{"IUser"} { + explicit IUser() : ServiceFramework{"NFC::IUser"} { // clang-format off static const FunctionInfo functions[] = { - {0, nullptr, "Initialize"}, - {1, nullptr, "Finalize"}, - {2, nullptr, "GetState"}, - {3, nullptr, "IsNfcEnabled"}, + {0, &IUser::InitializeOld, "InitializeOld"}, + {1, &IUser::FinalizeOld, "FinalizeOld"}, + {2, &IUser::GetStateOld, "GetStateOld"}, + {3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"}, {400, nullptr, "Initialize"}, {401, nullptr, "Finalize"}, {402, nullptr, "GetState"}, @@ -130,11 +131,47 @@ public: RegisterHandlers(functions); } + +private: + enum class NfcStates : u32 { + Finalized = 6, + }; + + void InitializeOld(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 2, 0}; + rb.Push(RESULT_SUCCESS); + + // We don't deal with hardware initialization so we can just stub this. + LOG_DEBUG(Service_NFC, "called"); + } + + void IsNfcEnabledOld(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u8>(Settings::values.enable_nfc); + + LOG_DEBUG(Service_NFC, "IsNfcEnabledOld"); + } + + void GetStateOld(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NFC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushEnum(NfcStates::Finalized); // TODO(ogniK): Figure out if this matches nfp + } + + void FinalizeOld(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NFC, "(STUBBED) called"); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } }; class NFC_U final : public ServiceFramework<NFC_U> { public: - explicit NFC_U() : ServiceFramework{"nfc:u"} { + explicit NFC_U() : ServiceFramework{"nfc:user"} { // clang-format off static const FunctionInfo functions[] = { {0, &NFC_U::CreateUserInterface, "CreateUserInterface"}, diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp index 39c0c1e63..9a4eb9301 100644 --- a/src/core/hle/service/nfp/nfp.cpp +++ b/src/core/hle/service/nfp/nfp.cpp @@ -2,56 +2,67 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <atomic> + #include "common/logging/log.h" #include "core/core.h" #include "core/hle/ipc_helpers.h" #include "core/hle/kernel/event.h" +#include "core/hle/lock.h" #include "core/hle/service/hid/hid.h" #include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nfp/nfp_user.h" namespace Service::NFP { +namespace ErrCodes { +constexpr ResultCode ERR_TAG_FAILED(ErrorModule::NFP, + -1); // TODO(ogniK): Find the actual error code +} + Module::Interface::Interface(std::shared_ptr<Module> module, const char* name) - : ServiceFramework(name), module(std::move(module)) {} + : ServiceFramework(name), module(std::move(module)) { + auto& kernel = Core::System::GetInstance().Kernel(); + nfc_tag_load = + Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:NFCTagDetected"); +} Module::Interface::~Interface() = default; class IUser final : public ServiceFramework<IUser> { public: - IUser() : ServiceFramework("IUser") { + IUser(Module::Interface& nfp_interface) + : ServiceFramework("NFP::IUser"), nfp_interface(nfp_interface) { static const FunctionInfo functions[] = { {0, &IUser::Initialize, "Initialize"}, - {1, nullptr, "Finalize"}, + {1, &IUser::Finalize, "Finalize"}, {2, &IUser::ListDevices, "ListDevices"}, - {3, nullptr, "StartDetection"}, - {4, nullptr, "StopDetection"}, - {5, nullptr, "Mount"}, - {6, nullptr, "Unmount"}, - {7, nullptr, "OpenApplicationArea"}, - {8, nullptr, "GetApplicationArea"}, + {3, &IUser::StartDetection, "StartDetection"}, + {4, &IUser::StopDetection, "StopDetection"}, + {5, &IUser::Mount, "Mount"}, + {6, &IUser::Unmount, "Unmount"}, + {7, &IUser::OpenApplicationArea, "OpenApplicationArea"}, + {8, &IUser::GetApplicationArea, "GetApplicationArea"}, {9, nullptr, "SetApplicationArea"}, {10, nullptr, "Flush"}, {11, nullptr, "Restore"}, {12, nullptr, "CreateApplicationArea"}, - {13, nullptr, "GetTagInfo"}, - {14, nullptr, "GetRegisterInfo"}, - {15, nullptr, "GetCommonInfo"}, - {16, nullptr, "GetModelInfo"}, + {13, &IUser::GetTagInfo, "GetTagInfo"}, + {14, &IUser::GetRegisterInfo, "GetRegisterInfo"}, + {15, &IUser::GetCommonInfo, "GetCommonInfo"}, + {16, &IUser::GetModelInfo, "GetModelInfo"}, {17, &IUser::AttachActivateEvent, "AttachActivateEvent"}, {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"}, {19, &IUser::GetState, "GetState"}, {20, &IUser::GetDeviceState, "GetDeviceState"}, {21, &IUser::GetNpadId, "GetNpadId"}, - {22, nullptr, "GetApplicationArea2"}, + {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"}, {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, {24, nullptr, "RecreateApplicationArea"}, }; RegisterHandlers(functions); auto& kernel = Core::System::GetInstance().Kernel(); - activate_event = - Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:ActivateEvent"); deactivate_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, "IUser:DeactivateEvent"); availability_change_event = Kernel::Event::Create(kernel, Kernel::ResetType::OneShot, @@ -59,6 +70,17 @@ public: } private: + struct TagInfo { + std::array<u8, 10> uuid; + u8 uuid_length; // TODO(ogniK): Figure out if this is actual the uuid length or does it + // mean something else + INSERT_PADDING_BYTES(0x15); + u32_le protocol; + u32_le tag_type; + INSERT_PADDING_BYTES(0x2c); + }; + static_assert(sizeof(TagInfo) == 0x54, "TagInfo is an invalid size"); + enum class State : u32 { NonInitialized = 0, Initialized = 1, @@ -66,15 +88,40 @@ private: enum class DeviceState : u32 { Initialized = 0, + SearchingForTag = 1, + TagFound = 2, + TagRemoved = 3, + TagNearby = 4, + Unknown5 = 5, + Finalized = 6 }; + struct CommonInfo { + u16_be last_write_year; + u8 last_write_month; + u8 last_write_day; + u16_be write_counter; + u16_be version; + u32_be application_area_size; + INSERT_PADDING_BYTES(0x34); + }; + static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size"); + void Initialize(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NFP, "(STUBBED) called"); + IPC::ResponseBuilder rb{ctx, 2, 0}; + rb.Push(RESULT_SUCCESS); state = State::Initialized; - IPC::ResponseBuilder rb{ctx, 2}; + LOG_DEBUG(Service_NFC, "called"); + } + + void GetState(Kernel::HLERequestContext& ctx) { + IPC::ResponseBuilder rb{ctx, 3, 0}; rb.Push(RESULT_SUCCESS); + rb.PushRaw<u32>(static_cast<u32>(state)); + + LOG_DEBUG(Service_NFC, "called"); } void ListDevices(Kernel::HLERequestContext& ctx) { @@ -83,80 +130,217 @@ private: ctx.WriteBuffer(&device_handle, sizeof(device_handle)); - LOG_WARNING(Service_NFP, "(STUBBED) called, array_size={}", array_size); + LOG_DEBUG(Service_NFP, "called, array_size={}", array_size); IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(0); + rb.Push<u32>(1); } - void AttachActivateEvent(Kernel::HLERequestContext& ctx) { + void GetNpadId(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 dev_handle = rp.Pop<u64>(); - LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle); + LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle); + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.Push<u32>(npad_id); + } + void AttachActivateEvent(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const u64 dev_handle = rp.Pop<u64>(); + LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); - rb.PushCopyObjects(activate_event); + rb.PushCopyObjects(nfp_interface.GetNFCEvent()); + has_attached_handle = true; } void AttachDeactivateEvent(Kernel::HLERequestContext& ctx) { IPC::RequestParser rp{ctx}; const u64 dev_handle = rp.Pop<u64>(); - LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle); + LOG_DEBUG(Service_NFP, "called, dev_handle=0x{:X}", dev_handle); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(deactivate_event); } - void GetState(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NFP, "(STUBBED) called"); - IPC::ResponseBuilder rb{ctx, 3}; + void StopDetection(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFP, "called"); + switch (device_state) { + case DeviceState::TagFound: + case DeviceState::TagNearby: + deactivate_event->Signal(); + device_state = DeviceState::Initialized; + break; + case DeviceState::SearchingForTag: + case DeviceState::TagRemoved: + device_state = DeviceState::Initialized; + break; + } + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(static_cast<u32>(state)); } void GetDeviceState(Kernel::HLERequestContext& ctx) { - LOG_WARNING(Service_NFP, "(STUBBED) called"); + LOG_DEBUG(Service_NFP, "called"); + auto nfc_event = nfp_interface.GetNFCEvent(); + if (!nfc_event->ShouldWait(Kernel::GetCurrentThread()) && !has_attached_handle) { + device_state = DeviceState::TagFound; + nfc_event->Clear(); + } + IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); rb.Push<u32>(static_cast<u32>(device_state)); } - void GetNpadId(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u64 dev_handle = rp.Pop<u64>(); - LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle); - IPC::ResponseBuilder rb{ctx, 3}; + void StartDetection(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFP, "called"); + + if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) { + device_state = DeviceState::SearchingForTag; + } + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetTagInfo(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFP, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + auto amiibo = nfp_interface.GetAmiiboBuffer(); + TagInfo tag_info{}; + std::memcpy(tag_info.uuid.data(), amiibo.uuid.data(), sizeof(tag_info.uuid.size())); + tag_info.uuid_length = static_cast<u8>(tag_info.uuid.size()); + + tag_info.protocol = 1; // TODO(ogniK): Figure out actual values + tag_info.tag_type = 2; + ctx.WriteBuffer(&tag_info, sizeof(TagInfo)); + rb.Push(RESULT_SUCCESS); + } + + void Mount(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFP, "called"); + + device_state = DeviceState::TagNearby; + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetModelInfo(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFP, "called"); + + IPC::ResponseBuilder rb{ctx, 2}; + auto amiibo = nfp_interface.GetAmiiboBuffer(); + ctx.WriteBuffer(&amiibo.model_info, sizeof(amiibo.model_info)); + rb.Push(RESULT_SUCCESS); + } + + void Unmount(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFP, "called"); + + device_state = DeviceState::TagFound; + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void Finalize(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFP, "called"); + + device_state = DeviceState::Finalized; + + IPC::ResponseBuilder rb{ctx, 2}; rb.Push(RESULT_SUCCESS); - rb.Push<u32>(npad_id); } void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) { - IPC::RequestParser rp{ctx}; - const u64 dev_handle = rp.Pop<u64>(); - LOG_WARNING(Service_NFP, "(STUBBED) called, dev_handle=0x{:X}", dev_handle); + LOG_WARNING(Service_NFP, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2, 1}; rb.Push(RESULT_SUCCESS); rb.PushCopyObjects(availability_change_event); } - const u64 device_handle{0xDEAD}; - const u32 npad_id{0}; // This is the first player controller id + void GetRegisterInfo(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NFP, "(STUBBED) called"); + + // TODO(ogniK): Pull Mii and owner data from amiibo + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetCommonInfo(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NFP, "(STUBBED) called"); + + // TODO(ogniK): Pull common information from amiibo + + CommonInfo common_info{}; + common_info.application_area_size = 0; + ctx.WriteBuffer(&common_info, sizeof(CommonInfo)); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void OpenApplicationArea(Kernel::HLERequestContext& ctx) { + LOG_DEBUG(Service_NFP, "called"); + // We don't need to worry about this since we can just open the file + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); + } + + void GetApplicationAreaSize(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NFP, "(STUBBED) called"); + // We don't need to worry about this since we can just open the file + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u32>(0); // This is from the GetCommonInfo stub + } + + void GetApplicationArea(Kernel::HLERequestContext& ctx) { + LOG_WARNING(Service_NFP, "(STUBBED) called"); + + // TODO(ogniK): Pull application area from amiibo + + IPC::ResponseBuilder rb{ctx, 3}; + rb.Push(RESULT_SUCCESS); + rb.PushRaw<u32>(0); // This is from the GetCommonInfo stub + } + + bool has_attached_handle{}; + const u64 device_handle{Common::MakeMagic('Y', 'U', 'Z', 'U')}; + const u32 npad_id{0}; // Player 1 controller State state{State::NonInitialized}; DeviceState device_state{DeviceState::Initialized}; - Kernel::SharedPtr<Kernel::Event> activate_event; Kernel::SharedPtr<Kernel::Event> deactivate_event; Kernel::SharedPtr<Kernel::Event> availability_change_event; + const Module::Interface& nfp_interface; }; void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_NFP, "called"); IPC::ResponseBuilder rb{ctx, 2, 0, 1}; rb.Push(RESULT_SUCCESS); - rb.PushIpcInterface<IUser>(); + rb.PushIpcInterface<IUser>(*this); +} + +void Module::Interface::LoadAmiibo(const std::vector<u8>& buffer) { + std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); + if (buffer.size() < sizeof(AmiiboFile)) { + return; // Failed to load file + } + std::memcpy(&amiibo, buffer.data(), sizeof(amiibo)); + nfc_tag_load->Signal(); +} +const Kernel::SharedPtr<Kernel::Event>& Module::Interface::GetNFCEvent() const { + return nfc_tag_load; +} +const Module::Interface::AmiiboFile& Module::Interface::GetAmiiboBuffer() const { + return amiibo; } void InstallInterfaces(SM::ServiceManager& service_manager) { diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h index 77df343c4..46370dedd 100644 --- a/src/core/hle/service/nfp/nfp.h +++ b/src/core/hle/service/nfp/nfp.h @@ -4,6 +4,9 @@ #pragma once +#include <array> +#include <vector> +#include "core/hle/kernel/event.h" #include "core/hle/service/service.h" namespace Service::NFP { @@ -15,7 +18,27 @@ public: explicit Interface(std::shared_ptr<Module> module, const char* name); ~Interface() override; + struct ModelInfo { + std::array<u8, 0x8> amiibo_identification_block; + INSERT_PADDING_BYTES(0x38); + }; + static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size"); + + struct AmiiboFile { + std::array<u8, 10> uuid; + INSERT_PADDING_BYTES(0x4a); + ModelInfo model_info; + }; + static_assert(sizeof(AmiiboFile) == 0x94, "AmiiboFile is an invalid size"); + void CreateUserInterface(Kernel::HLERequestContext& ctx); + void LoadAmiibo(const std::vector<u8>& buffer); + const Kernel::SharedPtr<Kernel::Event>& GetNFCEvent() const; + const AmiiboFile& GetAmiiboBuffer() const; + + private: + Kernel::SharedPtr<Kernel::Event> nfc_tag_load{}; + AmiiboFile amiibo{}; protected: std::shared_ptr<Module> module; diff --git a/src/core/perf_stats.cpp b/src/core/perf_stats.cpp index 7d95816fe..c716a462b 100644 --- a/src/core/perf_stats.cpp +++ b/src/core/perf_stats.cpp @@ -74,10 +74,6 @@ double PerfStats::GetLastFrameTimeScale() { } void FrameLimiter::DoFrameLimiting(microseconds current_system_time_us) { - // Max lag caused by slow frames. Can be adjusted to compensate for too many slow frames. Higher - // values increase the time needed to recover and limit framerate again after spikes. - constexpr microseconds MAX_LAG_TIME_US = 25000us; - if (!Settings::values.use_frame_limit) { return; } diff --git a/src/core/settings.h b/src/core/settings.h index 8f2da01c8..b5aeff29b 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -113,7 +113,8 @@ static const std::array<const char*, NumAnalogs> mapping = {{ struct Values { // System bool use_docked_mode; - std::string username; + bool enable_nfc; + int current_user; int language_index; // Controls diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index bca014a4a..78ba29fc1 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -155,7 +155,6 @@ void Maxwell3D::ProcessQueryGet() { ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop, "Units other than CROP are unimplemented"); - u32 value = Memory::Read32(*address); u64 result = 0; // TODO(Subv): Support the other query variables diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index f1b40e7f5..da7989db9 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp @@ -142,7 +142,6 @@ void SwizzledData(u8* swizzled_data, u8* unswizzled_data, const bool unswizzle, const u32 blocks_on_x = div_ceil(width, block_x_elements); const u32 blocks_on_y = div_ceil(height, block_y_elements); const u32 blocks_on_z = div_ceil(depth, block_z_elements); - const u32 blocks = blocks_on_x * blocks_on_y * blocks_on_z; const u32 gob_size = gob_x_bytes * gob_elements_y * gob_elements_z; const u32 xy_block_size = gob_size * block_height; const u32 block_size = xy_block_size * block_depth; diff --git a/src/web_service/CMakeLists.txt b/src/web_service/CMakeLists.txt index 9ad75e74a..01f2d129d 100644 --- a/src/web_service/CMakeLists.txt +++ b/src/web_service/CMakeLists.txt @@ -10,7 +10,7 @@ add_library(web_service STATIC create_target_directory_groups(web_service) get_directory_property(OPENSSL_LIBS - DIRECTORY ${CMAKE_SOURCE_DIR}/externals/libressl + DIRECTORY ${PROJECT_SOURCE_DIR}/externals/libressl DEFINITION OPENSSL_LIBS) 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 b901c29d2..9379d9110 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -82,10 +82,10 @@ set(UIS ) file(GLOB COMPAT_LIST - ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc - ${CMAKE_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) -file(GLOB_RECURSE ICONS ${CMAKE_SOURCE_DIR}/dist/icons/*) -file(GLOB_RECURSE THEMES ${CMAKE_SOURCE_DIR}/dist/qt_themes/*) + ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc + ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) +file(GLOB_RECURSE ICONS ${PROJECT_SOURCE_DIR}/dist/icons/*) +file(GLOB_RECURSE THEMES ${PROJECT_SOURCE_DIR}/dist/qt_themes/*) qt5_wrap_ui(UI_HDRS ${UIS}) diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index e8ab23326..39eef8858 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -8,7 +8,6 @@ #include "common/microprofile.h" #include "common/scm_rev.h" -#include "common/string_util.h" #include "core/core.h" #include "core/frontend/framebuffer_layout.h" #include "core/settings.h" @@ -107,9 +106,8 @@ private: GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread) : QWidget(parent), child(nullptr), emu_thread(emu_thread) { - std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_name, - Common::g_scm_branch, Common::g_scm_desc); - setWindowTitle(QString::fromStdString(window_title)); + setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") + .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc)); setAttribute(Qt::WA_AcceptTouchEvents); InputCommon::Init(); diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 71c6ebb41..1fe9a7edd 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -4,6 +4,7 @@ #include <QSettings> #include "common/file_util.h" +#include "core/hle/service/acc/profile_manager.h" #include "input_common/main.h" #include "yuzu/configuration/config.h" #include "yuzu/ui_settings.h" @@ -122,7 +123,11 @@ void Config::ReadValues() { qt_config->beginGroup("System"); Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool(); - Settings::values.username = qt_config->value("username", "yuzu").toString().toStdString(); + Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool(); + + Settings::values.current_user = std::clamp<int>(qt_config->value("current_user", 0).toInt(), 0, + Service::Account::MAX_USERS - 1); + Settings::values.language_index = qt_config->value("language_index", 1).toInt(); qt_config->endGroup(); @@ -258,7 +263,9 @@ void Config::SaveValues() { qt_config->beginGroup("System"); qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode); - qt_config->setValue("username", QString::fromStdString(Settings::values.username)); + qt_config->setValue("enable_nfc", Settings::values.enable_nfc); + qt_config->setValue("current_user", Settings::values.current_user); + qt_config->setValue("language_index", Settings::values.language_index); qt_config->endGroup(); diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index f5db9e55b..537d6e576 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp @@ -31,6 +31,7 @@ void ConfigureGeneral::setConfiguration() { ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); ui->use_cpu_jit->setChecked(Settings::values.use_cpu_jit); ui->use_docked_mode->setChecked(Settings::values.use_docked_mode); + ui->enable_nfc->setChecked(Settings::values.enable_nfc); } void ConfigureGeneral::PopulateHotkeyList(const HotkeyRegistry& registry) { @@ -45,4 +46,5 @@ void ConfigureGeneral::applyConfiguration() { Settings::values.use_cpu_jit = ui->use_cpu_jit->isChecked(); Settings::values.use_docked_mode = ui->use_docked_mode->isChecked(); + Settings::values.enable_nfc = ui->enable_nfc->isChecked(); } diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 1775c4d40..b82fffde8 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui @@ -68,19 +68,26 @@ <property name="title"> <string>Emulation</string> </property> - <layout class="QHBoxLayout" name="EmulationHorizontalLayout"> + <layout class="QHBoxLayout" name="EmulationHorizontalLayout"> + <item> + <layout class="QVBoxLayout" name="EmulationVerticalLayout"> + <item> + <widget class="QCheckBox" name="use_docked_mode"> + <property name="text"> + <string>Enable docked mode</string> + </property> + </widget> + </item> <item> - <layout class="QVBoxLayout" name="EmulationVerticalLayout"> - <item> - <widget class="QCheckBox" name="use_docked_mode"> - <property name="text"> - <string>Enable docked mode</string> - </property> - </widget> - </item> - </layout> + <widget class="QCheckBox" name="enable_nfc"> + <property name="text"> + <string>Enable NFC</string> + </property> + </widget> </item> - </layout> + </layout> + </item> + </layout> </widget> </item> <item> diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index e9ed9c38f..83cc49dfc 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -2,13 +2,30 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <algorithm> +#include <QFileDialog> +#include <QGraphicsItem> +#include <QGraphicsScene> +#include <QInputDialog> #include <QMessageBox> +#include <QStandardItemModel> +#include <QTreeView> +#include <QVBoxLayout> +#include "common/common_paths.h" +#include "common/logging/backend.h" +#include "common/string_util.h" #include "core/core.h" +#include "core/hle/service/acc/profile_manager.h" #include "core/settings.h" #include "ui_configure_system.h" #include "yuzu/configuration/configure_system.h" #include "yuzu/main.h" +static std::string GetImagePath(Service::Account::UUID uuid) { + return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + + "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; +} + static const std::array<int, 12> days_in_month = {{ 31, 29, @@ -24,7 +41,20 @@ static const std::array<int, 12> days_in_month = {{ 31, }}; -ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureSystem) { +// Same backup JPEG used by acc IProfile::GetImage if no jpeg found +static constexpr std::array<u8, 107> backup_jpeg{ + 0xff, 0xd8, 0xff, 0xdb, 0x00, 0x43, 0x00, 0x03, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x02, 0x02, + 0x02, 0x03, 0x03, 0x03, 0x03, 0x04, 0x06, 0x04, 0x04, 0x04, 0x04, 0x04, 0x08, 0x06, 0x06, 0x05, + 0x06, 0x09, 0x08, 0x0a, 0x0a, 0x09, 0x08, 0x09, 0x09, 0x0a, 0x0c, 0x0f, 0x0c, 0x0a, 0x0b, 0x0e, + 0x0b, 0x09, 0x09, 0x0d, 0x11, 0x0d, 0x0e, 0x0f, 0x10, 0x10, 0x11, 0x10, 0x0a, 0x0c, 0x12, 0x13, + 0x12, 0x10, 0x13, 0x0f, 0x10, 0x10, 0x10, 0xff, 0xc9, 0x00, 0x0b, 0x08, 0x00, 0x01, 0x00, 0x01, + 0x01, 0x01, 0x11, 0x00, 0xff, 0xcc, 0x00, 0x06, 0x00, 0x10, 0x10, 0x05, 0xff, 0xda, 0x00, 0x08, + 0x01, 0x01, 0x00, 0x00, 0x3f, 0x00, 0xd2, 0xcf, 0x20, 0xff, 0xd9, +}; + +ConfigureSystem::ConfigureSystem(QWidget* parent) + : QWidget(parent), ui(new Ui::ConfigureSystem), + profile_manager(std::make_unique<Service::Account::ProfileManager>()) { ui->setupUi(this); connect(ui->combo_birthmonth, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, @@ -32,6 +62,45 @@ ConfigureSystem::ConfigureSystem(QWidget* parent) : QWidget(parent), ui(new Ui:: connect(ui->button_regenerate_console_id, &QPushButton::clicked, this, &ConfigureSystem::refreshConsoleID); + layout = new QVBoxLayout; + tree_view = new QTreeView; + item_model = new QStandardItemModel(tree_view); + tree_view->setModel(item_model); + + tree_view->setAlternatingRowColors(true); + tree_view->setSelectionMode(QHeaderView::SingleSelection); + tree_view->setSelectionBehavior(QHeaderView::SelectRows); + tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); + tree_view->setSortingEnabled(true); + tree_view->setEditTriggers(QHeaderView::NoEditTriggers); + tree_view->setUniformRowHeights(true); + tree_view->setIconSize({64, 64}); + tree_view->setContextMenuPolicy(Qt::NoContextMenu); + + item_model->insertColumns(0, 1); + item_model->setHeaderData(0, Qt::Horizontal, "Users"); + + // We must register all custom types with the Qt Automoc system so that we are able to use it + // with signals/slots. In this case, QList falls under the umbrells of custom types. + qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); + + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + layout->addWidget(tree_view); + + ui->scrollArea->setLayout(layout); + + connect(tree_view, &QTreeView::clicked, this, &ConfigureSystem::SelectUser); + + connect(ui->pm_add, &QPushButton::pressed, this, &ConfigureSystem::AddUser); + connect(ui->pm_rename, &QPushButton::pressed, this, &ConfigureSystem::RenameUser); + connect(ui->pm_remove, &QPushButton::pressed, this, &ConfigureSystem::DeleteUser); + connect(ui->pm_set_image, &QPushButton::pressed, this, &ConfigureSystem::SetUserImage); + + scene = new QGraphicsScene; + ui->current_user_icon->setScene(scene); + this->setConfiguration(); } @@ -39,16 +108,74 @@ ConfigureSystem::~ConfigureSystem() = default; void ConfigureSystem::setConfiguration() { enabled = !Core::System::GetInstance().IsPoweredOn(); - ui->edit_username->setText(QString::fromStdString(Settings::values.username)); + ui->combo_language->setCurrentIndex(Settings::values.language_index); + + item_model->removeRows(0, item_model->rowCount()); + list_items.clear(); + + PopulateUserList(); + UpdateCurrentUser(); +} + +static QPixmap GetIcon(Service::Account::UUID uuid) { + const auto icon_url = QString::fromStdString(GetImagePath(uuid)); + QPixmap icon{icon_url}; + + if (!icon) { + icon.fill(Qt::black); + icon.loadFromData(backup_jpeg.data(), backup_jpeg.size()); + } + + return icon; +} + +void ConfigureSystem::PopulateUserList() { + const auto& profiles = profile_manager->GetAllUsers(); + for (const auto& user : profiles) { + Service::Account::ProfileBase profile; + if (!profile_manager->GetProfileBase(user, profile)) + continue; + + const auto username = Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); + + list_items.push_back(QList<QStandardItem*>{new QStandardItem{ + GetIcon(user).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + QString::fromStdString(username + '\n' + user.FormatSwitch())}}); + } + + for (const auto& item : list_items) + item_model->appendRow(item); +} + +void ConfigureSystem::UpdateCurrentUser() { + ui->pm_add->setEnabled(profile_manager->GetUserCount() < Service::Account::MAX_USERS); + + const auto& current_user = profile_manager->GetUser(Settings::values.current_user); + ASSERT(current_user != boost::none); + const auto username = GetAccountUsername(*current_user); + + scene->clear(); + scene->addPixmap( + GetIcon(*current_user).scaled(48, 48, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + ui->current_user_username->setText(QString::fromStdString(username)); } void ConfigureSystem::ReadSystemSettings() {} +std::string ConfigureSystem::GetAccountUsername(Service::Account::UUID uuid) const { + Service::Account::ProfileBase profile; + if (!profile_manager->GetProfileBase(uuid, profile)) + return ""; + return Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(profile.username.data()), profile.username.size()); +} + void ConfigureSystem::applyConfiguration() { if (!enabled) return; - Settings::values.username = ui->edit_username->text().toStdString(); + Settings::values.language_index = ui->combo_language->currentIndex(); Settings::Apply(); } @@ -92,3 +219,130 @@ void ConfigureSystem::refreshConsoleID() { ui->label_console_id->setText( tr("Console ID: 0x%1").arg(QString::number(console_id, 16).toUpper())); } + +void ConfigureSystem::SelectUser(const QModelIndex& index) { + Settings::values.current_user = + std::clamp<std::size_t>(index.row(), 0, profile_manager->GetUserCount() - 1); + + UpdateCurrentUser(); + + ui->pm_remove->setEnabled(profile_manager->GetUserCount() >= 2); + ui->pm_rename->setEnabled(true); + ui->pm_set_image->setEnabled(true); +} + +void ConfigureSystem::AddUser() { + Service::Account::UUID uuid; + uuid.Generate(); + + bool ok = false; + const auto username = + QInputDialog::getText(this, tr("Enter Username"), tr("Enter a username for the new user:"), + QLineEdit::Normal, QString(), &ok); + if (!ok) + return; + + profile_manager->CreateNewUser(uuid, username.toStdString()); + + item_model->appendRow(new QStandardItem{ + GetIcon(uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + QString::fromStdString(username.toStdString() + '\n' + uuid.FormatSwitch())}); +} + +void ConfigureSystem::RenameUser() { + const auto user = tree_view->currentIndex().row(); + const auto uuid = profile_manager->GetUser(user); + ASSERT(uuid != boost::none); + const auto username = GetAccountUsername(*uuid); + + Service::Account::ProfileBase profile; + if (!profile_manager->GetProfileBase(*uuid, profile)) + return; + + bool ok = false; + const auto new_username = + QInputDialog::getText(this, tr("Enter Username"), tr("Enter a new username:"), + QLineEdit::Normal, QString::fromStdString(username), &ok); + + if (!ok) + return; + + std::fill(profile.username.begin(), profile.username.end(), '\0'); + const auto username_std = new_username.toStdString(); + if (username_std.size() > profile.username.size()) { + std::copy_n(username_std.begin(), std::min(profile.username.size(), username_std.size()), + profile.username.begin()); + } else { + std::copy(username_std.begin(), username_std.end(), profile.username.begin()); + } + + profile_manager->SetProfileBase(*uuid, profile); + + item_model->setItem( + user, 0, + new QStandardItem{ + GetIcon(*uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + tr("%1\n%2", "%1 is the profile username, %2 is the formatted UUID (e.g. " + "00112233-4455-6677-8899-AABBCCDDEEFF))") + .arg(QString::fromStdString(username_std), + QString::fromStdString(uuid->FormatSwitch()))}); + UpdateCurrentUser(); +} + +void ConfigureSystem::DeleteUser() { + const auto index = tree_view->currentIndex().row(); + const auto uuid = profile_manager->GetUser(index); + ASSERT(uuid != boost::none); + const auto username = GetAccountUsername(*uuid); + + const auto confirm = + QMessageBox::question(this, tr("Confirm Delete"), + tr("You are about to delete user with name %1. Are you sure?") + .arg(QString::fromStdString(username))); + + if (confirm == QMessageBox::No) + return; + + if (Settings::values.current_user == tree_view->currentIndex().row()) + Settings::values.current_user = 0; + UpdateCurrentUser(); + + if (!profile_manager->RemoveUser(*uuid)) + return; + + item_model->removeRows(tree_view->currentIndex().row(), 1); + tree_view->clearSelection(); + + ui->pm_remove->setEnabled(false); + ui->pm_rename->setEnabled(false); +} + +void ConfigureSystem::SetUserImage() { + const auto index = tree_view->currentIndex().row(); + const auto uuid = profile_manager->GetUser(index); + ASSERT(uuid != boost::none); + const auto username = GetAccountUsername(*uuid); + + const auto file = QFileDialog::getOpenFileName(this, tr("Select User Image"), QString(), + "JPEG Images (*.jpg *.jpeg)"); + + if (file.isEmpty()) + return; + + FileUtil::Delete(GetImagePath(*uuid)); + + const auto raw_path = + FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + "/system/save/8000000000000010"; + if (FileUtil::Exists(raw_path) && !FileUtil::IsDirectory(raw_path)) + FileUtil::Delete(raw_path); + + FileUtil::CreateFullPath(GetImagePath(*uuid)); + FileUtil::Copy(file.toStdString(), GetImagePath(*uuid)); + + item_model->setItem( + index, 0, + new QStandardItem{ + GetIcon(*uuid).scaled(64, 64, Qt::IgnoreAspectRatio, Qt::SmoothTransformation), + QString::fromStdString(username + '\n' + uuid->FormatSwitch())}); + UpdateCurrentUser(); +} diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index f13de17d4..b73e0719c 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -5,8 +5,21 @@ #pragma once #include <memory> + +#include <QList> #include <QWidget> +namespace Service::Account { +class ProfileManager; +struct UUID; +} // namespace Service::Account + +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; +class QTreeView; +class QVBoxLayout; + namespace Ui { class ConfigureSystem; } @@ -21,18 +34,36 @@ public: void applyConfiguration(); void setConfiguration(); + void PopulateUserList(); + void UpdateCurrentUser(); + public slots: void updateBirthdayComboBox(int birthmonth_index); void refreshConsoleID(); + void SelectUser(const QModelIndex& index); + void AddUser(); + void RenameUser(); + void DeleteUser(); + void SetUserImage(); + private: void ReadSystemSettings(); + std::string GetAccountUsername(Service::Account::UUID uuid) const; + + QVBoxLayout* layout; + QTreeView* tree_view; + QStandardItemModel* item_model; + QGraphicsScene* scene; + + std::vector<QList<QStandardItem*>> list_items; std::unique_ptr<Ui::ConfigureSystem> ui; bool enabled; - std::u16string username; int birthmonth, birthday; int language_index; int sound_index; + + std::unique_ptr<Service::Account::ProfileManager> profile_manager; }; diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index f3f8db038..020b32a37 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -7,7 +7,7 @@ <x>0</x> <y>0</y> <width>360</width> - <height>377</height> + <height>483</height> </rect> </property> <property name="windowTitle"> @@ -22,34 +22,28 @@ <string>System Settings</string> </property> <layout class="QGridLayout" name="gridLayout"> - <item row="0" column="0"> - <widget class="QLabel" name="label_username"> + <item row="1" column="0"> + <widget class="QLabel" name="label_language"> <property name="text"> - <string>Username</string> + <string>Language</string> </property> </widget> </item> - <item row="0" column="1"> - <widget class="QLineEdit" name="edit_username"> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Fixed"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="maxLength"> - <number>32</number> + <item row="0" column="0"> + <widget class="QLabel" name="label_birthday"> + <property name="text"> + <string>Birthday</string> </property> </widget> </item> - <item row="1" column="0"> - <widget class="QLabel" name="label_birthday"> + <item row="3" column="0"> + <widget class="QLabel" name="label_console_id"> <property name="text"> - <string>Birthday</string> + <string>Console ID:</string> </property> </widget> </item> - <item row="1" column="1"> + <item row="0" column="1"> <layout class="QHBoxLayout" name="horizontalLayout_birthday2"> <item> <widget class="QComboBox" name="combo_birthmonth"> @@ -120,14 +114,7 @@ </item> </layout> </item> - <item row="2" column="0"> - <widget class="QLabel" name="label_language"> - <property name="text"> - <string>Language</string> - </property> - </widget> - </item> - <item row="2" column="1"> + <item row="1" column="1"> <widget class="QComboBox" name="combo_language"> <property name="toolTip"> <string>Note: this can be overridden when region setting is auto-select</string> @@ -187,31 +174,31 @@ <string>Russian (Русский)</string> </property> </item> - <item> - <property name="text"> - <string>Taiwanese</string> - </property> - </item> - <item> - <property name="text"> - <string>British English</string> - </property> - </item> - <item> - <property name="text"> - <string>Canadian French</string> - </property> - </item> - <item> - <property name="text"> - <string>Latin American Spanish</string> - </property> - </item> - <item> - <property name="text"> - <string>Simplified Chinese</string> - </property> - </item> + <item> + <property name="text"> + <string>Taiwanese</string> + </property> + </item> + <item> + <property name="text"> + <string>British English</string> + </property> + </item> + <item> + <property name="text"> + <string>Canadian French</string> + </property> + </item> + <item> + <property name="text"> + <string>Latin American Spanish</string> + </property> + </item> + <item> + <property name="text"> + <string>Simplified Chinese</string> + </property> + </item> <item> <property name="text"> <string>Traditional Chinese (正體中文)</string> @@ -219,14 +206,14 @@ </item> </widget> </item> - <item row="3" column="0"> + <item row="2" column="0"> <widget class="QLabel" name="label_sound"> <property name="text"> <string>Sound output mode</string> </property> </widget> </item> - <item row="3" column="1"> + <item row="2" column="1"> <widget class="QComboBox" name="combo_sound"> <item> <property name="text"> @@ -245,14 +232,7 @@ </item> </widget> </item> - <item row="4" column="0"> - <widget class="QLabel" name="label_console_id"> - <property name="text"> - <string>Console ID:</string> - </property> - </widget> - </item> - <item row="4" column="1"> + <item row="3" column="1"> <widget class="QPushButton" name="button_regenerate_console_id"> <property name="sizePolicy"> <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> @@ -272,6 +252,143 @@ </widget> </item> <item> + <widget class="QGroupBox" name="gridGroupBox"> + <property name="title"> + <string>Profile Manager</string> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="sizeConstraint"> + <enum>QLayout::SetNoConstraint</enum> + </property> + <item row="0" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QLabel" name="label"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Current User</string> + </property> + </widget> + </item> + <item> + <widget class="QGraphicsView" name="current_user_icon"> + <property name="minimumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>48</width> + <height>48</height> + </size> + </property> + <property name="verticalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="horizontalScrollBarPolicy"> + <enum>Qt::ScrollBarAlwaysOff</enum> + </property> + <property name="interactive"> + <bool>false</bool> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="current_user_username"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Username</string> + </property> + </widget> + </item> + </layout> + </item> + <item row="1" column="0"> + <widget class="QScrollArea" name="scrollArea"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShape"> + <enum>QFrame::StyledPanel</enum> + </property> + <property name="widgetResizable"> + <bool>false</bool> + </property> + </widget> + </item> + <item row="2" column="0"> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <widget class="QPushButton" name="pm_set_image"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Set Image</string> + </property> + </widget> + </item> + <item> + <spacer name="horizontalSpacer"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>40</width> + <height>20</height> + </size> + </property> + </spacer> + </item> + <item> + <widget class="QPushButton" name="pm_add"> + <property name="text"> + <string>Add</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pm_rename"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Rename</string> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="pm_remove"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Remove</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </item> + <item> <widget class="QLabel" name="label_disable_info"> <property name="text"> <string>System settings are available only when game is not running.</string> @@ -281,19 +398,6 @@ </property> </widget> </item> - <item> - <spacer name="verticalSpacer"> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="sizeHint" stdset="0"> - <size> - <width>20</width> - <height>40</height> - </size> - </property> - </spacer> - </item> </layout> </item> </layout> 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.cpp b/src/yuzu/main.cpp index bef9df00d..47f494841 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -10,6 +10,7 @@ // VFS includes must be before glad as they will conflict with Windows file api, which uses defines. #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" +#include "core/hle/service/acc/profile_manager.h" // These are wrappers to avoid the calls to CreateDirectory and CreateFile becuase of the Windows // defines. @@ -60,6 +61,8 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/hle/kernel/process.h" #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/fsp_ldr.h" +#include "core/hle/service/nfp/nfp.h" +#include "core/hle/service/sm/sm.h" #include "core/loader/loader.h" #include "core/perf_stats.h" #include "core/settings.h" @@ -100,6 +103,8 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif +constexpr u64 DLC_BASE_TITLE_ID_MASK = 0xFFFFFFFFFFFFE000; + /** * "Callouts" are one-time instructional messages shown to the user. In the config settings, there * is a bitfield "callout_flags" options, used to track if a message has already been shown to the @@ -422,6 +427,7 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::SDMC); }); connect(ui.action_Exit, &QAction::triggered, this, &QMainWindow::close); + connect(ui.action_Load_Amiibo, &QAction::triggered, this, &GMainWindow::OnLoadAmiibo); // Emulation connect(ui.action_Start, &QAction::triggered, this, &GMainWindow::OnStartGame); @@ -690,6 +696,7 @@ void GMainWindow::ShutdownGame() { ui.action_Stop->setEnabled(false); ui.action_Restart->setEnabled(false); ui.action_Report_Compatibility->setEnabled(false); + ui.action_Load_Amiibo->setEnabled(false); render_window->hide(); game_list->show(); game_list->setFilterFocus(); @@ -751,12 +758,43 @@ void GMainWindow::OnGameListOpenFolder(u64 program_id, GameListOpenTarget target open_target = "Save Data"; const std::string nand_dir = FileUtil::GetUserPath(FileUtil::UserPath::NANDDir); ASSERT(program_id != 0); - // TODO(tech4me): Update this to work with arbitrary user profile - // Refer to core/hle/service/acc/profile_manager.cpp ProfileManager constructor - constexpr u128 user_id = {1, 0}; + + Service::Account::ProfileManager manager{}; + const auto user_ids = manager.GetAllUsers(); + QStringList list; + for (const auto& user_id : user_ids) { + if (user_id == Service::Account::UUID{}) + continue; + Service::Account::ProfileBase base; + if (!manager.GetProfileBase(user_id, base)) + continue; + + list.push_back(QString::fromStdString(Common::StringFromFixedZeroTerminatedBuffer( + reinterpret_cast<const char*>(base.username.data()), base.username.size()))); + } + + bool ok = false; + const auto index_string = + QInputDialog::getItem(this, tr("Select User"), + tr("Please select the user's save data you would like to open."), + list, Settings::values.current_user, false, &ok); + if (!ok) + return; + + const auto index = list.indexOf(index_string); + ASSERT(index != -1 && index < 8); + + const auto user_id = manager.GetUser(index); + ASSERT(user_id != boost::none); path = nand_dir + FileSys::SaveDataFactory::GetFullPath(FileSys::SaveDataSpaceId::NandUser, FileSys::SaveDataType::SaveData, - program_id, user_id, 0); + program_id, user_id->uuid, 0); + + if (!FileUtil::Exists(path)) { + FileUtil::CreateFullPath(path); + FileUtil::CreateDir(path); + } + break; } case GameListOpenTarget::ModData: { @@ -823,14 +861,10 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src } void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_path) { - const auto path = fmt::format("{}{:016X}/romfs", - FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), program_id); - - const auto failed = [this, &path] { + const auto failed = [this] { QMessageBox::warning(this, tr("RomFS Extraction Failed!"), tr("There was an error copying the RomFS files or the user " "cancelled the operation.")); - vfs->DeleteDirectory(path); }; const auto loader = Loader::GetLoader(vfs->OpenFile(game_path, FileSys::Mode::Read)); @@ -845,10 +879,24 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa return; } - const auto romfs = - loader->IsRomFSUpdatable() - ? FileSys::PatchManager(program_id).PatchRomFS(file, loader->ReadRomFSIVFCOffset()) - : file; + const auto installed = Service::FileSystem::GetUnionContents(); + auto romfs_title_id = SelectRomFSDumpTarget(*installed, program_id); + + if (!romfs_title_id) { + failed(); + return; + } + + const auto path = fmt::format( + "{}{:016X}/romfs", FileUtil::GetUserPath(FileUtil::UserPath::DumpDir), *romfs_title_id); + + FileSys::VirtualFile romfs; + + if (*romfs_title_id == program_id) { + romfs = file; + } else { + romfs = installed->GetEntry(*romfs_title_id, FileSys::ContentRecordType::Data)->GetRomFS(); + } const auto extracted = FileSys::ExtractRomFS(romfs, FileSys::RomFSExtractionType::Full); if (extracted == nullptr) { @@ -860,6 +908,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa if (out == nullptr) { failed(); + vfs->DeleteDirectory(path); return; } @@ -870,8 +919,11 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa "files into the new directory while <br>skeleton will only create the directory " "structure."), {"Full", "Skeleton"}, 0, false, &ok); - if (!ok) + if (!ok) { failed(); + vfs->DeleteDirectory(path); + return; + } const auto full = res == "Full"; const auto entry_size = CalculateRomFSEntrySize(extracted, full); @@ -888,6 +940,7 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa } else { progress.close(); failed(); + vfs->DeleteDirectory(path); } } @@ -1174,6 +1227,7 @@ void GMainWindow::OnStartGame() { ui.action_Report_Compatibility->setEnabled(true); discord_rpc->Update(); + ui.action_Load_Amiibo->setEnabled(true); } void GMainWindow::OnPauseGame() { @@ -1278,6 +1332,27 @@ void GMainWindow::OnConfigure() { } } +void GMainWindow::OnLoadAmiibo() { + const QString extensions{"*.bin"}; + const QString file_filter = tr("Amiibo File (%1);; All Files (*.*)").arg(extensions); + const QString filename = QFileDialog::getOpenFileName(this, tr("Load Amiibo"), "", file_filter); + if (!filename.isEmpty()) { + Core::System& system{Core::System::GetInstance()}; + Service::SM::ServiceManager& sm = system.ServiceManager(); + auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user"); + if (nfc != nullptr) { + auto nfc_file = FileUtil::IOFile(filename.toStdString(), "rb"); + if (!nfc_file.IsOpen()) { + return; + } + std::vector<u8> amiibo_buffer(nfc_file.GetSize()); + nfc_file.ReadBytes(amiibo_buffer.data(), amiibo_buffer.size()); + nfc_file.Close(); + nfc->LoadAmiibo(amiibo_buffer); + } + } +} + void GMainWindow::OnAbout() { AboutDialog aboutDialog(this); aboutDialog.exec(); @@ -1318,15 +1393,17 @@ void GMainWindow::UpdateStatusBar() { void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string details) { QMessageBox::StandardButton answer; QString status_message; - const QString common_message = tr( - "The game you are trying to load requires additional files from your Switch to be dumped " - "before playing.<br/><br/>For more information on dumping these files, please see the " - "following wiki page: <a " - "href='https://yuzu-emu.org/wiki/" - "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System " - "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to quit " - "back to the game list? Continuing emulation may result in crashes, corrupted save " - "data, or other bugs."); + const QString common_message = + tr("The game you are trying to load requires additional files from your Switch to be " + "dumped " + "before playing.<br/><br/>For more information on dumping these files, please see the " + "following wiki page: <a " + "href='https://yuzu-emu.org/wiki/" + "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System " + "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to " + "quit " + "back to the game list? Continuing emulation may result in crashes, corrupted save " + "data, or other bugs."); switch (result) { case Core::System::ResultStatus::ErrorSystemFiles: { QString message = "yuzu was unable to locate a Switch system archive"; @@ -1357,9 +1434,12 @@ void GMainWindow::OnCoreError(Core::System::ResultStatus result, std::string det this, tr("Fatal Error"), tr("yuzu has encountered a fatal error, please see the log for more details. " "For more information on accessing the log, please see the following page: " - "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How to " - "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game list? " - "Continuing emulation may result in crashes, corrupted save data, or other bugs."), + "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How " + "to " + "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game " + "list? " + "Continuing emulation may result in crashes, corrupted save data, or other " + "bugs."), QMessageBox::Yes | QMessageBox::No, QMessageBox::No); status_message = "Fatal Error encountered"; break; @@ -1459,6 +1539,42 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) { } } +boost::optional<u64> GMainWindow::SelectRomFSDumpTarget( + const FileSys::RegisteredCacheUnion& installed, u64 program_id) { + const auto dlc_entries = + installed.ListEntriesFilter(FileSys::TitleType::AOC, FileSys::ContentRecordType::Data); + std::vector<FileSys::RegisteredCacheEntry> dlc_match; + dlc_match.reserve(dlc_entries.size()); + std::copy_if(dlc_entries.begin(), dlc_entries.end(), std::back_inserter(dlc_match), + [&program_id, &installed](const FileSys::RegisteredCacheEntry& entry) { + return (entry.title_id & DLC_BASE_TITLE_ID_MASK) == program_id && + installed.GetEntry(entry)->GetStatus() == Loader::ResultStatus::Success; + }); + + std::vector<u64> romfs_tids; + romfs_tids.push_back(program_id); + for (const auto& entry : dlc_match) + romfs_tids.push_back(entry.title_id); + + if (romfs_tids.size() > 1) { + QStringList list{"Base"}; + for (std::size_t i = 1; i < romfs_tids.size(); ++i) + list.push_back(QStringLiteral("DLC %1").arg(romfs_tids[i] & 0x7FF)); + + bool ok; + const auto res = QInputDialog::getItem( + this, tr("Select RomFS Dump Target"), + tr("Please select which RomFS you would like to dump."), list, 0, false, &ok); + if (!ok) { + return boost::none; + } + + return romfs_tids[list.indexOf(res)]; + } + + return program_id; +} + bool GMainWindow::ConfirmClose() { if (emu_thread == nullptr || !UISettings::values.confirm_before_closing) return true; diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 3663d6aed..7c7c223e1 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -10,6 +10,7 @@ #include <QMainWindow> #include <QTimer> +#include <boost/optional.hpp> #include "common/common_types.h" #include "core/core.h" #include "ui_main.h" @@ -29,8 +30,9 @@ class WaitTreeWidget; enum class GameListOpenTarget; namespace FileSys { +class RegisteredCacheUnion; class VfsFilesystem; -} +} // namespace FileSys namespace Tegra { class DebugContext; @@ -164,6 +166,7 @@ private slots: void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); void OnMenuRecentFile(); void OnConfigure(); + void OnLoadAmiibo(); void OnAbout(); void OnToggleFilterBar(); void OnDisplayTitleBars(bool); @@ -175,6 +178,8 @@ private slots: void OnReinitializeKeys(ReinitializeKeyBehavior behavior); private: + boost::optional<u64> SelectRomFSDumpTarget(const FileSys::RegisteredCacheUnion&, + u64 program_id); void UpdateStatusBar(); Ui::MainWindow ui; diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index dffd9c788..48d099591 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui @@ -57,8 +57,8 @@ <string>Recent Files</string> </property> </widget> - <addaction name="action_Install_File_NAND" /> - <addaction name="separator"/> + <addaction name="action_Install_File_NAND"/> + <addaction name="separator"/> <addaction name="action_Load_File"/> <addaction name="action_Load_Folder"/> <addaction name="separator"/> @@ -68,6 +68,8 @@ <addaction name="action_Select_NAND_Directory"/> <addaction name="action_Select_SDMC_Directory"/> <addaction name="separator"/> + <addaction name="action_Load_Amiibo"/> + <addaction name="separator"/> <addaction name="action_Exit"/> </widget> <widget class="QMenu" name="menu_Emulation"> @@ -117,11 +119,14 @@ <addaction name="menu_Tools" /> <addaction name="menu_Help"/> </widget> - <action name="action_Install_File_NAND"> - <property name="text"> - <string>Install File to NAND...</string> - </property> - </action> + <action name="action_Install_File_NAND"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="text"> + <string>Install File to NAND...</string> + </property> + </action> <action name="action_Load_File"> <property name="text"> <string>Load File...</string> @@ -253,6 +258,14 @@ <string>Restart</string> </property> </action> + <action name="action_Load_Amiibo"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string>Load Amiibo...</string> + </property> + </action> <action name="action_Report_Compatibility"> <property name="enabled"> <bool>false</bool> diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 5e42e48b2..b456266a6 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -8,6 +8,7 @@ #include "common/file_util.h" #include "common/logging/log.h" #include "common/param_package.h" +#include "core/hle/service/acc/profile_manager.h" #include "core/settings.h" #include "input_common/main.h" #include "yuzu_cmd/config.h" @@ -125,10 +126,11 @@ void Config::ReadValues() { // System Settings::values.use_docked_mode = sdl2_config->GetBoolean("System", "use_docked_mode", false); - Settings::values.username = sdl2_config->Get("System", "username", "yuzu"); - if (Settings::values.username.empty()) { - Settings::values.username = "yuzu"; - } + Settings::values.enable_nfc = sdl2_config->GetBoolean("System", "enable_nfc", true); + const auto size = sdl2_config->GetInteger("System", "users_size", 0); + + Settings::values.current_user = std::clamp<int>( + sdl2_config->GetInteger("System", "current_user", 0), 0, Service::Account::MAX_USERS - 1); // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index a97b75f7b..e0b223cd6 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -174,6 +174,10 @@ use_virtual_sd = # 1: Yes, 0 (default): No use_docked_mode = +# Allow the use of NFC in games +# 1 (default): Yes, 0 : No +enable_nfc = + # Sets the account username, max length is 32 characters # yuzu (default) username = yuzu |